Extension:MirrorTools

From MediaWiki.org
Jump to: navigation, search
MediaWiki extensions manual - list
Crystal Clear action run.png
MirrorTools

Release status: experimental

Implementation API
Description This extension facilitates real-time mirroring of page histories.
Author(s) Tisanetalk
Last version 1.0.0
MediaWiki 1.17+
PHP 5
Database changes yes
Schema registration yes
License GPL
Download
Check usage and version matrix

This is the MirrorTools extension and some bots that go with it.

Contents

What can this extension do? [edit]

This extension extends various API classes to allow revisions to be added with arbitrary user names (and, in a later version, timestamps). This aids in mirroring of another wiki's content.

Installation [edit]

Edit LocalSettings.php in the root of your MediaWiki installation, and add the following line near the bottom:

require_once("$IP/extensions/MirrorTools/MirrorTools.php");

Then run update.php to install the tables.

Usage [edit]

See the api.php on your wiki for more details on parameters for the various MirrorTools API modules.

To get the bot to work:

Development notes [edit]

Presently, this only handles mirroring of account creations. Partially written (and therefore not yet available) is mirroring of edits (mirroredit). Also needed are:

  • moves (mirrormove)
  • deletions (mirrordelete, to be handled by placing a deletion marker in the history)
  • undeletions (mirrorundelete, to be handled similarly to page creation)
  • protect (mirrorprotect)
  • unprotect (mirrorunprotect)
  • etc.

It may happen that a page is created on the remote wiki with a page title that already exists on the local wiki. That's fine; just merge the histories. It may happen that on the remote wiki, a page is moved to a page title that already exists on the local wiki. In such a case, the two page histories would be switched. E.g., suppose foo exists on the remote wiki (and is mirrored locally), and bar exists on the local wiki but not on the remote wiki. Then on the remote wiki, foo is moved to bar. On the local wiki, bar is then moved to foo, and foo is then moved to bar, and a redirect is put on top of the page history of foo redirecting the user to bar.

It may happen that some revisions will need to be buried in the page history below what already exists. This will require changing around the rev_parent_id values and such.

After all these are done, the next step will be to lock down mirrored pages to prevent any changes from being made to them, except by the mirrorbot.

Once that is done, the next step will be to cause clicks on "edit," "move," etc. tabs to take one to the appropriate urls at the remote wiki.

Next development steps [edit]

It needs to be programmed to have a loop that pulls and pushes at appropriate intervals. What that interval might be, I'm not sure. Maybe x seconds? Or, preferably, it can detect how far behind it's gotten and calibrate itself accordingly, based on the average account creations, edits, etc. per x seconds.

Files [edit]

inclubot_passwords.php [edit]

<?
// MirrorBot API url
$wikiUrl = "http://localhost/w/api.php";
 
// MirrorPullBot bot login
$pullUser = 'YourWikipediaBotUsername';
$pullPass = 'yourwikipediabotpassword';
 
// MirrorPushBot bot login
$pushUser = 'YourLocalBotUsername';
$pushPass = 'yourlocalbotpassword';
 
// Credentials for direct access to bot's database
$host = "localhost";
$hostUser = "root";
$hostPass = "hostpassword";
$hostDbName = "hostdbname";

mirrorpullbot.php [edit]

<?php
 
// Two options: -q rc, -q rev
$options = getopt( 'q:');
if ( !isset ( $options['q'] ) ) {
      die ( 'Usage: php mirrorpullbot.php -q<option (e.g. rc, rev)>' . "\n" );
}
$allowableOptions = array(
      'rc',
      'rev',
      'us'
);
if ( !in_array ( $options[ 'q' ], $allowableOptions ) ) {
      die ( "Allowable options: rc, rev, us\n" );
}
 
// Get the passwords
$passwordFile = 'inclubot_passwords.php';
if ( !file_exists ( $passwordFile ) ) {
      die ( "File $passwordFile does not exist\n" );
}
 
$con = new mysqli( $host, $hostUser, $hostPass, $hostDbName );
if (!$con) {
      die('Could not connect: ' . mysql_error());
}
 
/* Setup my classes. */
include('botclasses.php');
 
