Script zum Upload von Bildern

Bearbeiten

seit 12 Februar 2016 kommt die Fehlermeldung „Fetching a token via action=login is deprecated. Use action=query&meta=tokens&type=login instead.“

Die Funktion liefert zwar noch einen Token, beim Einlogversuch kommt aber die Meldung „WrongToken“

Werde mich drum kümmern

Zweck / Funktion / Umgebung

Bearbeiten

Dieses Script läuft unter unixoiden Systemen auf denen PHP (ab 5.3) mit der curl Erweiterung installiert ist. Es ist ein Kommandozeilentool (CLI - Command Line Interface).

Installation / Aufruf

Bearbeiten

Den Programmtext in eine beliebige Datei (im Folgenden nehmen wir wiki-upload.cli.php ) kopieren und die Datei mit "chmod u+x wiki-upload.cli.php" als ausführbar markieren. Aufgerufen wird es mit "./wiki-upload.cli.php <Bilddatei>". Beim ersten Aufruf legt das Programm eine Konfigurationsdatei "~/.wiki-upload.cli.php.config" im Homeverzeichnis an (Anmerk.: der Name der Konfigurationsdatei beginnt mit "." - bei Unixoiden steht der Punkt für "versteckte Datei"). In der Konfigurationsdatei müssen Benutzername und Password in die entsprechenden Variablen eingetragen werden.

Der lokale Name der Bilddatei wird bei Wiki übernommen. Vorher also der Datei einen vernünftigen Namen geben!

Mehrere Bilder können in einer For-Schleife hochgeladen werden:

 # Syntax für die bash-shell
 for  Datei  in  $(ls *.[j,J][p,P][g,G]) 
 do
   wiki-upload.cli.php  $Datei
 done


Sicherheit

Bearbeiten

Bei dem Programm wurde kein Wert auf Sicherheit gelegt. Insbesondere das Abspeichern von Passwörtern in Dateien ist nicht sicher. Auch werden Protokolldaten (einschl. Password) im Verzeichnis /tmp abgelegt. Das Programm kann aber natürlich um Sicherheitsaspekte erweitert werden.

Programmtext

