Jump to content

Extension:Rsync: Difference between revisions

From mediawiki.org
Content deleted Content added
changed type to interface, added hooks used to infobox
m Undid edits by 2601:846:580:5ED0:0:0:0:EEB3 (talk) to last version by Baris6161TURK
 
(7 intermediate revisions by 7 users not shown)
Line 1: Line 1:
{{Archived extension|414911}}
<?php
/*<!--<wikitext>-->
{{Extension
|name = rsync
|status = experimental
|type = interface
|hook1 = ArticleSaveComplete
|hook2 = ArticleDelete
|hook3 = ArticleDeleteComplete
|hook4 = SpecialMovepageAfterMove
|hook5 = FileUpload
|hook6 = AddNewAccount
|hook7 = ArticleProtectComplete
|hook8 = RecentChange_save
|author = [[user:jldupont|Jean-Lou Dupont]]
|image =
|version = See SVN ($Id: rsync.php 678 2007-08-17 15:32:06Z jeanlou.dupont $)
|update =
|mediawiki = tested on 1.10 but probably works with a earlier versions
|download = [http://bizzwiki.googlecode.com/svn/trunk/BizzWiki/extensions/rsync/ SVN]
|readme =
|changelog =
|description =
|parameters =
|rights =
|example =
}}
== WARNING ==
This extension is work in progress.

<!--@@
== File Status ==
This section is only valid when viewing the page in a BizzWiki environment.
<code>(($#extractmtime|@@mtime@@$)) (($#extractfile|@@file@@$))</code>

Status: (($#comparemtime|<b>File system copy is newer - [{{fullurl:{{NAMESPACE}}:{{PAGENAME}}|action=reload}} Reload] </b>|Up to date$))
@@-->
== Purpose==
Provides a file based export of all the page changes. The directory containing the files can be used
along with 'rsync' to provide backup & restore functionality.

== Features ==
* Page
** Creation (done)
** Update (done)
** Delete (done)
** Move (done)
** Protection (done)
* File
** Upload
** Re-upload
** Delete
** Move (not allowed - hence, nothing to implement)
* User
** Account creation
** Account options update

== Theory Of Operation ==
Page change events are trapped and the resulting new/updated pages are written to a
specified filesystem directory. Trapping is done through the 'RecentChange_save' hook.

=== New page ===
File generated: <code>RC-id-new.xml</code>

=== Edit page ===
File generated: <code>RC-id-edit.xml</code>

=== Delete page ===
File generated: <code>RC-id-delete.xml</code>

=== Move page ===
File generated: <code>RC-id-move.xml</code>

=== Filename ===
Format: <code> RC-id-type.xml</code>
* id: unique identifier generated in the context of the 'RecentChanges' table
* type: 'new', 'edit' or 'log'
** new --> new page
** edit --> edit on a page
** delete --> page deletion
** log --> log entry

== Implementation ==
=== New Page ===
* Do complete export

=== Edit Page ===
* Do complete export

=== Move Page ===
A move transaction is the page with the new title enclosed in an xml file WITH
a new section 'source_title'.
* Complete export with 'source_title' section

=== Delete Page ===
* Export but don't 'writeRevision'

=== Page Restrictions ===
Needed to superclass 'WikiExporter' and 'XmlDumpWriter' classes.
* Export but don't 'writeRevision'
* Added 'restrictions' section to the XML dump

== Usage Notes ==
Make sure that the dump directory is writable.

== Dependancy ==
* [[Extension:StubManager|StubManager extension]]

== Installation ==
To install independantly from BizzWiki:
* Download [[Extension:StubManager]] extension
* Apply the following changes to 'LocalSettings.php'
<source lang=php>
require_once('/extensions/StubManager.php');
require('/extensions/rsync/rsync_stub.php');
</source>

== History ==

== See Also ==
This extension is part of the [[Extension:BizzWiki|BizzWiki Platform]].

== Code ==
<!--</wikitext>--><source lang=php>*/

$wgExtensionCredits[rsync::thisType][] = array(
'name' => rsync::thisName,
'version' => StubManager::getRevisionId('$Id: rsync.php 678 2007-08-17 15:32:06Z jeanlou.dupont $'),
'author' => 'Jean-Lou Dupont',
'description' => "Provides page changes in an export file format.",
);

