/** * @param Title $title * @param PostRevision $post * @param User $user * @param string $content * @param string $format wikitext|html * @param string $changeType * @return PostSummary */ public static function create(Title $title, PostRevision $post, User $user, $content, $format, $changeType) { $obj = new self(); $obj->revId = UUID::create(); $obj->user = UserTuple::newFromUser($user); $obj->prevRevision = null; $obj->changeType = $changeType; $obj->summaryTargetId = $post->getPostId(); $obj->setContent($content, $format, $title); return $obj; }
/** * @param PostRevision|PostSummary $object * @param array $metadata * @return string alphadecimal uuid * @throws InvalidInputException When $object is not PostRevision or PostSummary */ protected function findTopicRootId($object, array $metadata) { if (isset($metadata['workflow']) && $metadata['workflow'] instanceof Workflow) { return $metadata['workflow']->getId(); } elseif ($object instanceof PostRevision) { return $object->getRootPost()->getPostId()->getAlphadecimal(); } elseif ($object instanceof PostSummary) { return $object->getCollection()->getWorkflowId()->getAlphadecimal(); } else { throw new InvalidInputException('Unexpected revision type: ' . get_class($object)); } }
protected function log(PostRevision $post, Workflow $workflow) { $moderationChangeTypes = self::getModerationChangeTypes(); if (!in_array($post->getChangeType(), $moderationChangeTypes)) { // Do nothing for non-moderation actions return; } if ($this->moderationLogger->canLog($post, $post->getChangeType())) { $workflowId = $workflow->getId(); $this->moderationLogger->log($post, $post->getChangeType(), $post->getModeratedReason(), $workflowId); } }
public function somethingProvider() { return array(array('Reply recent change goes to the topic', NS_TOPIC, function ($workflow, $user) { $first = PostRevision::create($workflow, $user, 'blah blah', 'wikitext'); return $first->reply($workflow, $user, 'fofofo', 'wikitext'); })); }
public function commit() { if ($this->action !== 'new-topic') { throw new FailCommitException('Unknown commit action', 'fail-commit'); } $metadata = array('workflow' => $this->topicWorkflow, 'board-workflow' => $this->workflow, 'topic-title' => $this->topicTitle, 'first-post' => $this->firstPost); /* * Order of storage is important! We've been changing when we stored * workflow a couple of times. For now, it needs to be stored first: * * OccupationListener.php (workflow listener) must first create the * board before NotificationListener.php (topic/post listeners) * creates notifications (& mails) that link to the board * * ReferenceExtractor.php (run from ReferenceRecorder.php, a post * listener) needs to parse content with Parsoid & for that it needs * the board title. AbstractRevision::getContent() will figure out * the title from the workflow: $this->getCollection()->getTitle() * If you even feel the need to change the order, make sure you come * up with a fix for the above things ;) */ $this->storage->put($this->workflow, array()); // 'discussion' workflow $this->storage->put($this->topicWorkflow, $metadata); // 'topic' workflow $this->storage->put($this->topicListEntry, $metadata); $this->storage->put($this->topicTitle, $metadata); if ($this->firstPost !== null) { $this->storage->put($this->firstPost, $metadata + array('reply-to' => $this->topicTitle)); } $output = array('topic-page' => $this->topicWorkflow->getArticleTitle()->getPrefixedText(), 'topic-id' => $this->topicTitle->getPostId(), 'topic-revision-id' => $this->topicTitle->getRevisionId(), 'post-id' => $this->firstPost ? $this->firstPost->getPostId() : null, 'post-revision-id' => $this->firstPost ? $this->firstPost->getRevisionId() : null); return $output; }
/** * @dataProvider spamProvider */ public function testSpam($message, $expect, $content, $maxLength) { $title = Title::newFromText('UTPage'); $user = User::newFromName('127.0.0.1', false); $workflow = Workflow::create('topic', $title); $topic = PostRevision::create($workflow, $user, 'title content', 'wikitext'); $reply = $topic->reply($workflow, $user, $content, 'wikitext'); $spamFilter = new ContentLengthFilter($maxLength); $status = $spamFilter->validate($this->getMock('IContextSource'), $reply, null, $title); $this->assertEquals($expect, $status->isOK()); }
/** * There was a bug where all anonymous users got the same * user links output, this checks that they are distinct. */ public function testNonRepeatingUserLinksForAnonymousUsers() { $templating = $this->mockTemplating(); $user = User::newFromName('127.0.0.1', false); $title = Title::newMainPage(); $workflow = Workflow::create('topic', $title); $topicTitle = PostRevision::create($workflow, $user, 'some content', 'wikitext'); $hidden = $topicTitle->moderate($user, $topicTitle::MODERATED_HIDDEN, 'hide-topic', 'hide and go seek'); $this->assertContains('Special:Contributions/127.0.0.1', $templating->getUserLinks($hidden), 'User links should include anonymous contributions'); $hidden = $topicTitle->moderate(User::newFromName('10.0.0.2', false), $topicTitle::MODERATED_HIDDEN, 'hide-topic', 'hide and go seek'); $this->assertContains('Special:Contributions/10.0.0.2', $templating->getUserLinks($hidden), 'An alternate user should have the correct anonymous contributions'); }
public function testSetsRevIdAndPostIdForReplys() { $state = $this->createState(); $user = User::newFromName('127.0.0.1', false); $title = Title::newMainPage(); $topicWorkflow = Workflow::create('topic', $title); $topicTitle = PostRevision::create($topicWorkflow, $user, 'sing song', 'wikitext'); $reply = $topicTitle->reply($topicWorkflow, $user, 'fantastic!', 'wikitext'); $now = time(); $state->setRevisionTimestamp($reply, $now - 54321); $this->assertEquals($now - 54321, $reply->getRevisionId()->getTimestampObj()->getTimestamp(TS_UNIX), 'The first reply revision must have its revision id set appropriatly'); $this->assertTrue($reply->getPostId()->equals($reply->getRevisionId()), 'The first revision of a reply shares its postId and revId'); }
public function testContentLength() { $content = 'This is a topic title'; $nextContent = 'Changed my mind'; $title = Title::newMainPage(); $user = User::newFromName('127.0.0.1', false); $workflow = Workflow::create('topic', $title); $topic = PostRevision::create($workflow, $user, $content, 'wikitext'); $this->assertEquals(0, $topic->getPreviousContentLength()); $this->assertEquals(mb_strlen($content), $topic->getContentLength()); $next = $topic->newNextRevision($user, $nextContent, 'wikitext', 'edit-title', $title); $this->assertEquals(mb_strlen($content), $next->getPreviousContentLength()); $this->assertEquals(mb_strlen($nextContent), $next->getContentLength()); }
public function testValidateDoesntBlowUp() { $filter = new ConfirmEdit(); if (!$filter->enabled()) { $this->markTestSkipped('ConfirmEdit is not enabled'); } $user = User::newFromName('127.0.0.1', false); $title = Title::newMainPage(); $workflow = Workflow::create('topic', $title); $oldRevision = PostRevision::create($workflow, $user, 'foo', 'wikitext'); $newRevision = $oldRevision->newNextRevision($user, 'bar', 'wikitext', 'change-type', $title); $context = $this->getMock('IContextSource'); $context->expects($this->any())->method('getUser')->will($this->returnValue($user)); $status = $filter->validate($context, $newRevision, $oldRevision, $title); $this->assertInstanceOf('Status', $status); $this->assertTrue($status->isGood()); }
/** * @param string $action Permissions action to require to return revision * @return AbstractRevision|null * @throws InvalidDataException */ public function loadTopicTitle($action = 'view') { if ($this->workflow->isNew()) { throw new InvalidDataException('New workflows do not have any related content', 'missing-topic-title'); } if ($this->topicTitle === null) { $found = $this->storage->find('PostRevision', array('rev_type_id' => $this->workflow->getId()), array('sort' => 'rev_id', 'order' => 'DESC', 'limit' => 1)); if (!$found) { throw new InvalidDataException('Every workflow must have an associated topic title', 'missing-topic-title'); } $this->topicTitle = reset($found); // this method loads only title, nothing else; otherwise, you're // looking for loadRootPost $this->topicTitle->setChildren(array()); $this->topicTitle->setDepth(0); $this->topicTitle->setRootPost($this->topicTitle); } if (!$this->permissions->isAllowed($this->topicTitle, $action)) { $this->addError('permissions', $this->getDisallowedErrorMessage($this->topicTitle)); return null; } return $this->topicTitle; }
/** * @param UUID[] $missing * @return PostRevision */ protected function createFakePosts(array $missing) { $parents = $this->treeRepository->fetchParentMap($missing); $posts = array(); foreach ($missing as $uuid) { $alpha = $uuid->getAlphadecimal(); if (!isset($parents[$alpha])) { wfDebugLog('Flow', __METHOD__ . ": Unable not locate parent for postid {$alpha}"); continue; } $content = wfMessage('flow-stub-post-content')->text(); $username = wfMessage('flow-system-usertext')->text(); $user = User::newFromName($username); // create a stub post instead of failing completely $post = PostRevision::newFromId($uuid, $user, $content, 'wikitext'); $post->setReplyToId($parents[$alpha]); $posts[$alpha] = $post; } return $posts; }
/** * Update history revision */ private function updateHistory(PostRevision $post, $wiki) { if ($post->getPrevRevisionId()) { $parent = Container::get('storage.post')->get(UUID::create($post->getPrevRevisionId())); if ($parent) { $this->updateRevision($parent, $wiki); $this->updateHistory($parent, $wiki); } } }
/** * Process an array of users linked to in a comment into a list of users * who should actually be notified. * * Removes duplicates, anonymous users, self-mentions, and mentions of the * owner of the talk page * @param User[] $mentions Array of User objects * @param PostRevision $post The Post that is being examined. * @param \Title $title The Title of the page that the comment is made on. * @return array Array of user IDs */ protected function filterMentionedUsers($mentions, PostRevision $post, $title) { $outputMentions = array(); global $wgFlowMaxMentionCount; foreach ($mentions as $mentionedUser) { // Don't notify anonymous users if ($mentionedUser->isAnon()) { continue; } // Don't notify the user who made the post if ($mentionedUser->getId() == $post->getUserId()) { continue; } if (count($outputMentions) > $wgFlowMaxMentionCount) { break; } $outputMentions[$mentionedUser->getId()] = $mentionedUser->getId(); } return $outputMentions; }
/** * Generate a reply to $this->revision (which is a topic title) * * @param array $overrides * @return PostRevision * @throws \Flow\Exception\FlowException */ protected function generatePost($overrides) { $uuid = UUID::create(); return $this->generateObject($overrides + array('rev_change_type' => 'reply', 'tree_rev_descendant_id' => $uuid->getBinary(), 'rev_type_id' => $uuid->getBinary(), 'tree_parent_id' => $this->revision->getPostId())); }
/** * @param UUID[] $topicIds * @return PostRevision[] * @throws InvalidDataException */ public function getMulti(array $topicIds) { if (!$topicIds) { return array(); } // load posts for all located post ids $allPostIds = $this->fetchRelatedPostIds($topicIds); $queries = array(); foreach ($allPostIds as $postId) { $queries[] = array('rev_type_id' => $postId); } $found = $this->storage->findMulti('PostRevision', $queries, array('sort' => 'rev_id', 'order' => 'DESC', 'limit' => 1)); /** @var PostRevision[] $posts */ $posts = $children = array(); foreach ($found as $indexResult) { $post = reset($indexResult); // limit => 1 means only 1 result per query if (isset($posts[$post->getPostId()->getAlphadecimal()])) { throw new InvalidDataException('Multiple results for id: ' . $post->getPostId()->getAlphadecimal(), 'fail-load-data'); } $posts[$post->getPostId()->getAlphadecimal()] = $post; } $prettyPostIds = array(); foreach ($allPostIds as $id) { $prettyPostIds[] = $id->getAlphadecimal(); } $missing = array_diff($prettyPostIds, array_keys($posts)); if ($missing) { // convert string uuid's into UUID objects /** @var UUID[] $missingUUID */ $missingUUID = array_map(array('Flow\\Model\\UUID', 'create'), $missing); // we'll need to know parents to add stub post correctly in post hierarchy $parents = $this->treeRepo->fetchParentMap($missingUUID); $missingParents = array_diff($missing, array_keys($parents)); if ($missingParents) { // if we can't fetch a post's original position in the tree // hierarchy, we can't create a stub post to display, so bail throw new InvalidDataException('Missing Posts & parents: ' . json_encode($missingParents), 'fail-load-data'); } foreach ($missingUUID as $postId) { $content = wfMessage('flow-stub-post-content')->text(); $username = wfMessage('flow-system-usertext')->text(); $user = \User::newFromName($username); // create a stub post instead of failing completely $post = PostRevision::newFromId($postId, $user, $content, 'wikitext'); $post->setReplyToId($parents[$postId->getAlphadecimal()]); $posts[$postId->getAlphadecimal()] = $post; wfWarn('Missing Posts: ' . FormatJson::encode($missing)); } } // another helper to catch bugs in dev $extra = array_diff(array_keys($posts), $prettyPostIds); if ($extra) { throw new InvalidDataException('Found unrequested posts: ' . FormatJson::encode($extra), 'fail-load-data'); } // populate array of children foreach ($posts as $post) { if ($post->getReplyToId()) { $children[$post->getReplyToId()->getAlphadecimal()][$post->getPostId()->getAlphadecimal()] = $post; } } $extraParents = array_diff(array_keys($children), $prettyPostIds); if ($extraParents) { throw new InvalidDataException('Found posts with unrequested parents: ' . FormatJson::encode($extraParents), 'fail-load-data'); } foreach ($posts as $postId => $post) { $postChildren = array(); $postDepth = 0; // link parents to their children if (isset($children[$postId])) { // sort children with oldest items first ksort($children[$postId]); $postChildren = $children[$postId]; } // determine threading depth of post $replyToId = $post->getReplyToId(); while ($replyToId && isset($children[$replyToId->getAlphadecimal()])) { $postDepth++; $replyToId = $posts[$replyToId->getAlphadecimal()]->getReplyToId(); } $post->setChildren($postChildren); $post->setDepth($postDepth); } // return only the requested posts, rest are available as children. // Return in same order as requested /** @var PostRevision[] $roots */ $roots = array(); foreach ($topicIds as $id) { $roots[$id->getAlphadecimal()] = $posts[$id->getAlphadecimal()]; } // Attach every post in the tree to its root. setRootPost // recursively applies it to all children as well. foreach ($roots as $post) { $post->setRootPost($post); } return $roots; }
/** * Saves a PostRevision to storage. * Be sure to add the required tables to $tablesUsed and add @group Database * to the class' phpDoc. * * @param PostRevision $revision */ protected function store(PostRevision $revision) { if ($revision->isTopicTitle()) { $root = $revision; } else { /** @var PostCollection $parentCollection */ $parentCollection = PostCollection::newFromId($revision->getReplyToId()); $root = $parentCollection->getRoot()->getLastRevision(); } $topicWorkflow = $this->workflows[$root->getCollectionId()->getAlphadecimal()]; $boardWorkflow = Container::get('factory.loader.workflow')->createWorkflowLoader($topicWorkflow->getOwnerTitle())->getWorkflow(); $metadata = array('workflow' => $topicWorkflow, 'board-workflow' => $boardWorkflow); // check if this topic (+ workflow + board workflow + board page) have // already been inserted or do so now $found = $this->getStorage()->find('TopicListEntry', array('topic_id' => $topicWorkflow->getId())); if (!$found) { $title = $boardWorkflow->getOwnerTitle(); $user = User::newFromName('127.0.0.1', false); /** @var OccupationController $occupationController */ $occupationController = Container::get('occupation_controller'); // make sure user has rights to create board $user->mRights = array_merge($user->getRights(), array('flow-create-board')); $occupationController->allowCreation($title, $user); $occupationController->ensureFlowRevision(new \Article($title), $boardWorkflow); $topicListEntry = TopicListEntry::create($boardWorkflow, $topicWorkflow); $this->getStorage()->put($boardWorkflow, $metadata); $this->getStorage()->put($topicWorkflow, $metadata); $this->getStorage()->put($topicListEntry, $metadata); } $this->getStorage()->put($revision, $metadata); /** @var SplQueue $deferredQueue */ $deferredQueue = Container::get('deferred_queue'); while (!$deferredQueue->isEmpty()) { try { DeferredUpdates::addCallableUpdate($deferredQueue->dequeue()); // doing updates 1 by 1 so an exception doesn't break others in // the queue DeferredUpdates::doUpdates(); } catch (\MWException $e) { // ignoring exceptions for now, not all are phpunit-proof yet } } // save for removal at end of tests $this->revisions[] = $revision; }
/** * Gets the root post ID for a given PostRevision * @param PostRevision $revision The revision to get the root post ID for. * @return UUID The UUID for the root post. * @throws \MWException */ protected function getRootPostId(PostRevision $revision) { $postId = $revision->getPostId(); if ($revision->isTopicTitle()) { return $postId; } elseif (isset($this->rootPostIdCache[$postId->getAlphadecimal()])) { return $this->rootPostIdCache[$postId->getAlphadecimal()]; } else { throw new \MWException("Unable to find root post ID for post " . $postId->getAlphadecimal()); } }
/** * @param TopicImportState $state * @param IImportPost $post * @param PostRevision $replyTo * @param string $logPrefix */ public function importPost(TopicImportState $state, IImportPost $post, PostRevision $replyTo, $logPrefix = ' ') { $state->parent->logger->info($logPrefix . "Importing post"); $postId = $state->parent->getImportedId($post); $topRevision = false; if ($postId) { $topRevision = $state->parent->getTopRevision('PostRevision', $postId); } if ($topRevision) { $state->parent->logger->info($logPrefix . "Post previously imported"); } else { $replyRevisions = $this->importObjectWithHistory($post, function (IObjectRevision $rev) use($replyTo, $state) { return $replyTo->reply($state->topicWorkflow, $state->parent->createUser($rev->getAuthor()), $rev->getText(), 'wikitext'); }, 'edit-post', $state->parent, $state->topicWorkflow->getArticleTitle()); $topRevision = end($replyRevisions); $metadata = array('workflow' => $state->topicWorkflow, 'board-workflow' => $state->parent->boardWorkflow, 'topic-title' => $state->topicTitle, 'reply-to' => $replyTo); $state->parent->put($replyRevisions, $metadata); $state->parent->recordAssociation($topRevision->getPostId(), $post); $state->parent->logger->info($logPrefix . "Finished importing post with " . count($replyRevisions) . " revisions"); $state->parent->postprocessor->afterPostImported($state, $post, $topRevision->getPostId()); } $state->recordModificationTime($topRevision->getRevisionId()); foreach ($post->getReplies() as $subReply) { $this->importPost($state, $subReply, $topRevision, $logPrefix . ' '); } }
public static function onIRCLineURLProvider() { // data providers do not run in the same context as the actual test, as such we // can't create Title objects because they can have the wrong wikiID. Instead we // pass closures into the test that create the objects within the correct context. $newHeader = function (User $user) { $title = Title::newFromText('Talk:Hook_test'); $workflow = Container::get('factory.loader.workflow')->createWorkflowLoader($title)->getWorkflow(); $header = Header::create($workflow, $user, 'header content', 'wikitext'); $metadata = array('workflow' => $workflow, 'revision' => $header); /** @var OccupationController $occupationController */ $occupationController = Container::get('occupation_controller'); // make sure user has rights to create board $user->mRights = array_merge($user->getRights(), array('flow-create-board')); $occupationController->allowCreation($title, $user); $occupationController->ensureFlowRevision(new \Article($title), $workflow); Container::get('storage')->put($workflow, $metadata); return $metadata; }; $freshTopic = function (User $user) { $title = Title::newFromText('Talk:Hook_test'); $boardWorkflow = Container::get('factory.loader.workflow')->createWorkflowLoader($title)->getWorkflow(); $topicWorkflow = Workflow::create('topic', $boardWorkflow->getArticleTitle()); $topicList = TopicListEntry::create($boardWorkflow, $topicWorkflow); $topicTitle = PostRevision::create($topicWorkflow, $user, 'some content', 'wikitext'); $metadata = array('workflow' => $topicWorkflow, 'board-workflow' => $boardWorkflow, 'topic-title' => $topicTitle, 'revision' => $topicTitle); /** @var OccupationController $occupationController */ $occupationController = Container::get('occupation_controller'); // make sure user has rights to create board $user->mRights = array_merge($user->getRights(), array('flow-create-board')); $occupationController->allowCreation($title, $user); $occupationController->ensureFlowRevision(new \Article($title), $boardWorkflow); $storage = Container::get('storage'); $storage->put($boardWorkflow, $metadata); $storage->put($topicWorkflow, $metadata); $storage->put($topicList, $metadata); $storage->put($topicTitle, $metadata); return $metadata; }; $replyToTopic = function (User $user) use($freshTopic) { $metadata = $freshTopic($user); $firstPost = $metadata['topic-title']->reply($metadata['workflow'], $user, 'ffuts dna ylper', 'wikitext'); $metadata = array('first-post' => $firstPost, 'revision' => $firstPost) + $metadata; Container::get('storage.post')->put($firstPost, $metadata); return $metadata; }; return array(array('Freshly created topic', $freshTopic, array('action' => 'history')), array('Reply to topic', $replyToTopic, array('action' => 'history')), array('Edit topic title', function ($user) use($freshTopic) { $metadata = $freshTopic($user); $title = $metadata['workflow']->getArticleTitle(); return array('revision' => $metadata['revision']->newNextRevision($user, 'gnihtemos gnihtemos', 'wikitext', 'edit-title', $title)) + $metadata; }, array('action' => 'compare-post-revisions')), array('Edit post', function ($user) use($replyToTopic) { $metadata = $replyToTopic($user); $title = $metadata['workflow']->getArticleTitle(); return array('revision' => $metadata['revision']->newNextRevision($user, 'IT\'S CAPS LOCKS DAY!', 'wikitext', 'edit-post', $title)) + $metadata; }, array('action' => 'compare-post-revisions')), array('Edit board header', function ($user) use($newHeader) { $metadata = $newHeader($user); $title = $metadata['workflow']->getArticleTitle(); return array('revision' => $metadata['revision']->newNextRevision($user, 'STILL CAPS LOCKS DAY!', 'wikitext', 'edit-header', $title)) + $metadata; }, array('action' => 'compare-header-revisions')), array('Moderate a post', function ($user) use($replyToTopic) { $metadata = $replyToTopic($user); return array('revision' => $metadata['revision']->moderate($user, $metadata['revision']::MODERATED_DELETED, 'delete-post', 'something about cruise control')) + $metadata; }, array('action' => 'history')), array('Moderate a topic', function ($user) use($freshTopic) { $metadata = $freshTopic($user); return array('revision' => $metadata['revision']->moderate($user, $metadata['revision']::MODERATED_HIDDEN, 'hide-topic', 'adorable kittens')) + $metadata; }, array('action' => 'history'))); }
/** * While topic's themselves are plaintext and do not contain any references, * moderation actions change what references are visible. When transitioning * from or to a generically visible state (unmoderated or locked) the entire * topic + summary needs to be re-evaluated. * * @param Workflow $workflow * @param PostRevision $current Topic revision object that was inserted * @return array Contains two arrays, first the references to add a second * the references to remove * @throws FlowException */ protected function calculateChangesFromTopic(Workflow $workflow, PostRevision $current) { if ($current->isFirstRevision()) { return array(array(), array()); } $previous = $this->storage->get('PostRevision', $current->getPrevRevisionId()); if (!$previous) { throw new FlowException('Expcted previous revision of ' . $current->getPrevRevisionId()->getAlphadecimal()); } $isHidden = self::isHidden($current); $wasHidden = self::isHidden($previous); if ($isHidden === $wasHidden) { return array(array(), array()); } // re-run $revisions = $this->collectTopicRevisions($workflow); $added = array(); $removed = array(); foreach ($revisions as $revision) { list($add, $remove) = $this->calculateChangesFromExisting($workflow, $revision, $current); $added = array_merge($added, $add); $removed = array_merge($removed, $remove); } return array($added, $removed); }
/** * Recursively get the data for all children. This will add the revision's * content to the results array, with the post ID as key. * * @param PostRevision|PostSummary $revision * @return array */ public function getRevisionsData($revision) { // make sure we don't parse text that isn't meant to be parsed (e.g. // topic titles are never meant to be parsed from wikitext to html) $format = $revision->isFormatted() ? 'html' : 'wikitext'; // store type of revision so we can also search for very specific types // (e.g. titles only) // possible values will be: // * title // * post // * post-summary $type = $revision->getRevisionType(); if (method_exists($revision, 'isTopicTitle') && $revision->isTopicTitle()) { $type = 'title'; } $data = array(); if ($this->permissions->isAllowed($revision, 'view')) { $data[] = array('id' => $revision->getCollectionId()->getAlphadecimal(), 'text' => trim(Sanitizer::stripAllTags($revision->getContent($format))), 'source_text' => $revision->getContent('wikitext'), 'moderation_state' => $revision->getModerationState(), 'timestamp' => $revision->getCollectionId()->getTimestamp(TS_ISO_8601), 'update_timestamp' => $revision->getRevisionId()->getTimestamp(TS_ISO_8601), 'type' => $type); } if ($revision instanceof PostRevision) { // get data from all child posts too foreach ($revision->getChildren() as $child) { $data = array_merge($data, $this->getRevisionsData($child)); } } return $data; }