include('inclubot_passwords.php');
$wiki      = new wikipedia;
$wiki->url = "http://en.wikipedia.org/w/api.php";
#$wiki->url = "http://meta.wikimedia.org/w/api.php";

$wiki->login($user,$pass);
unset($pass);
 
switch ( $options[ 'q' ] ) {
      case 'rc':
            // Patrolled flag removed because of a lack of patrol right without being autoconfirmed :(
            $ret = $wiki->query ('?action=query&list=recentchanges&rcstart=2012-11-09T21:47:37Z'
            . '&rcdir=newer&rcprop=user|userid|comment|timestamp|title|ids|sizes|redirect|loginfo'
            . '|flags|loginfo|tags&rclimit=500&format=php' , true);
            $events = $ret['query']['recentchanges'];
            $table = 'mb_rc_queue';
            $fields = array (
                  'mbrcq_rc_id' => 'rcid',
                  'mbrcq_anon' => 'anon',
                  'mbrcq_rc_bot' => 'bot',
                  'mbrcq_rc_comment' => 'comment',
                  'mbrcq_rc_log_action' => 'logaction',
                  'mbrcq_rc_logid' => 'logid',
                  'mbrcq_rc_logtype' => 'logtype',
                  'mbrcq_rc_minor' => 'minor',
                  'mbrcq_rc_new' => 'new',
                  'mbrcq_rc_new_len' => 'newlen',
                  'mbrcq_rc_namespace' => 'ns',
                  'mbrcq_rc_old_len' => 'oldlen',
                  'mbrcq_rc_cur_id' => 'pageid',
                  'mbrcq_rc_patrolled' => 'patrolled',
                  'mbrcq_rc_thisoldid' => 'revid',
                  'mbrcq_rc_lastoldidid' => 'revoldid',
                  'mbrcq_redirect' => 'redirect',
                  'mbrcq_rc_timestamp' => 'timestamp',
                  'mbrcq_rc_title' => 'title',
                  'mbrcq_rc_type' => 'type',
                  'mbrcq_rc_user' => 'userid',
                  'mbrcq_rc_user_text' => 'user',
                  'mbrcq_user' => 'addeduserid', // This isn't actually in the API result
                  'mbrcq_user_text' => 'addeduser' // This isn't actually in the API result
            );
            $stringFields = array (
                  'title',
                  'type',
                  'action',
                  'user',
                  'comment',
                  'tags',
                  'logaction',
                  'logtype',
                  'addeduser',
            );
            $booleanFields = array (
                  'anon',
                  'bot',
                  'minor',
                  'new',
                  'patrolled',
                  'redirect',
            );
            $defaultFields = array (
                  'rcid' => 0,
                  'anon' => 0,
                  'bot' => 0,
                  'comment' => "''",
                  'logaction' => 'NULL',
                  'logid' => 0,
                  'logtype' => "''",
                  'minor' => 0,
                  'new' => 0,
                  'newlen' => 0,
                  'ns' => 0,
                  'oldlen' => 0,
                  'pageid' => 0,
                  'patrolled' => 0,
                  'revid' => 0,
                  'revoldid' => 0,
                  'redirect' => 0,
                  'timestamp' => "''",
                  'title' => "''",
                  'type' => 'NULL',
                  'user' => "''",
                  'userid' => 0,
                  'addeduser' => "''",
                  'addeduserid' => 0,
            );
            break;
      case 'us':
            $table = 'mb_rc_queue';
            $where = "mbrcq_rc_log_action='create2' AND mbrcq_user=0";
            $ret = $con->query( "SELECT * FROM mb_rc_queue "
                  ."WHERE $where LIMIT 500" );
            if ( !$ret ) {
                  die ( "No $where items\n" );
            }
            $userTitle = array();
            while ( $value = $ret->fetch_assoc() ) {
                  $userTitle[] = $value[ 'mbrcq_rc_title' ];
            }
            $firstUserTitle = true;
            $queryChunk = '';
            foreach ( $userTitle as $thisUserTitle ) {
                  if ( !$firstUserTitle ) {
                        $queryChunk .= '|';
                  }
                  $firstUserTitle = false;
                  $queryChunk .= $thisUserTitle;
            }
            $ret = $wiki->query ('?action=query&list=users&ususers=' . $queryChunk
                  . '&format=php' , true);
            if ( !$ret ) {
                  echo "Error: Did not retrieve any user IDs from query\n";
            }
            $events = $ret['query']['users'];
            foreach ( $events as $thisEvent ) {
                  $name = "'" . $con->real_escape_string( $thisEvent[ 'name' ] ) . "'";
                  $pageTitle = "'User:" . $con->real_escape_string( $thisEvent[ 'name' ] ) . "'";
                  $query = 'UPDATE mb_rc_queue SET '
                        . 'mbrcq_user=' . $thisEvent[ 'userid']
                        . ', mbrcq_user_text=' . $name
                        . " WHERE mbrcq_rc_log_action='create2' AND mbrcq_rc_title="
                        . $pageTitle;
                  echo $query . "\n";
                  $status = $con->query ( $query );
                  if ( $status ) {
                        echo "Success\n";
                  } else {
                        echo "Failure\n";
                  }
            }
            die();
            break;
}
 