class rsync
{
const thisType = 'other';
const thisName = 'rsync';
const defaultDir = '_backup';
var $directory;
var $found;

//
var $rc;
var $op; // current operation
var $executeDeferredInRcHook;
/**
*/
public function __construct()
{
$this->found = false;
$this->op = null;
// assume the default directory location
$this->directory = self::defaultDir;
// format the directory path.
global $IP;
$this->dir = $IP.'/'.$this->directory;
rsync_operation::$dir = $this->dir;
$this->executeDeferredInRcHook = false;
}
/**
Handles article creation & update
Creation and Update operations can not be discerned;
they are handled both as 'edit'.
*/
public function hArticleSaveComplete( &$article, &$user, &$text, &$summary, $minor,
$dontcare1, $dontcare2, &$flags )
{
if (!$this->found)
return true;
$this->op = new rsync_operation(rsync_operation::action_edit,
$article,
WikiExporter::CURRENT,
true, // include last revision text
$this->rc->mAttribs['rc_id'],
$this->rc->mAttribs['rc_timestamp']
);
rsync_operations::add( $this->op );
rsync_operations::execute();
return true;
}

/**
WARNING: If ArticleDelete hook fails, we might have some stranded resources
e.g. temporary file
*/
public function hArticleDelete( &$article, &$user, $reason )
{
$this->op = new rsync_operation(rsync_operation::action_delete,
$article,
WikiExporter::CURRENT,
false, // don't include last revision text
null, // we don't know the id just yet
null // nor the timestamp
);
rsync_operations::add( $this->op );
rsync_operations::execute();
return true;
}
/**
Handles article delete.
*/
public function hArticleDeleteComplete( &$article, &$user, $reason )
{
$this->op->setIdTs( $this->rc->mAttribs['rc_id'],
$this->rc->mAttribs['rc_timestamp'] );
rsync_operations::executeDeferred();
return true;
}
/**
Handles article move.
This hook is often called twice:
- Once for the page
- Once for the 'talk' page corresponding to 'page'
*/
public function hSpecialMovepageAfterMove( &$sp, &$oldTitle, &$newTitle )
{
if (!$this->found)
return true;
$this->op = new rsync_operation(rsync_operation::action_move,
$newTitle,
WikiExporter::CURRENT,
true, // include last revision text
$this->rc->mAttribs['rc_id'],
$this->rc->mAttribs['rc_timestamp']
);
rsync_operations::add( $this->op );
$this->op->setSourceTitle( $oldTitle );
rsync_operations::execute();

return true;
}
/**
File Upload
*/
public function hFileUpload( &$img )
{
$this->op = new rsync_operation(rsync_operation::action_createfile,
$article,
WikiExporter::CURRENT,
false, // do not include last revision text
null,
null
);
rsync_operations::add( $this->op );
rsync_operations::execute();
$this->executeDeferredInRcHook = true;
return true;
}
/**
*/
#public function hUploadComplete( &$img ) { return true; }
/**
TBD
*/
public function hAddNewAccount( &$user )
{
return true;
}
/**
Just send the 'page' details which contain the 'restrictions'
aka 'protection' information.
*/
public function hArticleProtectComplete( &$article, &$user, &$limit, &$reason )
{
$this->op = new rsync_operation(rsync_operation::action_protect,
$article,
WikiExporter::CURRENT,
false, // do not include last revision text
null,
null
);
rsync_operations::add( $this->op );
rsync_operations::execute();
$this->executeDeferredInRcHook = true;
return true;
}
// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
/**
Just grab the essential parameters we need to complete the transaction.
*/
public function hRecentChange_save( &$rc )
{
$this->rc = $rc;
$this->found = true;

if ($this->executeDeferredInRcHook)
{
$this->op->setIdTs( $this->rc->mAttribs['rc_id'],
$this->rc->mAttribs['rc_timestamp'] );
rsync_operations::executeDeferred();
}

return true;
}

} // end class

class rsync_operations
{
static $liste;
static $dListe;
static $id = null;
static $timestamp = null;
public static function add( &$op ) { self::$liste[] = $op; }
public static function addDeferred( &$op ) { self::$dListe[] = $op; }
public static function execute()
{
if (!empty( self::$liste ))
foreach( self::$liste as &$op )
{
$d = $op->getDeferralState();
$op->execute();
if ($d)
self::addDeferred( $op );
}
}

public static function executeDeferred()
{
if (!empty( self::$dListe ))
foreach( self::$dListe as &$op )
$op->executeDeferred();
}
} // end class

/** ************************************************************
Follows is a class that defines an 'rsync' export operation.
*/