Bearbeiten
#!/usr/bin/php -f 
<?php
// Kommandzilenversion (CLI). 
// Die oberste Zeile daher bitte so belassen wie sie ist. 

 
// Wichtigste Quelle: 
// https://www.mediawiki.org/wiki/User:Bcoughlan/Login_with_curl
// über
// https://www.mediawiki.org/wiki/API:Login 

 
function check_uploadfile()
{
  // Die hochzuladende Datei prüfen (hier wird z.Zt. nur auf 
  // die Existenz geprüft) 
 
  // Das Array $settings global benutzen. 
  global $settings;
 
  if (!file_exists($settings['upload_file_name'])) 
  {
    throw new Exception("upolaodfile don't exist - check the first parameter of this porgramm");
  }
}
 
 
function read_configfile()
{
  // Das Array $settings global benutzen. 
  global $settings;
 
  // Name dieses Programms ohne Pfadangabe der Variablen
  // $settings;['my_filename'] zuweisen. 
  $settings['my_filename'] = basename( __FILE__ );
  // Anmerkung: da diese Var. zur Benennung der Konfig-Dateien benutzt 
  // wird, wirken sich Namensänderungen dieses Programms auch automatisch 
  // auf die Namen der Konfig-Dateien aus. Das Programm kann also nach 
  // Herzenslust umgenannt werden. 
 
  // Home Verz. ermitteln (kann in $_SERVER und mal in $_ENV stehen)
  if (isset($_SERVER['HOME'])) {$settings['my_HOME'] = $_SERVER['HOME'];}; 
  if (isset(   $_ENV['HOME'])) {$settings['my_HOME'] =    $_ENV['HOME'];}; 
  if (empty($settings['my_HOME'])) 
  {
    throw new Exception("Error getting enviroment var. HOME \n");
  }
 
  // den Dateinamen für die Konfig-Datei im Home-Verz. festlegen. 
  $settings['home_configfilename'] = "${settings['my_HOME']}/.${settings['my_filename']}.config";
 
  // Falls die Konfig-Datei im Home-Verz. nicht existiert ...  
  if (!file_exists($settings['home_configfilename'])) 
  {
    // ... Anlegen der Datei ...
    file_put_contents($settings['home_configfilename'], $GLOBALS['default_config']);
    // ... um Programmabbruch mit Hinweis die Konfigdatei nun zu editieren .... 
    if (file_exists($settings['home_configfilename'])) 
    {
      throw new Exception("${settings['home_configfilename']} created - pleas edit the file");
    }
    // ... und falls sie nicht angelegt werden konnte brechen wir ebenfalls ab. 
    else
    {
      throw new Exception("can't create configfile ${settings['home_configfilename']}");
    }
  } 
  // Wenn wir Funktion read_configfile() bis hier durchlaufen haben, sollte 
  // ein anständiges Konfigfile vorliegen ... wir lesen es nun per Include ein. 
 
  // Includen der Konfig-Datei.  
  // Beim Includen werden Felder des Arrays $settings gesetzt. 
  require($settings['home_configfilename']);
  // Testen ob der Include funktioniert hat:
  if (empty($settings['wikiroot'])) 
  {
    throw new Exception("${settings['home_configfilename']} not includet");
  }
 
  // Falls direkt neben der Programmdatei auch eine Konfig-Datei liegt  
  // wird diese ebenfalls eingelesen. 
  if (file_exists(__FILE__ . ".config")) 
  {
    require(__FILE__ . ".config"); 
  }
}
 
 
function httpRequest($url, $post="") 
{
  // Ueber diese Funktion findet die Kommunikation zum WIKI-Server statt. 
 
  // Die oben definieten Variablen hier nutzen
  global $settings;
 
  $ch = curl_init();
  // Allgemeine Werte für die Kommunikation festlegen. 
  curl_setopt ($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.9) Gecko/20071025 Firefox/2.0.0.9');
  curl_setopt ($ch, CURLOPT_URL, ($url));
  curl_setopt ($ch, CURLOPT_ENCODING, "UTF-8" );
  curl_setopt ($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt ($ch, CURLOPT_COOKIEFILE, $settings['cookiefile']);
  curl_setopt ($ch, CURLOPT_COOKIEJAR,  $settings['cookiefile']);
  // Hier kommen die Post-Daten hinzu
  if (!empty($post)) curl_setopt($ch,CURLOPT_POSTFIELDS,$post);
 
  // Geschwätziges Protokoll in $settings['curl_verbose_file'] ausgeben. 
  // Achtung: auf Rechnern mit mehreren Benutzern können diese  Protokoll- 
  // daten u.u. von Dritten (nebst Passwörter etc.) eingesehen  werden. 
  if (!@isset($settings['curl_log']))
  {
    // Keine Ahnung warum, aber mit "fopen(handle,'w')" schreibt curl 
    // nicht alle Daten ins Logfile ... Option 'a' in Kombination mit 
    // "seek()" ist ein Workarount. 
    // Wer bedenken hat, dass die Daten von einem Mitbenutzer des 
    // Rechners missbraucht werden sollte $curl_verbose_file auf "/dev/null" 
    // seten. 
    $settings['curl_log'] = fopen($settings['curl_verbose_file'], 'a');
    fseek ($settings['curl_log'] , 0);
  };
  fwrite($settings['curl_log'], "#-POST-#" . print_r($post, true) . "#-POST-#\n");
  curl_setopt($ch, CURLOPT_VERBOSE, true); 
  curl_setopt($ch, CURLOPT_STDERR, $settings['curl_log']); 
 
  // curl Anfrage absetzen 
  $xml = curl_exec($ch);
  if (!$xml) 
  {
    throw new Exception("Error getting data from server ($url): " . curl_error($ch));
  }
  curl_close($ch);
 
  // Ausgabe der Antworten des Wiki-Server
  // (bischen viele Konsolenausgaben ... aber man kanns ja rauswerfen ...)
  $dom = new DOMDocument();
  $dom->loadXML($xml);
  $dom->preserveWhiteSpace = FALSE;
  $dom->formatOutput = TRUE;
  $ausgabe=$dom->saveXML(); 
  echo "Response of http:Request (Begin)\n";
  echo $ausgabe;
  echo "(End)\n";
  // (... ggf. bis hier die Konsolenausgaben löschen falls sie stören)
 
  // Rueckgabe 
  return $xml;
}
 
 
function login ($user, $pass, $token='') 
// Diese Funktion hat zwei Aufgaben: wird sie nur mit $user und $pass 
// aufgerufen, so holt sie nur einen Token zum Login beim Wikiserver. 
// Erst beim zweiten Aufruf dieser Funktion mit $token logt man sich 
// beim Wikiserver ein.
// ACHTUNG: beim zweiten Aufruf enthält das Ergebnis einen lgtoken. 
{
  // ganz oben definiertes Array §settings hier nutzen
  global $settings;
  // an den Servernamen den Pfad dranhaengen
  $url = $settings['wikiroot'] . "/w/api.php";
  // Parameter fuer Aufruf zusammenbauen 
  $params  =  "action=login"; 
  $params .= "&lgname=$user";
  $params .= "&lgpassword=$pass";
  $params .= "&format=xml";
  // Falls dieser Funktion der Paramter $token übergeben wurde uebergeben 
  // wir ihn als Parameter (beim Aufruf des Wikiservers ohne Token erhalten 
  // wir einen solchen). 
  if (!empty($token)) 
  {
    $params .= "&lgtoken=$token";
  }
  // den Wikiserveraufruf absetzen
  $xml_str = httpRequest($url, $params);
  // Wenn der Aufruf absolut keine Antwort ergab geben wir eine Meldung raus. 
  if (empty($xml_str)) 
  {
    throw new Exception("No data received from server. Check that API is enabled.");
  }
  // Die XML-Antwort in ein Array umwandeln.  
  $xml_obj = simplexml_load_string($xml_str);
  if (!empty($token)) 
  {
    //Check for successful login
    $expr = "/api/login[@result='Success']";
    $result = $xml_obj->xpath($expr);
 
    if(!count($result)) 
    {
      throw new Exception("Login failed");
    }
  } 
  else 
  {
    $expr = "/api/login[@token]";
    $result = $xml_obj->xpath($expr);
 
    if(!count($result)) 
    {
      throw new Exception("Login token not found in XML");
    }
  }
 
  return $result[0]->attributes()->token;
}


function chk_userright ($right="upload") 
// Funktion testet, ob ein als Parameter übergebenes Bentzerrecht vergeben ist. 
// Als Default fragen wir nach dem Recht "upload". 
/* Eine Auswahl von möglichen Rechen bzw. Parametern sind: 
  autopatrol, createaccount, read, edit, createpage, createtalk, writeapi, 
  editmyusercss, editmyuserjs, viewmywatchlist, editmywatchlist, 
  viewmyprivateinfo, editmyprivateinfo, editmyoptions, centralauth-merge,
  abusefilter-view, abusefilter-log, translate, vipsscaler-test, upload,
  reupload-own, move-rootuserpages, move-categorypages, minoredit, purge,
  sendemail, translate-messagereview, translate-groupreview, 
  mwoauthmanagemygrants, reupload, move, collectionsaveasuserpage,
  collectionsaveascommunitypage, autoconfirmed, editsemiprotected,
  transcode-reset, kipcaptcha, abusefilter-log-detail, mwoauthproposeconsumer,
*/
/* Anmerkung: die Funktion kann genutzt werden, um zu Testen ob ein Benutzer 
  bereits eingeloggt ist (kann bei mehrmaligen Aufrufen eines CLI Programms das 
  ständige Ein- und Ausloggen ersparen sofern die Verbindungsdaten im Cookiefile 
  gespeichert sind). 
*/
{
  // ganz oben definiertes Array §settings hier nutzen
  global $settings;
  // an den Servernamen den Pfad  dranhaengen
  $url = $settings['wikiroot'] . "/w/api.php";
  // Parameter fuer Aufruf zusammenbauen 
  $params  = "action=query"; 
  $params .= "&meta=userinfo"; 
  $params .= "&uiprop=rights"; 
  $params .= "&format=xml";
  // den Wikiserveraufruf absetzen
  $xml_str = httpRequest($url, $params);
  // Die XML-Antwort in ein Objekt umwandeln.  
  $xml_obj = simplexml_load_string($xml_str);
  $expr = "/api/query/userinfo/rights/r";
  $result = $xml_obj->xpath($expr);
  // wegen Blödheit eine vernünftige XPath-Abfrage zu machen puhlen wir im Array
  $return=false; 
  foreach ($result  as $value) 
     if ($value == $right) $return=true;
  // die nächsten zwei Zeilen können zum debuggen mal auskommentiert werden 
  //   echo "Das Recht '$right' ";
  //   echo ($return ? "ist gesetzt" : "ist NICHT gesetzt");
  // Rückgabe als Booleanwert 
  return $return; 
}

 
function logout () 
{
  // Achtung: bei Ausloggen ist wiki ziemlich gruendlich. Auch Sessions von 
  // anderen Rechnern (Browser) werden beendet! Möglicherweise ist es sinnvoll, 
  // in diesem Program auf das Ausloggen zu verzichten. 
  //
  // ganz oben definiertes Array §settings hier nutzen
  global $settings;
  // an den Servernamen den Pfad  dranhaengen
  $url = $settings['wikiroot'] . "/w/api.php";
  // Parameter fuer Aufruf zusammenbauen 
  $params  =  "action=logout"; 
  $params .= "&format=xml";
  // Falls dieser Funktion der Paramter $token übergeben wurde übergeben 
  // wir ihn als Parameter (beim Aufruf des Wikiservers ohne Token erhalten 
  // wir einen solchen). 
  // den Wikiserveraufruf absetzen
  $xml_str = httpRequest($url, $params);
}
 
 
function get_edittoken() 
{
 
  /* Habe ursprünglich wie in https://www.mediawiki.org/wiki/API:Tokens
     beschrieben mir einen edit-Token mit "action=token" etc. geholt. 
     Das funktinierte zwar, habe aber immer die Warnung "action=tokens 
     has been deprecated. Please use action=query&amp;meta=tokens instead." 
     erhalten. Habe daher auf action/meta umgestellt. Bei der Serverantw. 
     heisst der Token nun auch "csrftoken" statt "token" (csrf steht fuer 
     "Cross-Site-Request-Forgery". */
 
  // ganz oben definiertes Array $settings hier nutzen
  global $settings;
  // an den Servernamen den Pfad dranhängen
  $url = $settings['wikiroot'] . "/w/api.php";
  // Parameter für Aufruf zusammenbauen 
  $params = array('action'   => "query", 
                  'meta'     => "tokens", 
                  'type'     => "csrf",  // Parameter type kann weg gelassen werden
                  'format'   => "xml");  
  // Werte fuer type (mehrere mit '|' trennen): csrf, deleteglobalaccount, patrol, 
  // rollback, setglobalaccountstatus, userrights, watch (Default: csrf)

  // den Wikiserveraufruf absetzen
  $xml_str = httpRequest($url, $params);
  // Wenn der Aufruf absolut keine Antwort ergab geben wir eine Meldung raus. 
  if (empty($xml_str)) 
  {
    throw new Exception("No data received from server. Check that API is enabled.");
  }
  // Die XML-Antwort in ein Array umwandeln.  
  $xml_obj = simplexml_load_string($xml_str);
 
  // Ermitteln des Token
  $expr = "/api/query/tokens[@csrftoken]";
  $result = $xml_obj->xpath($expr);
  if(!count($result)) 
  {
    throw new Exception("Login token not found in XML");
  }
 
  // Rückgabe des Token
  return $result[0]->attributes()->csrftoken; 
}
 
 
 
function upload($token) 
{
  echo $token;
 
  // ganz oben definiertes Array $settings hier nutzen
  global $settings;
  // an den Servernamen den Pfad  dranhängen
  $url = $settings['wikiroot'] . "/w/api.php";
  // Parameter fuer Aufruf zusammenbauen 
  // Anmerkung: beim original Uploadforumlar werden die Felder "commonet" und 
  // "text" gleich gesetzt. Comment erscheinen u.a. in der Tabelle von 
  //   http://de.wikipedia.org/wiki/Spezial:Dateien/Summer_..._hier!
  // ganz rechts. 
  $params = array('action'         => "upload", 
                  'comment'        => "${settings['upload_file_text']}", 
                  'text'           => "${settings['upload_file_text']}",
                  'filename'       => "${settings['upload_file_name']}",
                  'file'           => "@${settings['upload_file_name']}",
                  'watchlist'      => "watch", // (moegl. Werte: watch, preferences, nochange)
               // 'ignorewarnings' => "", // ohne Wert --> vorhandene Datei wird überschrieben 
                  'token'          => "$token",
                  'format'         => "xml");  
 
  // den Wikiserveraufruf absetzen
  $xml_str = httpRequest($url, $params);
  // Wenn der Aufruf absolut keine Antwort ergab geben wir eine Meldung raus. 
  if (empty($xml_str)) 
  {
    throw new Exception("No data received from server. Check that API is enabled.");
  }
  // Die XML-Antwort in ein Array umwandeln.  
  $xml_obj = simplexml_load_string($xml_str);
 
  return ;
}
 
 
/////////////// Beginn der Hauptprogramms /////////////
 
// Der Name der Upload-Datei ist der erste Programmparameter
$settings['upload_file_name']  =  $GLOBALS['argv'][1];
// Anmerk.: bei einem Ausbau des Programms sollten die Optionen mit getopt() 
// Ausgewertet werden. 
 
// Defaultwerte für die Konfigurationsdatei
// 
// Achtung: hier drunter kommt ein nowdoc, kein heredoc! 
// Nowdoc's werden nicht geparst (setzen aber PHP Ver. 5.3 voraus) 
// Das nowdoc entspricht dem Text einer Konfig-Datei in die u.a. 
// username und password eingetragen werden muss ...  
// Siehe nach dem ersten Programmdurchlauf ~/.<Name-dieses-Programms>.config
// 
// Zur besseren Quelltextlesbarkeit sind hier die Daten für commons eingetragen. 
// Durch Änderen der URL in $settings['wikiroot'] und der Vorlagen kann dieses 
// Programm für jede andere Wiki genutzt werden. 
$default_config =<<<'DEFAULT_CONFIG_NOWDOC'
<?php
$settings['wikiroot']          = "http://commons.wikimedia.org"; 
$settings['user']              = "mein Wikiaccount";
$settings['pass']              = "mein Wikipassword";
$settings['cookiefile']        = "cookies.tmp";
$settings['curl_verbose_file'] = sys_get_temp_dir() . '/' . $settings['my_filename'];
$settings['upload_file_text']  =<<<UPLOAD_FILE_TEXT
{{Information
|Description    ={{de|Beschreibung folgt}} 
|Source         ={{own}}
|Author         = ~~~~
|Date           = 2014-
|Permission     =
|Other_versions = 
}}
== {{int:license-header}} ==
{{self|cc-by-sa-3.0}}
[[Category:Files by user ${settings['user']}]]

UPLOAD_FILE_TEXT;
?>
DEFAULT_CONFIG_NOWDOC;
 
 
 
try 
{
  global $settings;
  echo "########### call function check_uploadfile() - Uploadfile pruefen\n"; 
  check_uploadfile(); 
  echo "########### call function read_configfile() - Konfig-Datei(en) einlesen\n";
  read_configfile();
  echo "########### call function login() - Token holen\n";
  $token     = login($settings['user'], $settings['pass']);
  echo "########### call funktion login() - jetzt wird eingelogt\n";
  $null      = login($settings['user'], $settings['pass'], $token);
  echo "########### call function get_edittoken() - Token zum Editiren holen\n";
  $edittoken = get_edittoken();
  echo "value of the token: $edittoken \n"; 
  echo "########### call function upload() - raus die Maus\n";
  $null      = upload($edittoken);
  echo "########### call function logout() \n";
  $null      = logout();
} 
catch (Exception $e) 
{
  fwrite(STDERR, "MyErr -  FAILED: " . $e->getMessage() . "\n");
  if (@file_exists($settings['curl_verbose_file'])) 
  {
    fwrite(STDERR, "see also ${settings['curl_verbose_file']} \n");
  };
  die();
}
 
// Sterben ist bei CLI immer gut 
// (damit eventueller Müll hinter dem schliessenden PHP-Tag nicht ausgegeben wird). 
die();
?>
Dieser "Müll" wird nicht ausgegeben ...
... nun sind wir tot und haben nicht einmal Tschüss gesagt ...

Warum dieses Script???

Bearbeiten

Bevor ich dieses Script geschrieben habe, habe ich natürlich geguckt was es schon gibt:

Als erstes fand ich ein Script unter c:Commons:File upload service/Script. Das script ist von Benutzer:Eloquence (dem allseits bekannten Erik Möller) und leider als deprecated markiert. Weil das Script einfach zu installieren war, hab ich es trotzdem mal laufen lassen. Lief prima! Meldet ständig das meine Dateien erfolgreich hochgeladen seien ... nur gefunden habe ich hochgeladenen Dateien nie.

Das zweite Script das ich gefunden habe war c:User:Nichalp/Upload script. Als ich gelesen habe, dass zum Betrieb des Ganzen erst CSV Dateien (die zuvor mit irgendeinen ein CVS-creator-script erzeugt werden) bearbeitet werden müssen (OpenOffice.org Spreadsheet or Microsoft Office Word werden vorgeschlagen) haben sich mir die Krallen der Hinterläufe aufgerollt. Natürlich kann man auch CSV anderes bearbeiten ... man kann auch mit Katzen spielen ... das tut ja schon weh ...

Vielleicht gibts schon andere Upload-Scripte die gut sind. Habe aber nach zwei Reinfällen nicht danach gesucht. Wer was weiß kann sich gerne bei mir melden (oder es auch direkt hier eintragen ... ich behalte mir im zweifel aber Löschung auf meiner Benutzerseite vor).

Mir persönlich reicht das obige Script für meine Zwecke (auch wenn es alles andere als elegant ist). Wer Kriik hat und das Script ernsthaft nutzen möchte, der kann sich gerne melden. Ich bin für Weiterentwicklung offen wenn es Bedarf gibt.