$dbFields = array_keys ( $fields );
$userRow = array_values ( $fields );
$undesirables = array ( '-', ':', 'T', 'Z' );
$row = 'insert into ' . $table . ' ( ' . implode ( ', ', $dbFields ) . ' ) values ';
$isFirstInEvent = true;
// For each user creation event in that result set
foreach ( $events as $thisLogevent ) {
      // Default values for adduserid and adduser
      if ( isset ( $thisLogevent[ 'userid' ] ) ) {
            $thisLogevent[ 'addeduserid' ] = $thisLogevent[ 'userid' ];
      }
      if ( isset ( $thisLogevent[ 'user' ] ) ) {
            $thisLogevent[ 'addeduser' ] = $thisLogevent[ 'user' ];
      }
      // Make those different if it's a create2
      if ( isset ( $thisLogevent[ 'logaction' ] ) ) {
            if ( $thisLogevent[ 'logaction' ] == 'create2' ) {
                  $thisLogevent [ 'addeduserid' ] = 0;
                  $title = $thisLogevent[ 'title' ];
                  $strposTitle = strpos ( $title, ':' );
                  $thisLogevent [ 'addeduser' ] = substr ( $title, $strposTitle + 1
                        , strlen ( $title ) - $strposTitle );
            }
      }
      if ( !$isFirstInEvent ) {
            $row .= ', ';
      }
      $isFirstInEvent = false;
      $row .= '( ';
      $isFirstInItem = true;
      // Get rid of dashes, colons, Ts and Zs in timestamp
      $thisLogevent['timestamp'] = str_replace ( $undesirables, '', $thisLogevent['timestamp'] );
      // Iterate over those database fields
      foreach ( $userRow as $thisRowItem ) {
            if ( !$isFirstInItem ) {
                  $row .= ', ';
            }
            $isFirstInItem = false;
            // If it's a boolean field, 1 if it's there, 0 if not
            if ( in_array( $thisRowItem, $booleanFields ) ) {
                  if ( isset ( $thisLogevent[ $thisRowItem ] ) ) {
                        $row .= '1';
                  } else {
                        $row .= '0';
                  }
            } else {
                  if ( isset ( $thisLogevent[$thisRowItem] ) ) {
                        // If it's an array (e.g. tag array), implode it
                        if ( is_array ( $thisLogevent[$thisRowItem] ) ) {
                              $thisLogevent[$thisRowItem] = implode ( $thisLogevent[$thisRowItem] );
                        }
                        // If it's a string field, escape it
                        if ( in_array ( $thisRowItem, $stringFields ) ) {
                              $thisLogevent[$thisRowItem] = "'" . $con->real_escape_string
                                    ( $thisLogevent[$thisRowItem] ) . "'";
                        }
                        $row .= $thisLogevent[$thisRowItem];
                  } else {
                        $row .= $defaultFields[$thisRowItem];
                  }
            }
      }
      $row .= ')';
}
$row .= ';';
echo $row . "\n";
 
$con->query ( $row );
if ( $con ) {
      echo "Success\n";
} else {
      echo "Failure\n";
}
$con->close();

mirrorpushbot.php [edit]

<?php 
 
/* Setup my classes. */
include('botclasses.php');
$wiki      = new wikipedia;
$wiki->url = $wikiUrl;
 
