| Current File : /home/jvzmxxx/wiki/extensions/Flow/includes/Import/LiquidThreadsApi/Objects.php |
<?php
namespace Flow\Import\LiquidThreadsApi;
use AppendIterator;
use ArrayIterator;
use Flow\Import\IImportHeader;
use Flow\Import\IImportObject;
use Flow\Import\IImportPost;
use Flow\Import\IImportSummary;
use Flow\Import\IImportTopic;
use Flow\Import\ImportException;
use Flow\Import\IObjectRevision;
use Flow\Import\IRevisionableObject;
use Flow\Import\LiquidThreadsApi\ConversionStrategy;
use Iterator;
use MWTimestamp;
use Title;
use User;
abstract class PageRevisionedObject implements IRevisionableObject {
/** @var int **/
protected $pageId;
/**
* @var ImportSource
*/
protected $importSource;
/**
* @param ImportSource $source
* @param int $pageId ID of the remote page
*/
function __construct( $source, $pageId ) {
$this->importSource = $source;
$this->pageId = $pageId;
}
/**
* Gets the raw revisions, after filtering but before being converted to
* ImportRevision.
*
* @return Array Filtered revision array
*/
protected function getRevisionData() {
$pageData = $this->importSource->getPageData( $this->pageId );
// filter revisions without content (deleted)
foreach ( $pageData['revisions'] as $key => $value ) {
if ( isset( $value['texthidden'] ) ) {
unset( $pageData['revisions'][$key] );
}
}
// the iterators expect this to be a 0 indexed list
$pageData['revisions'] = array_values( $pageData['revisions'] );
return $pageData;
}
public function getRevisions() {
$pageData = $this->getRevisionData();
$scriptUser = $this->importSource->getScriptUser();
return new RevisionIterator( $pageData, $this, function( $data, $parent ) use ( $scriptUser ) {
return new ImportRevision( $data, $parent, $scriptUser );
} );
}
}
class ImportPost extends PageRevisionedObject implements IImportPost {
/**
* @var array
*/
protected $apiResponse;
/**
* @param ImportSource $source
* @param array $apiResponse
*/
public function __construct( ImportSource $source, array $apiResponse ) {
parent::__construct( $source, $apiResponse['rootid'] );
$this->apiResponse = $apiResponse;
}
/**
* @return string
*/
public function getAuthor() {
return $this->apiResponse['author']['name'];
}
/**
* Gets the username (or IP) from the signature.
*
* @return string|null Returns username, IP, or null if none could be detected
*/
public function getSignatureUser() {
$signatureText = $this->apiResponse['signature'];
return self::extractUserFromSignature( $signatureText );
}
/**
* Gets the username (or IP) from the provided signature.
*
* @return string|null Returns username, IP, or null if none could be detected
*/
public static function extractUserFromSignature( $signatureText ) {
$users = \EchoDiscussionParser::extractUsersFromLine( $signatureText );
if ( count( $users ) > 0 ) {
return $users[0];
} else {
return null;
}
}
/**
* @return string|false
*/
public function getCreatedTimestamp() {
return wfTimestamp( TS_MW, $this->apiResponse['created'] );
}
/**
* @return string|false
*/
public function getModifiedTimestamp() {
return wfTimestamp( TS_MW, $this->apiResponse['modified'] );
}
public function getTitle() {
$pageData = $this->importSource->getPageData( $this->apiResponse['rootid'] );
return Title::newFromText( $pageData['title'] );
}
/**
* @return Iterator<IImportPost>
*/
public function getReplies() {
return new ReplyIterator( $this );
}
/**
* @return array
*/
public function getApiResponse() {
return $this->apiResponse;
}
/**
* @return ImportSource
*/
public function getSource() {
return $this->importSource;
}
public function getRevisions() {
$authorUsername = $this->getAuthor();
$signatureUsername = $this->getSignatureUser();
if ( $signatureUsername !== null && $signatureUsername !== $authorUsername ) {
$originalRevisionData = $this->getRevisionData();
// This is not the same object as the last one in the original iterator,
// but it should be fungible.
$lastLqtRevision = new ImportRevision(
end( $originalRevisionData['revisions'] ),
$this,
$this->importSource->getScriptUser()
);
$signatureRevision = $this->createSignatureClarificationRevision( $lastLqtRevision, $authorUsername, $signatureUsername );
$originalRevisions = parent::getRevisions();
$iterator = new AppendIterator();
$iterator->append( $originalRevisions );
$iterator->append( new ArrayIterator( array( $signatureRevision ) ) );
return $iterator;
} else {
return parent::getRevisions();
}
}
/**
* Creates revision clarifying signature difference
*
* @param IObjectRevision $lastRevision Last revision prior to the clarification revision
* @param string $authorUsername Author username
* @param string $signatureUsername Username extracted from signature
* @return ScriptedImportRevision Generated top import revision
*/
protected function createSignatureClarificationRevision( IObjectRevision $lastRevision, $authorUsername, $signatureUsername ) {
$wikitextForLastRevision = $lastRevision->getText();
$newWikitext = $wikitextForLastRevision;
$templateName = wfMessage( 'flow-importer-lqt-different-author-signature-template' )->inContentLanguage()->plain();
$arguments = implode( '|', array(
"authorUser=$authorUsername",
"signatureUser=$signatureUsername",
) );
$newWikitext .= "\n\n{{{$templateName}|$arguments}}";
$clarificationRevision = new ScriptedImportRevision(
$this,
$this->importSource->getScriptUser(),
$newWikitext,
$lastRevision
);
return $clarificationRevision;
}
public function getObjectKey() {
return $this->importSource->getObjectKey( 'thread_id', $this->apiResponse['id'] );
}
}
/**
* This is a bit of a weird model, acting as a revision of itself.
*/
class ImportTopic extends ImportPost implements IImportTopic, IObjectRevision {
/**
* @return string
*/
public function getText() {
return $this->apiResponse['subject'];
}
public function getAuthor() {
return $this->apiResponse['author']['name'];
}
public function getRevisions() {
// we only have access to a single revision of the topic
return new ArrayIterator( array( $this ) );
}
public function getReplies() {
$topPost = new ImportPost( $this->importSource, $this->apiResponse );
return new ArrayIterator( array( $topPost ) );
}
public function getTimestamp() {
return wfTimestamp( TS_MW, $this->apiResponse['created'] );
}
/**
* @return IImportSummary|null
*/
public function getTopicSummary() {
$id = $this->getSummaryId();
if ( $id > 0 ) {
$data = $this->importSource->getPageData( $id );
if ( isset( $data['revisions'][0] ) ) {
return new ImportSummary( $data, $this->importSource );
} else {
return null;
}
} else {
return null;
}
}
/**
* @return integer
*/
protected function getSummaryId() {
return $this->apiResponse['summaryid'];
}
/**
* This needs to have a different value than the same apiResponse in an ImportPost.
* The ImportPost version refers to the first response to the topic.
*/
public function getObjectKey() {
return 'topic' . $this->importSource->getObjectKey( 'thread_id', $this->apiResponse['id'] );
}
public function getLogType() {
return "lqt-to-flow-topic";
}
public function getLogParameters() {
return array(
'lqt_thread_id' => $this->apiResponse['id'],
'lqt_orig_title' => $this->getTitle()->getPrefixedText(),
'lqt_subject' => $this->getText(),
);
}
public function getLqtThreadId() {
return $this->apiResponse['id'];
}
}
class ImportSummary extends PageRevisionedObject implements IImportSummary {
/** @var ImportSource **/
protected $source;
/**
* @param array $apiResponse
* @param ImportSource $source
* @throws ImportException
*/
public function __construct( array $apiResponse, ImportSource $source ) {
parent::__construct( $source, $apiResponse['pageid'] );
}
public function getObjectKey() {
return $this->importSource->getObjectKey( 'summary_id', $this->pageId );
}
}
class ImportRevision implements IObjectRevision {
/** @var IImportObject **/
protected $parent;
/** @var array **/
protected $apiResponse;
/**
* @var User Account used when the imported revision is by a suppressed user
*/
protected $scriptUser;
/**
* Creates an ImportRevision based on a MW page revision
*
* @param array $apiResponse An element from api.query.revisions
* @param IImportObject $parentObject
* @param User $scriptUser Account used when the imported revision is by a suppressed user
*/
function __construct( array $apiResponse, IImportObject $parentObject, User $scriptUser ) {
$this->apiResponse = $apiResponse;
$this->parent = $parentObject;
$this->scriptUser = $scriptUser;
}
/**
* @return string
*/
public function getText() {
$contentKey = 'content';
$content = $this->apiResponse[$contentKey];
if ( isset( $this->apiResponse['userhidden'] ) ) {
$template = wfMessage( 'flow-importer-lqt-suppressed-user-template' )->inContentLanguage()->plain();
$content .= "\n\n{{{$template}}}";
}
return $content;
}
public function getTimestamp() {
return wfTimestamp( TS_MW, $this->apiResponse['timestamp'] );
}
public function getAuthor() {
if ( isset( $this->apiResponse['userhidden'] ) ) {
return $this->scriptUser->getName();
} else {
return $this->apiResponse['user'];
}
}
public function getObjectKey() {
return $this->parent->getObjectKey() . ':rev:' . $this->apiResponse['revid'];
}
}
// The Moved* series of topics handle the LQT move stubs. They need to
// have their revision content rewriten from #REDIRECT to a template that
// has visible output like lqt generated per-request.
class MovedImportTopic extends ImportTopic {
public function getReplies() {
$topPost = new MovedImportPost( $this->importSource, $this->apiResponse );
return new ArrayIterator( array( $topPost ) );
}
}
class MovedImportPost extends ImportPost {
public function getRevisions() {
$scriptUser = $this->importSource->getScriptUser();
$factory = function ( $data, $parent ) use ( $scriptUser ) {
return new MovedImportRevision( $data, $parent, $scriptUser );
};
$pageData = $this->importSource->getPageData( $this->pageId );
return new RevisionIterator( $pageData, $this, $factory );
}
}
class MovedImportRevision extends ImportRevision {
/**
* Rewrites the '#REDIRECT [[...]]' of an autogenerated lqt moved
* thread stub into a template. While we don't re-write the link
* here, after importing the referenced thread LqtRedirector will
* make that Thread page a redirect to the Flow topic, essentially
* making these links still work.
*/
public function getText() {
$text = parent::getText();
$content = \ContentHandler::makeContent( $text, null, CONTENT_MODEL_WIKITEXT );
$target = $content->getRedirectTarget();
if ( !$target ) {
throw new ImportException( "Could not detect redirect within: $text" );
}
// To get the new talk page that this belongs to we would need to query the api
// for the new topic, for now not bothering.
$template = wfMessage( 'flow-importer-lqt-moved-thread-template' )->inContentLanguage()->plain();
$arguments = implode( '|', array(
'author=' . parent::getAuthor(),
'date=' . MWTimestamp::getInstance( $this->apiResponse['timestamp'] )->timestamp->format( 'Y-m-d' ),
'title=' . $target->getPrefixedText(),
) );
return "{{{$template}|$arguments}}";
}
}
// Represents a revision the script makes on its own behalf, using a script user
class ScriptedImportRevision implements IObjectRevision {
/** @var IImportObject **/
protected $parent;
/** @var User */
protected $destinationScriptUser;
/** @var string */
protected $revisionText;
/** @var string */
protected $timestamp;
/**
* Creates a ScriptedImportRevision with the given timestamp, given a script user
* and arbitrary text.
*
* @param IImportObject $parentObject Object this is a revision of
* @param User $destinationScriptUser User that performed this scripted edit
* @param string $revisionText Text of revision
* @param IObjectRevision $baseRevision Base revision, used only for timestamp generation
*/
function __construct( IImportObject $parentObject, User $destinationScriptUser, $revisionText, $baseRevision ) {
$this->parent = $parentObject;
$this->destinationScriptUser = $destinationScriptUser;
$this->revisionText = $revisionText;
$baseTimestamp = wfTimestamp( TS_UNIX, $baseRevision->getTimestamp() );
// Set a minute after. If it uses $baseTimestamp again, there can be time
// collisions.
$this->timestamp = wfTimestamp( TS_UNIX, $baseTimestamp + 60 );
}
public function getText() {
return $this->revisionText;
}
public function getTimestamp() {
return $this->timestamp;
}
public function getAuthor() {
return $this->destinationScriptUser->getName();
}
// XXX: This is called but never used, but if it were, including getText and getAuthor in
// the key might not be desirable, because we don't necessarily want to re-import
// the revision when these change.
public function getObjectKey() {
return $this->parent->getObjectKey() . ':rev:scripted:' . md5( $this->getText() . $this->getAuthor() );
}
}
class ImportHeader extends PageRevisionedObject implements IImportHeader {
/** @var ApiBackend **/
protected $api;
/** @var string **/
protected $title;
/** @var array **/
protected $pageData;
/** @var ImportSource **/
protected $source;
public function __construct( ApiBackend $api, ImportSource $source, $title ) {
$this->api = $api;
$this->title = $title;
$this->source = $source;
$this->pageData = null;
}
public function getRevisions() {
if ( $this->pageData === null ) {
// Previous revisions of the header are preserved in the underlying wikitext
// page history. Only the top revision is imported.
$response = $this->api->retrieveTopRevisionByTitle( array( $this->title ) );
$this->pageData = reset( $response );
}
$revisions = array();
if ( isset( $this->pageData['revisions'] ) && count( $this->pageData['revisions'] ) > 0 ) {
$lastLqtRevision = new ImportRevision(
end( $this->pageData['revisions'] ),
$this,
$this->source->getScriptUser()
);
$titleObject = Title::newFromText( $this->title );
$cleanupRevision = $this->createHeaderCleanupRevision( $lastLqtRevision, $titleObject );
$revisions = array( $lastLqtRevision, $cleanupRevision );
}
return new ArrayIterator( $revisions );
}
/**
* @param IObjectRevision $lastRevision last imported header revision
* @param Title $archiveTitle archive page title associated with header
* @return IObjectRevision generated revision for cleanup edit
*/
protected function createHeaderCleanupRevision( IObjectRevision $lastRevision, Title $archiveTitle ) {
$wikitextForLastRevision = $lastRevision->getText();
// This is will remove all instances, without attempting to check if it's in
// nowiki, etc. It also ignores case and spaces in places where it doesn't
// matter.
$newWikitext = ConversionStrategy::removeLqtMagicWord( $wikitextForLastRevision );
$templateName = wfMessage( 'flow-importer-lqt-converted-template' )->inContentLanguage()->plain();
$arguments = implode( '|', array(
'archive=' . $archiveTitle->getPrefixedText(),
'date=' . MWTimestamp::getInstance()->timestamp->format( 'Y-m-d' ),
) );
$newWikitext .= "\n\n{{{$templateName}|$arguments}}";
$cleanupRevision = new ScriptedImportRevision(
$this,
$this->source->getScriptUser(),
$newWikitext,
$lastRevision
);
return $cleanupRevision;
}
public function getObjectKey() {
return $this->source->getObjectKey( 'header_for', $this->title );
}
}