class rsync_operation
{
//
static $dir;
// Constants
const action_none = 0;
const action_create = 1; // TBD
// page related
const action_edit = 2;
const action_delete = 3;
const action_move = 4;
const action_protect = 5;
// file related
const action_createfile = 6;
const action_deletefile = 7;
// Commit Operation parameters
var $includeRevision;
var $deferralRequired;
var $id;
var $timestamp;
var $action;
var $ns;
var $titre;

var $sourceTitle; // for move action

var $isFilenameTemp;
var $filename;
var $history; // current or full
var $text;
public function __construct( $action, &$object, $history, $includeRevision, $id, $ts )
{
if ( $object instanceof Article )
$title = $object->mTitle;
else
$title = $object;

$this->ns = $title->getNamespace();
$this->titre = $title->getText();

$this->action = $action;
$this->history = $history;
$this->includeRevision = $includeRevision;
$this->deferralRequired = $this->getDeferralState( );

$this->id = $id;
$this->timestamp = $ts;

// will get filled later.
$this->filename = null; // gets filled during 'updateList'
$this->isFilenameTemp = null;
$this->sourceTitle = null;
}
public function setIdTs( $id, $ts ) { $this->id = $id; $this->timestamp = $ts; }
public function setSourceTitle( &$t ) { $this->sourceTitle = $t; }
/**
Delete action requires deferral
*/
public function getDeferralState( )
{
if ($this->action == self::action_delete)
return true;

if ($this->action == self::action_protect)
return true;
return false;
}
/**
Page-rcid-action-namespace.xml
*/
protected function generateFilename( )
{
if ($this->id === null)
{
$this->isFilenameTemp = true;
$this->filename = tempnam( self::$dir, 'rsync_' );
return;
}
$this->filename = self::$dir."/Page-".$this->id.'-'.$this->action.'-'.$this->ns.'.xml';
}
public function execute()
{
$this->generateFilename();

/*
switch( $this->action )
{
case self::action_edit:
case self::action_delete:
}
*/
$this->export();
}
/**
Deferred execution consists in renaming the
temporary export file.
*/
public function executeDeferred()
{
$tempName = $this->filename;
// generate a permanent filename
$this->generateFilename();
self::rename( $tempName /*source*/, $this->filename /*target*/ );
}

protected function rename( &$source, &$target )
{
$r = rename( $source, $target );
if ($r === false)
throw new MWException();
}
/**
This function uses MediaWiki's 'WikiExporter' class.
*/
private function export( )
{
$dump = new DumpFileOutput( $this->filename );

$db = wfGetDB( DB_SLAVE );
$exporter = new WikiExporterEx( $db, $this->history );
// used for 'move' operation
if (!empty( $this->sourceTitle ))
$exporter->setSourceTitle( $this->sourceTitle );
$exporter->setOutputSink( $dump );
$exporter->includeRevision( $this->includeRevision );

$exporter->openStream();
$title = Title::makeTitle( $this->ns, $this->titre );
if( is_null( $title ) ) return;

$exporter->setPageTitle( $title );
$exporter->pageByTitle( $title );
$exporter->closeStream();
}
} // end class















/**
Class definition can be found in includes/Export.php
*/
class XmlDumpWriterEx extends XmlDumpWriter
{
var $pageTitle;
var $sourceTitle;
var $includeRevision;
function openPage( $row )
{
$out = parent::openPage( $row );

if ($this->pageTitle instanceof Title)
{
$this->pageTitle->loadRestrictions();
if (!empty( $this->pageTitle->mRestrictions ))
$out .= $this->getRestrictionsSection( $this->pageTitle->mRestrictions,
$this->pageTitle->mRestrictionsExpiry,
$this->pageTitle->mCascadeRestriction
);
}
if (is_a( $this->sourceTitle, 'Title'))
$out .= $this->getSourceTitleSection();
return $out;
}
function getRestrictionsSection( &$restrictions, $expiry, $cascading )
{
$result = "<restrictions>\n";
foreach( $restrictions as $restrictionType => &$levels )
foreach( $levels as $level)
$result .= " <restriction type='".$restrictionType."' level='".$level.
"' expiry='".$expiry."' cascading='".$cascading."' />\n";

$result .= "</restrictions>\n";
return $result;
}
function getSourceTitleSection()
{
return "<source_title>".Namespace::getCanonicalName( $this->sourceTitle->getNamespace() ).
":".$this->sourceTitle->getText()."</source_title>\n";
}
function writeRevision( &$row )
{
if (!$this->includeRevision)
return null;
return parent::writeRevision( $row );
}
} // end class

class WikiExporterEx extends WikiExporter
{
public function __construct( &$db, $history = WikiExporter::CURRENT,
$buffer = WikiExporter::BUFFER, $text = WikiExporter::TEXT )
{
parent::__construct( $db, $history, $buffer, $text );
$this->writer = new XmlDumpWriterEx();
$this->writer->includeRevision = true;
}
public function setPageTitle( &$title )
{
$this->writer->pageTitle = $title;
}
/**
The source title is used in 'move' operations.
*/
public function setSourceTitle( &$title )
{
$this->writer->sourceTitle = $title;
}
/**
It is helpful not to include the full revision text sometimes.
*/
public function includeRevision( &$enable )
{
$this->writer->includeRevision = $enable;
}
} // end class

//</source>

Latest revision as of 19:31, 25 November 2021