$dbread = new mysqli( $host, $hostUser, $hostPass, $hostDbName );
if ( !$dbread ) {
      die( 'Could not connect: ' . mysql_error());
}
$ret = $dbread->query( "SELECT * FROM mb_rc_queue WHERE mbrcq_rc_logtype='newusers'" );
if ( !$ret ) {
      die ( "No newusers rows found\n" );
}
$row = $ret->fetch_assoc(); // Just do one row for testing purposes
$row = $ret->fetch_assoc(); // Just do one row for testing purposes
$row = $ret->fetch_assoc(); // Just do one row for testing purposes
var_dump ( $row );
 
$wiki->login($pushUser,$pushPass);
$token = $wiki->getedittoken ();
unset($pass);
$fields = array(
      'rcid' => 'mbrcq_rc_id',
      'logid' => 'mbrcq_rc_logid',
      'pageid' => 'mbrcq_rc_cur_id',
      'pagenamespace' => 'mbrcq_rc_namespace',
      'pagetitle' => 'mbrcq_rc_title',
      'type' => 'mbrcq_rc_logtype',
      'logaction' => 'mbrcq_rc_log_action',
      'user' => 'mbrcq_rc_user_text',
      'userid' => 'mbrcq_rc_user',
      'timestamp' => 'mbrcq_rc_timestamp',
      'comment' => 'mbrcq_rc_comment',
      'tags' => 'mbrcq_tags'
);
$query = '?action=createaccount&format=php';
foreach ( $fields as $key => $value ) {
      if ( isset ( $row[ $value ] ) ) {
            $query .= '&' . $key . '=' . $row[ $value ];
      }
}
$ret = $wiki->query ( '?action=createaccount&format=php'
      . '&rcid=' . $row['mbrcq_rc_id']
      . '&logid=' . $row['mbrcq_rc_logid']
      . '&pageid=' . $row['mbrcq_rc_cur_id']
      . '&pagenamespace=' . $row['mbrcq_rc_namespace']
      . '&pagetitle=' . $row['mbrcq_rc_title']
      . '&type=' . $row['mbrcq_rc_logtype']
      . '&logaction=' . $row['mbrcq_rc_log_action']
      . '&user=' . $row['mbrcq_rc_user_text']
      . '&userid=' . $row['mbrcq_rc_user']
      . '&timestamp=' . $row['mbrcq_rc_timestamp']
      . '&comment=' . $row['mbrcq_rc_comment']
      . '&tags=' . $row['mbrcq_tags']
      . '&token=' . $token
      , true ); // Post
var_dump ( $ret );

mirrorpushbot.php [edit]

<?php 
 
/* Setup my classes. */
include('botclasses.php');
$wiki      = new wikipedia;
$wiki->url = $wikiUrl;
 
$dbread = new mysqli( $host, $hostUser, $hostPass, $hostDbName );
if ( !$dbread ) {
      die( 'Could not connect: ' . mysql_error());
}
$ret = $dbread->query( "SELECT * FROM mb_rc_queue WHERE mbrcq_rc_logtype='newusers'" );
if ( !$ret ) {
      die ( "No newusers rows found\n" );
}
$row = $ret->fetch_assoc(); // Just do one row for testing purposes
$row = $ret->fetch_assoc(); // Just do one row for testing purposes
$row = $ret->fetch_assoc(); // Just do one row for testing purposes
var_dump ( $row );
 
$wiki->login($pushUser,$pushPass);
$token = $wiki->getedittoken ();
unset($pass);
$fields = array(
      'rcid' => 'mbrcq_rc_id',
      'logid' => 'mbrcq_rc_logid',
      'pageid' => 'mbrcq_rc_cur_id',
      'pagenamespace' => 'mbrcq_rc_namespace',
      'pagetitle' => 'mbrcq_rc_title',
      'type' => 'mbrcq_rc_logtype',
      'logaction' => 'mbrcq_rc_log_action',
      'user' => 'mbrcq_rc_user_text',
      'userid' => 'mbrcq_rc_user',
      'timestamp' => 'mbrcq_rc_timestamp',
      'comment' => 'mbrcq_rc_comment',
      'tags' => 'mbrcq_tags'
);
$query = '?action=createaccount&format=php';
foreach ( $fields as $key => $value ) {
      if ( isset ( $row[ $value ] ) ) {
            $query .= '&' . $key . '=' . $row[ $value ];
      }
}
$ret = $wiki->query ( '?action=createaccount&format=php'
      . '&rcid=' . $row['mbrcq_rc_id']
      . '&logid=' . $row['mbrcq_rc_logid']
      . '&pageid=' . $row['mbrcq_rc_cur_id']
      . '&pagenamespace=' . $row['mbrcq_rc_namespace']
      . '&pagetitle=' . $row['mbrcq_rc_title']
      . '&type=' . $row['mbrcq_rc_logtype']
      . '&logaction=' . $row['mbrcq_rc_log_action']
      . '&user=' . $row['mbrcq_rc_user_text']
      . '&userid=' . $row['mbrcq_rc_user']
      . '&timestamp=' . $row['mbrcq_rc_timestamp']
      . '&comment=' . $row['mbrcq_rc_comment']
      . '&tags=' . $row['mbrcq_tags']
      . '&token=' . $token
      , true ); // Post
var_dump ( $ret );

MirrorTools.php [edit]

<?php
/**
 * MirrorTools extension by Tisane
 * URL: http://www.mediawiki.org/wiki/Extension:MirrorTools
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * http://www.gnu.org/copyleft/gpl.html
 */
 
/* Alert the user that this is not a valid entry point to MediaWiki if they try to access the
special pages file directly.*/
 
if ( !defined( 'MEDIAWIKI' ) ) {
        echo <<<EOT
                To install the MirrorTools extension, put the following line in LocalSettings.php:
                require( "extensions/MirrorTools/MirrorTools.php" );
EOT;
        exit( 1 );
}
 
$wgExtensionCredits['other'][] = array(
        'path' => __FILE__,
        'name' => 'MirrorTools',
        'author' => 'Leucosticte',
        'url' => 'https://www.mediawiki.org/wiki/Extension:MirrorTools',
        'descriptionmsg' => 'mirrortools-desc',
        'version' => '1.1.1',
);
 
$dir = dirname( __FILE__ ) . '/';
#$wgAutoloadClasses['ApiMirrorEditPage'] = $dir . 'APIMirrorTools.php';
$wgAutoloadClasses['ApiCreateAccount'] = $dir . 'ApiCreateAccount.php';
$wgAutoloadClasses['MirrorEditPage'] = $dir . 'MirrorTools.classes.php';
#$wgAPIModules['mirroredit'] = 'ApiMirrorEditPage';
$wgAPIModules['createaccount'] = 'ApiCreateAccount';
 
// Path to internationalization file
$wgExtensionMessagesFiles['MirrorTools'] = $dir . 'MirrorTools.i18n.php';
 
// New user rights
$wgAvailableRights[] = 'mirroredit';
$wgGroupPermissions['MirrorTools']['mirroredit'] = true;
$wgHooks['LoadExtensionSchemaUpdates'][] = 'MirrorToolsHooks::MirrorToolsCreateTable';
 
class MirrorToolsHooks {
        public static function MirrorToolsCreateTable( $updater ) {
                $updater->addExtensionUpdate( array( 'addTable', 'mirror_logging',
                        dirname( __FILE__ ) . '/mirror-logging.sql', true ) );
                $updater->addExtensionUpdate( array( 'addTable', 'mirror_page',
                        dirname( __FILE__ ) . '/mirror-page.sql', true ) );
                $updater->addExtensionUpdate( array( 'addTable', 'mirror_revision',
                        dirname( __FILE__ ) . '/mirror-revision.sql', true ) );
                $updater->addExtensionUpdate( array( 'addTable', 'mirror_user',
                        dirname( __FILE__ ) . '/mirror-user.sql', true ) );
                return true;
        }
}

ApiCreateAccount.php [edit]

<?php
/**
 * Created on August 7, 2012
 *
 * Copyright © 2012 Tyler Romeo <tylerromeo@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * http://www.gnu.org/copyleft/gpl.html
 *
 * @file
 */
 
/**
 * Unit to authenticate account registration attempts to the current wiki.
 *
 * @ingroup API
 */
class ApiCreateAccount extends ApiBase {
        public function execute() {
                $params = $this->extractRequestParams();
                $isUserAlreadyThere = User::idFromName ( $params['user'] );
                if ( $isUserAlreadyThere ) {
                        $this->dieUsageMsg( 'userexists' );
                }
                $dbw = wfGetDB( DB_MASTER );
                #$nextUserId = $dbw->nextSequenceValue( 'user_user_id_seq' );
                $userRow = array (
                        'user_name' => $params['user'],
                        'user_password' => User::crypt ( str_shuffle (
                                'abcdefghijklmnopqrstuvwxyz' ) ),
                );
                $dbw->insert ( 'user', $userRow );
                $nextUserId = $dbw->insertId();
                $addedUser =
                $logRow = array (
                        'log_type' => 'newusers',
                        'log_action' => $params['logaction'],
                        'log_timestamp' => $params['timestamp'],
                        'log_user' => $nextUserId,
                        'log_user_text' => $params['user'],
                        'log_namespace' => $params['pagenamespace'],
                        'log_title' => $params['pagetitle'],
                        'log_page' => $params['pageid'],
                        'log_comment' => $params['comment'],
                );
                $dbw->insert ( 'logging', $logRow );
                $nextLogId = $dbw->insertId();
                $mirrorUserRow = array (
                        'mrus_user_id' => $nextUserId,
                        'mrus_mr_user_id' => $params['userid'],
                );
                $dbw->insert ( 'mirror_user', $mirrorUrc_serRow );
                $mirrorLogRow = array (
                        'mrlg_log_id' => $nextLogId,
                        'mrlg_mr_log_id' => $params['logid'],
                );
                $dbw->insert ( 'mirror_logging', $mirrorLogRow );
                $rcRow = array (
                        'rc_timestamp' => $params['timestamp'],
                        'rc_user' => $nextUserId,
                        'rc_user_text' => $params['user'],
                        'rc_namespace' => $params['pagenamespace'],
                        'rc_title' => $params['pagetitle'],
                        'rc_comment' => $params['comment'],
                        'rc_minor' => 0,
                        'rc_bot' => 0,
                        'rc_new' => 0,
                        'rc_cur_id' => 0,
                        'rc_this_oldid' => 0,
                        'rc_last_oldid' => 0,
                        'rc_type' => 3,
                        'rc_moved_to_ns' => 0,
                        'rc_moved_to_title' => '',
                        'rc_patrolled' => 1,
                        'rc_ip' => '', // The local wiki will not have access to this info
                        'rc_deleted' => 0,
                        'rc_logid' => $nextLogId,
                        'rc_log_type' => 'newusers',
                        'rc_log_action' => 'create',
                        'rc_params' => 2
                );
                $dbw->insert( 'recentchanges' )->$rwRow;
                $nextRcId = $dbw->insertId();
                $mirrorRcRow = array (
                        'mrrc_rc_id' => $nextRcId,
                        'mrrc_mr_rc_id' => $params['rc_id'],
                        'mrrc_mr_cur_id' => 0,
                        'mrrc_mr_user' => $nextUser,
                        'mrrc_mr_user_text' => $params['userid'],
                        'mrrc_timestamp' => wfTimestampNow()
                );
                $result['result'] = 'success';
        }
 
        public function getDescription() {
                return 'Create a new user account.';
        }
 
        public function needsToken() {
                return true;
        }
 
        public function mustBePosted() {
                return true;
        }
 
        public function isReadMode() {
                return false;
        }
 
        public function isWriteMode() {
                return true;
        }
 
        public function getAllowedParams() {
                global $wgEmailConfirmToEdit;
                return array(
                        'logid' => array (
                                ApiBase::PARAM_TYPE => 'integer',
                                ApiBase::PARAM_REQUIRED => true
                        ),
                        'pageid' => array (
                                ApiBase::PARAM_TYPE => 'integer',
                                ApiBase::PARAM_REQUIRED => true
                        ),
                        'pagenamespace' => array (
                                ApiBase::PARAM_TYPE => 'integer',
                                ApiBase::PARAM_REQUIRED => true
                        ),
                        'pagetitle' => array (
                                ApiBase::PARAM_TYPE => 'string',
                                ApiBase::PARAM_REQUIRED => true
                        ),
                        'type' => array (
                                ApiBase::PARAM_TYPE => 'string',
                                ApiBase::PARAM_REQUIRED => true
                        ),
                        'logaction' => array (
                                ApiBase::PARAM_TYPE => 'string',
                                ApiBase::PARAM_REQUIRED => true
                        ),
                        'user' => array (
                                ApiBase::PARAM_TYPE => 'string',
                                ApiBase::PARAM_REQUIRED => true
                        ),
                        'userid' => array (
                                ApiBase::PARAM_TYPE => 'integer',
                                ApiBase::PARAM_REQUIRED => true
                        ),
                        'timestamp' => array (
                                ApiBase::PARAM_TYPE => 'timestamp',
                                ApiBase::PARAM_REQUIRED => true
                        ),
                        'comment' => array (
                                ApiBase::PARAM_TYPE => 'string',
                        ),
                        'tags' => array (
                                ApiBase::PARAM_TYPE => 'string',
                        ),
                        'rcid' => array (
                                ApiBase::PARAM_TYPE => 'integer',
                                ApiBase::PARAM_REQUIRED => true
                        ),
                        'token' => array (
                                ApiBase::PARAM_TYPE => 'string',
                                ApiBase::PARAM_REQUIRED => true
                        )
                );
        }
 
        public function getParamDescription() {
                $p = $this->getModulePrefix();
                return array(
                        'logid' => null,
                        'pageid' => null,
                        'pagenamespace' => null,
                        'pagetitle' => null,
                        'type' => null,
                        'logaction' => null,
                        'user' => null,
                        'userid' => null,
                        'timestamp' => null,
                        'comment' => null,
                        'tags' => null,
                        'rcid' => null,
                        'token' => null,
                );
        }
 
        public function getResultProperties() {
                return array(
                        'createaccount' => array(
                                'result' => array(
                                        ApiBase::PROP_TYPE => array(
                                                'success',
                                                'warning',
                                                'needtoken'
                                        )
                                ),
                                'username' => array(
                                        ApiBase::PROP_TYPE => 'string',
                                        ApiBase::PROP_NULLABLE => true
                                ),
                                'userid' => array(
                                        ApiBase::PROP_TYPE => 'int',
                                        ApiBase::PROP_NULLABLE => true
                                ),
                                'token' => array(
                                        ApiBase::PROP_TYPE => 'string',
                                        ApiBase::PROP_NULLABLE => true
                                ),
                        )
                );
        }
 
        public function getPossibleErrors() {
                $localErrors = array(
                        'wrongpassword',
                        'sessionfailure',
                        'sorbs_create_account_reason',
                        'noname',
                        'userexists',
                        'password-name-match',
                        'password-login-forbidden',
                        'noemailtitle',
                        'invalidemailaddress',
                        'externaldberror'
                );
 
                $errors = parent::getPossibleErrors();
                // All local errors are from LoginForm, which means they're actually message keys.
                foreach( $localErrors as $error ) {
                        $errors[] = array( 'code' => $error, 'info' => wfMessage( $error )->parse() );
                }
 
                // 'passwordtooshort' has parameters. :(
                global $wgMinimalPasswordLength;
                $errors[] = array(
                        'code' => 'passwordtooshort',
                        'info' => wfMessage( 'passwordtooshort', $wgMinimalPasswordLength )->parse()
                );
                return $errors;
        }
 
        public function getExamples() {
                return array(
                        'api.php?action=createaccount&name=testuser&password=test123',
                        'api.php?action=createaccount&name=testmailuser&mailpassword=true&reason=MyReason',
                );
        }
 
        public function getHelpUrls() {
                return 'https://www.mediawiki.org/wiki/API:Account creation';
        }
 
        public function getVersion() {
                return __CLASS__ . ': $Id$';
        }
}

mirror-logging.sql [edit]

-- This table keeps track of which log events are mirrored and what local log IDs correspond
-- to what remote log IDs.
--
CREATE TABLE mirror_logging (
    -- Unique ID to identify each mirrored log event
    mrlg_id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
    -- Local logging.log_id
    mrlg_log_id INT UNSIGNED NOT NULL,
    -- Remote logging.log_id
    mrlg_mr_log_id INT UNSIGNED NOT NULL,
    -- Time at which the mirroring took place
    mrlg_timestamp BINARY(14) NOT NULL DEFAULT ''
);
 
CREATE INDEX mrlg_id ON mirror_logging (mrlg_id);
 
COMMIT;

mirror-page.sql [edit]

-- This table keeps track of which pages are mirrored and what local page IDs correspond
-- to what remote page IDs.
--
CREATE TABLE mirror_page (
    -- Unique ID to identify each mirrored page
    mrpg_id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
    -- Local page.page_id
    mrpg_page_id INT UNSIGNED NOT NULL,
    -- Remote page.page_id
    mrpg_mr_page_id INT UNSIGNED NOT NULL
);
 
CREATE INDEX mrpg_id ON mirror_page (mrpg_id);
 
COMMIT;

mirror-recentchanges.sql [edit]

-- This table lists data pertaining to mirrored recent changes.
--
CREATE TABLE mirror_recentchanges (
    -- Unique ID to identify each mirrored recentchange
    mrrc_id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
    -- Recentchanges ID (recentchanges.rc_id on this wiki)
    mrrc_rc_id INT UNSIGNED NOT NULL,
    -- Mirrored recentchanges ID (recentchanges.rc_id on mirrored wiki)
    mrrc_mr_rc_id INT UNSIGNED NOT NULL,
    -- Mirrored page ID (recentchanges.rc_cur_id on mirrored wiki)
    mrrc_mr_cur_id INT UNSIGNED NOT NULL,
    -- Mirrored user ID (recentchanges.rc_user on mirrored wiki)
    mrrc_mr_user INT UNSIGNED NOT NULL DEFAULT 0,
    -- Mirrored user name (revision.rev_user_text on mirrored wiki)
    mrrc_mr_rev_user_text VARCHAR(255) BINARY NOT NULL DEFAULT '',
    -- Time at which the mirroring took place
    mrrc_timestamp BINARY(14) NOT NULL DEFAULT ''
) MAX_ROWS=10000000 AVG_ROW_LENGTH=1024;
 
CREATE INDEX mrrc_id ON mirror_recentchanges (mrrc_id);
 
COMMIT;

mirror-revision.sql [edit]

-- This table lists data pertaining to mirrored content.
--
CREATE TABLE mirror_revision (
    -- Unique ID to identify each mirrored revision
    mrrv_id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
    -- Revision ID (revision.rev_id on this wiki)
    mrrv_rev_id INT UNSIGNED NOT NULL,
    -- Mirrored revision ID (revision.rev_id on mirrored wiki)
    mrrv_mr_rev_id INT UNSIGNED NOT NULL,
    -- Mirrored page ID (revision.rev_page on mirrored wiki)
    mrrv_mr_rev_page INT UNSIGNED NOT NULL,
    -- Mirrored user ID (revision.rev_user on mirrored wiki)
    mrrv_mr_rev_user INT UNSIGNED NOT NULL DEFAULT 0,
    -- Mirrored user name (revision.rev_user_text on mirrored wiki)
    mrrv_mr_rev_user_text VARCHAR(255) BINARY NOT NULL DEFAULT '',
    -- Time at which the mirroring took place
    mrrv_timestamp BINARY(14) NOT NULL DEFAULT '',
    -- Mirrored parent ID (revision.rev_parent_id on mirrored wiki)
    mrrv_mr_rev_parent_id INT UNSIGNED NOT NULL
) MAX_ROWS=10000000 AVG_ROW_LENGTH=1024;
 
CREATE INDEX mrrv_id ON mirror_revision (mrrv_id);
 
COMMIT;

mirror-user.sql [edit]

-- This table keeps track of which users are mirrored and what local user IDs correspond
-- to what remote user IDs.
--
CREATE TABLE mirror_user (
    -- Unique ID to identify each mirrored user
    mrus_id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
    -- Local user.user_id
    mrus_user_id INT UNSIGNED NOT NULL,
    -- Remote user.user_id
    mrus_mr_user_id INT UNSIGNED NOT NULL
);
 
CREATE INDEX mrus_id ON mirror_user (mrus_id);
 
COMMIT;

See also [edit]