/**
  * @param AbstractRevision $revision
  * @param Title $title
  * @return array
  */
 public function getLinks(AbstractRevision $revision, Title $title)
 {
     global $wgParser;
     $options = new \ParserOptions();
     $output = $wgParser->parse($revision->getContent('wikitext'), $title, $options);
     return array_keys($output->getExternalLinks());
 }
 /**
  * @param string $type
  * @param AbstractRevision $object
  * @param array $metadata
  * @param array $params
  * @throws InvalidDataException
  */
 protected function notifyPostChange($type, $object, $metadata, array $params = array())
 {
     if (!isset($metadata['workflow'], $metadata['topic-title'])) {
         throw new InvalidDataException('Invalid metadata for revision ' . $object->getRevisionId()->getAlphadecimal(), 'missing-metadata');
     }
     $workflow = $metadata['workflow'];
     if (!$workflow instanceof Workflow) {
         throw new InvalidDataException('Workflow metadata is not a Workflow', 'missing-metadata');
     }
     $this->notificationController->notifyPostChange($type, $params + array('revision' => $object, 'title' => $workflow->getOwnerTitle(), 'topic-workflow' => $workflow, 'topic-title' => $metadata['topic-title']));
 }
 /**
  * @param string[] $row
  * @param Header|null $obj
  * @return Header
  */
 public static function fromStorageRow(array $row, $obj = null)
 {
     /** @var $obj Header */
     $obj = parent::fromStorageRow($row, $obj);
     $obj->workflowId = UUID::create($row['rev_type_id']);
     return $obj;
 }
 /**
  * @param IContextSource $context
  * @param AbstractRevision $newRevision
  * @param AbstractRevision|null $oldRevision
  * @param Title $title
  * @return Status
  */
 public function validate(IContextSource $context, AbstractRevision $newRevision, AbstractRevision $oldRevision = null, Title $title)
 {
     global $wgSpamRegex;
     /*
      * This should not roundtrip to Parsoid; SpamRegex checks will be
      * performed upon submitting new content, and content is always
      * submitted in wikitext. It will only be transformed once it's being
      * saved to DB.
      */
     $text = $newRevision->getContent('wikitext');
     // back compat, $wgSpamRegex may be a single string or an array of regexes
     $regexes = (array) $wgSpamRegex;
     foreach ($regexes as $regex) {
         if (preg_match($regex, $text, $matches)) {
             return Status::newFatal('spamprotectionmatch', $matches[0]);
         }
     }
     return Status::newGood();
 }
 /**
  * @param AbstractRevision $revision Revision object
  * @param array $row Revision row
  * @param array $metadata;
  */
 public function onAfterInsert($revision, array $row, array $metadata)
 {
     global $wgRCFeeds;
     // No action on imported revisions
     if (isset($metadata['imported']) && $metadata['imported']) {
         return;
     }
     $action = $revision->getChangeType();
     $revisionId = $revision->getRevisionId()->getAlphadecimal();
     $timestamp = $revision->getRevisionId()->getTimestamp();
     /** @var Workflow $workflow */
     $workflow = $metadata['workflow'];
     $user = $revision->getUser();
     if (!$this->isAllowed($revision, $action)) {
         return;
     }
     $title = $this->getRcTitle($workflow, $revision->getChangeType());
     $attribs = array('rc_namespace' => $title->getNamespace(), 'rc_title' => $title->getDBkey(), 'rc_user' => $row['rev_user_id'], 'rc_user_text' => $this->usernames->get(wfWikiId(), $row['rev_user_id'], $row['rev_user_ip']), 'rc_type' => RC_FLOW, 'rc_source' => self::SRC_FLOW, 'rc_minor' => 0, 'rc_bot' => 0, 'rc_patrolled' => $user->isAllowed('autopatrol') ? 1 : 0, 'rc_old_len' => $revision->getPreviousContentLength(), 'rc_new_len' => $revision->getContentLength(), 'rc_this_oldid' => 0, 'rc_last_oldid' => 0, 'rc_log_type' => null, 'rc_params' => serialize(array('flow-workflow-change' => array('action' => $action, 'revision_type' => get_class($revision), 'revision' => $revisionId, 'workflow' => $workflow->getId()->getAlphadecimal()))), 'rc_cur_id' => 0, 'rc_comment' => '', 'rc_timestamp' => $timestamp, 'rc_deleted' => 0);
     $rc = $this->rcFactory->newFromRow((object) $attribs);
     $rc->save(true);
     // Insert into db
     $feeds = $wgRCFeeds;
     // Override the IRC formatter with our own formatter
     foreach (array_keys($feeds) as $name) {
         $feeds[$name]['original_formatter'] = $feeds[$name]['formatter'];
         $feeds[$name]['formatter'] = $this->ircFormatter;
     }
     // pre-load the irc formatter which will be triggered via hook
     $this->ircFormatter->associate($rc, array('revision' => $revision) + $metadata);
     // run the feeds/irc/etc external notifications
     $rc->notifyRCFeeds($feeds);
 }
 /**
  * @param IContextSource $context
  * @param AbstractRevision $newRevision
  * @param AbstractRevision|null $oldRevision
  * @param Title $title
  * @return Status
  */
 public function validate(IContextSource $context, AbstractRevision $newRevision, AbstractRevision $oldRevision = null, Title $title)
 {
     $vars = \AbuseFilter::getEditVars($title);
     $vars->addHolders(\AbuseFilter::generateUserVars($this->user), \AbuseFilter::generateTitleVars($title, 'ARTICLE'));
     $vars->setVar('ACTION', $newRevision->getChangeType());
     /*
      * This should not roundtrip to Parsoid; AbuseFilter checks will be
      * performed upon submitting new content, and content is always
      * submitted in wikitext. It will only be transformed once it's being
      * saved to DB.
      */
     $vars->setLazyLoadVar('new_wikitext', 'FlowRevisionContent', array('revision' => $newRevision));
     $vars->setLazyLoadVar('new_size', 'length', array('length-var' => 'new_wikitext'));
     /*
      * This may roundtrip to Parsoid if content is stored in HTML.
      * Since the variable is lazy-loaded, it will not roundtrip unless the
      * variable is actually used.
      */
     $vars->setLazyLoadVar('old_wikitext', 'FlowRevisionContent', array('revision' => $oldRevision));
     $vars->setLazyLoadVar('old_size', 'length', array('length-var' => 'old_wikitext'));
     return \AbuseFilter::filterAction($vars, $title, $this->group);
 }
 /**
  * @param IContextSource $context
  * @param AbstractRevision $newRevision
  * @param AbstractRevision|null $oldRevision
  * @param Title $title
  * @return Status
  */
 public function validate(IContextSource $context, AbstractRevision $newRevision, AbstractRevision $oldRevision = null, Title $title)
 {
     global $wgOut;
     $newContent = $newRevision->getContent('wikitext');
     $oldContent = $oldRevision !== null ? $oldRevision->getContent('wikitext') : '';
     /** @var SimpleCaptcha $captcha */
     $captcha = ConfirmEditHooks::getInstance();
     $wikiPage = new WikiPage($title);
     // first check if the submitted content is offensive (as flagged by
     // ConfirmEdit), next check for a (valid) captcha to have been entered
     if ($captcha->shouldCheck($wikiPage, $newContent, false, false, $oldContent) && !$captcha->passCaptchaLimited()) {
         // getting here means we submitted bad content without good captcha
         // result (or any captcha result at all) - let's get the captcha
         // HTML to display as error message!
         $html = $captcha->getForm();
         // some captcha implementations need CSS and/or JS, which is added
         // via their getForm() methods (which we just called) -
         // let's extract those and respond them along with the form HTML
         $html = $wgOut->buildCssLinks() . $wgOut->getScriptsForBottomQueue(true) . $html;
         $msg = wfMessage('flow-spam-confirmedit-form')->rawParams($html);
         return Status::newFatal($msg);
     }
     return Status::newGood();
 }
 /**
  * @param AbstractRevision $revision
  * @param Title|null $title
  * @param UUID $workflowId
  * @param UUID $oldRevId
  * @return Anchor
  * @throws FlowException When $revision is not PostRevision, Header or PostSummary
  */
 public function diffLink(AbstractRevision $revision, Title $title = null, UUID $workflowId, UUID $oldRevId = null)
 {
     if ($revision instanceof PostRevision) {
         return $this->diffPostLink($title, $workflowId, $revision->getRevisionId(), $oldRevId);
     } elseif ($revision instanceof Header) {
         return $this->diffHeaderLink($title, $workflowId, $revision->getRevisionId(), $oldRevId);
     } elseif ($revision instanceof PostSummary) {
         return $this->diffSummaryLink($title, $workflowId, $revision->getRevisionId(), $oldRevId);
     } else {
         throw new FlowException('Unknown revision type: ' . get_class($revision));
     }
 }
 /**
  * Retrieves the current revision for a given AbstractRevision
  * @param  AbstractRevision $revision The revision to retrieve the current revision for.
  * @return AbstractRevision|null      AbstractRevision of the current revision.
  */
 protected function getCurrentRevision(AbstractRevision $revision)
 {
     $collectionId = $revision->getCollectionId();
     if (!isset($this->currentRevisionsCache[$collectionId->getAlphadecimal()])) {
         $currentRevision = $revision->getCollection()->getLastRevision();
         $this->currentRevisionsCache[$collectionId->getAlphadecimal()] = $currentRevision->getRevisionId();
         $this->revisionCache[$currentRevision->getRevisionId()->getAlphadecimal()] = $currentRevision;
     }
     $currentRevisionId = $this->currentRevisionsCache[$collectionId->getAlphadecimal()];
     return $this->revisionCache[$currentRevisionId->getAlphaDecimal()];
 }
 /**
  * Given a certain revision, returns the next revision.
  *
  * @param AbstractRevision $revision
  * @return AbstractRevision|null null if there is no next revision
  */
 public function getNextRevision(AbstractRevision $revision)
 {
     // make sure all revisions have been loaded
     $this->getAllRevisions();
     // find requested id, based on given revision
     $ids = array_keys($this->revisions);
     $current = array_search($revision->getRevisionId()->getAlphadecimal(), $ids);
     $next = $current - 1;
     if ($next < 0) {
         return null;
     }
     return $this->getRevision(UUID::create($ids[$next]));
 }
 /**
  * @param AbstractRevision $revision
  * @return bool
  */
 private function excludeFromContributions(AbstractRevision $revision)
 {
     return (bool) $this->actions->getValue($revision->getChangeType(), 'exclude_from_contributions');
 }
 /**
  * Find a topic list id related to an abstract revision
  *
  * @param AbstractRevision $object
  * @param string[] $row
  * @param array $metadata
  * @return string Alphadecimal uid of the related board
  * @throws InvalidInputException When $object is not a Header, PostRevision or
  *  PostSummary instance.
  * @throws DataModelException When the related id cannot be located
  */
 protected function findTopicListId(AbstractRevision $object, array $row, array $metadata)
 {
     if ($object instanceof Header) {
         return $row['rev_type_id'];
     }
     if (isset($metadata['workflow']) && $metadata['workflow'] instanceof Workflow) {
         $topicId = $metadata['workflow']->getId();
     } else {
         if ($object instanceof PostRevision) {
             $post = $object;
         } elseif ($object instanceof PostSummary) {
             $post = $object->getCollection()->getPost()->getLastRevision();
         } else {
             throw new InvalidInputException('Unexpected object type: ' . get_class($object));
         }
         $topicId = $post->getRootPost()->getPostId();
     }
     $found = $this->om->find(array('topic_id' => $topicId));
     if (!$found) {
         throw new DataModelException("No topic list contains topic " . $topicId->getAlphadecimal() . ", called for revision " . $object->getRevisionId()->getAlphadecimal());
     }
     /** @var TopicListEntry $topicListEntry */
     $topicListEntry = reset($found);
     return $topicListEntry->getListId()->getAlphadecimal();
 }
 /**
  * @param AbstractRevision $revision
  * @return string
  */
 public function getContent(AbstractRevision $revision)
 {
     return $this->apply($revision->getContent('html'), $revision->getCollection()->getTitle());
 }
 /**
  * @param AbstractRevision $obj
  * @return string[]
  */
 public static function toStorageRow($obj)
 {
     return array('rev_id' => $obj->revId->getAlphadecimal(), 'rev_user_id' => $obj->user->id, 'rev_user_ip' => $obj->user->ip, 'rev_user_wiki' => $obj->user->wiki, 'rev_parent_id' => $obj->prevRevision ? $obj->prevRevision->getAlphadecimal() : null, 'rev_change_type' => $obj->changeType, 'rev_type' => $obj->getRevisionType(), 'rev_type_id' => $obj->getCollectionId()->getAlphadecimal(), 'rev_content' => $obj->content, 'rev_content_url' => $obj->contentUrl, 'rev_flags' => implode(',', $obj->flags), 'rev_mod_state' => $obj->moderationState, 'rev_mod_user_id' => $obj->moderatedBy ? $obj->moderatedBy->id : null, 'rev_mod_user_ip' => $obj->moderatedBy ? $obj->moderatedBy->ip : null, 'rev_mod_user_wiki' => $obj->moderatedBy ? $obj->moderatedBy->wiki : null, 'rev_mod_timestamp' => $obj->moderationTimestamp, 'rev_mod_reason' => $obj->moderatedReason, 'rev_last_edit_id' => $obj->lastEditId ? $obj->lastEditId->getAlphadecimal() : null, 'rev_edit_user_id' => $obj->lastEditUser ? $obj->lastEditUser->id : null, 'rev_edit_user_ip' => $obj->lastEditUser ? $obj->lastEditUser->ip : null, 'rev_edit_user_wiki' => $obj->lastEditUser ? $obj->lastEditUser->wiki : null, 'rev_content_length' => $obj->contentLength, 'rev_previous_content_length' => $obj->previousContentLength);
 }
 /**
  * @param IContextSource $context
  * @param AbstractRevision $newRevision
  * @param AbstractRevision|null $oldRevision
  * @param Title $title
  * @return Status
  */
 public function validate(IContextSource $context, AbstractRevision $newRevision, AbstractRevision $oldRevision = null, Title $title)
 {
     return $newRevision->getContentLength() > $this->maxLength ? Status::newFatal('flow-error-content-too-long', $this->maxLength) : Status::newGood();
 }
 /**
  * @param string $action
  * @param AbstractRevision|null $parent
  * @param array $overrides
  * @return PostRevision
  */
 public function generateRevision($action, AbstractRevision $parent = null, array $overrides = array())
 {
     $overrides['rev_change_type'] = $action;
     if ($parent) {
         $overrides['rev_parent_id'] = $parent->getRevisionId()->getBinary();
         $overrides['tree_rev_descendant_id'] = $parent->getPostId()->getBinary();
         $overrides['rev_type_id'] = $parent->getPostId()->getBinary();
     }
     switch ($action) {
         case 'restore-post':
             $overrides += array('rev_mod_state' => $this->moderation[$action], 'rev_mod_user_id' => null, 'rev_mod_user_ip' => null, 'rev_mod_timestamp' => null, 'rev_mod_reason' => 'unit test');
             break;
         case 'hide-post':
         case 'delete-post':
         case 'suppress-post':
             $overrides += array('rev_mod_state' => $this->moderation[$action], 'rev_mod_user_id' => 1, 'rev_mod_user_ip' => null, 'rev_mod_timestamp' => wfTimestampNow(), 'rev_mod_reason' => 'unit test');
             break;
         default:
             // nothing special
             break;
     }
     $revision = $this->generateObject($overrides);
     $this->store($revision);
     return $revision;
 }
 /**
  * @var AbstractRevision $summary
  * @var string           $timestamp
  */
 public function setRevisionTimestamp(AbstractRevision $revision, $timestamp)
 {
     $uid = $this->getTimestampId($timestamp);
     $setRevId = true;
     // We don't set the topic title postId as it was inherited from the workflow.  We only set the
     // postId for first revisions because further revisions inherit it from the parent which was
     // set appropriately.
     if ($revision instanceof PostRevision && $revision->isFirstRevision() && !$revision->isTopicTitle()) {
         $this->postIdProperty->setValue($revision, $uid);
     }
     if ($setRevId) {
         if ($revision->getRevisionId()->equals($revision->getLastContentEditId())) {
             $this->lastEditIdProperty->setValue($revision, $uid);
         }
         $this->revIdProperty->setValue($revision, $uid);
     }
 }
 /**
  * @param AbstractRevision $revision
  * @return AbstractRevision
  */
 protected function getRoot(AbstractRevision $revision)
 {
     if ($revision instanceof PostSummary) {
         $topicId = $revision->getSummaryTargetId();
     } elseif ($revision instanceof PostRevision && !$revision->isTopicTitle()) {
         try {
             $topicId = $revision->getCollection()->getWorkflowId();
         } catch (DataModelException $e) {
             // failed to locate root post (most likely in unit tests, where
             // we didn't store the tree)
             return $revision;
         }
     } else {
         // if we can't the revision it back to a root, this revision is root
         return $revision;
     }
     $collection = PostCollection::newFromId($topicId);
     return $collection->getLastRevision();
 }
 protected static function isHidden(AbstractRevision $revision)
 {
     return $revision->isModerated() && $revision->getModerationState() !== $revision::MODERATED_LOCKED;
 }
 /**
  * Cache key for last revision
  *
  * @param AbstractRevision $revision
  * @return string
  */
 protected function getLastRevCacheKey(AbstractRevision $revision)
 {
     return $revision->getCollectionId()->getAlphadecimal() . '-' . $revision->getRevisionType() . '-last-rev';
 }
 /**
  * @todo Move this to AbstractBlock and use for summary/header/etc.
  * @param AbstractRevision $revision
  * @return Message
  */
 protected function getDisallowedErrorMessage(AbstractRevision $revision)
 {
     if (in_array($this->action, array('moderate-topic', 'moderate-post'))) {
         /*
          * When failing to moderate an already moderated action (like
          * undo), show the more general "you have insufficient
          * permissions for this action" message, rather than the
          * specialized "this topic is <hidden|deleted|suppressed>" msg.
          */
         return $this->context->msg('flow-error-not-allowed');
     }
     $state = $revision->getModerationState();
     // display simple message
     // i18n messages:
     //  flow-error-not-allowed-hide,
     //  flow-error-not-allowed-reply-to-hide-topic
     //  flow-error-not-allowed-delete
     //  flow-error-not-allowed-reply-to-delete-topic
     //  flow-error-not-allowed-suppress
     //  flow-error-not-allowed-reply-to-suppress-topic
     if ($revision instanceof PostRevision) {
         $type = $revision->isTopicTitle() ? 'topic' : 'post';
     } else {
         $type = $revision->getRevisionType();
     }
     // Show a snippet of the relevant log entry if available.
     if (\LogPage::isLogType($state)) {
         // check if user has sufficient permissions to see log
         $logPage = new \LogPage($state);
         if ($this->context->getUser()->isAllowed($logPage->getRestriction())) {
             // LogEventsList::showLogExtract will write to OutputPage, but we
             // actually just want that text, to write it ourselves wherever we want,
             // so let's create an OutputPage object to then get the content from.
             $rc = new \RequestContext();
             $output = $rc->getOutput();
             // get log extract
             $entries = \LogEventsList::showLogExtract($output, array($state), $this->workflow->getArticleTitle()->getPrefixedText(), '', array('lim' => 10, 'showIfEmpty' => false, 'msgKey' => array(array("flow-error-not-allowed-{$this->action}-to-{$state}-{$type}", "flow-error-not-allowed-{$state}-extract"))));
             // check if there were any log extracts
             if ($entries) {
                 $message = new \RawMessage('$1');
                 return $message->rawParams($output->getHTML());
             }
         }
     }
     return $this->context->msg(array("flow-error-not-allowed-{$this->action}-to-{$state}-{$type}", "flow-error-not-allowed-{$state}", "flow-error-not-allowed"));
 }
 protected function expandReferences(Workflow $workflow, AbstractRevision $revision, array $references)
 {
     $referenceObjs = array();
     $factory = new ReferenceFactory($workflow, $revision->getRevisionType(), $revision->getCollectionId());
     foreach ($references as $ref) {
         $referenceObjs[] = $factory->{$ref['factoryMethod']}($ref['refType'], $ref['value']);
     }
     return $referenceObjs;
 }
 public function isComparable(AbstractRevision $cur, AbstractRevision $prev)
 {
     if ($cur->getRevisionType() == $prev->getRevisionType()) {
         return $cur->getCollectionId()->equals($prev->getCollectionId());
     } else {
         return false;
     }
 }
 protected function calcContentLength(AbstractRevision $revision)
 {
     if ($revision->isModerated() && !$revision->isLocked()) {
         return 0;
     } else {
         return $revision->getContentLength() ?: mb_strlen($revision->getContent('wikitext'));
     }
 }
 /**
  * @param PostRevision $rev
  * @return string[]
  */
 public static function toStorageRow($rev)
 {
     return parent::toStorageRow($rev) + array('tree_parent_id' => $rev->replyToId ? $rev->replyToId->getAlphadecimal() : null, 'tree_rev_descendant_id' => $rev->postId->getAlphadecimal(), 'tree_rev_id' => $rev->revId->getAlphadecimal(), 'tree_orig_user_id' => $rev->origUser->id, 'tree_orig_user_ip' => $rev->origUser->ip, 'tree_orig_user_wiki' => $rev->origUser->wiki);
 }
 /**
  * @param AbstractRevision $revision
  * @return string
  */
 protected function decideContentFormat(AbstractRevision $revision)
 {
     if ($revision instanceof PostRevision && $revision->isTopicTitle()) {
         return 'plaintext';
     }
     $alpha = $revision->getRevisionId()->getAlphadecimal();
     if (isset($this->revisionContentFormat[$alpha])) {
         return $this->revisionContentFormat[$alpha];
     }
     return $this->contentFormat;
 }
 /**
  * @param AbstractRevision $revision
  * @return MWTimestamp
  * @throws Exception
  * @throws TimestampException
  * @throws \Flow\Exception\DataModelException
  */
 protected function getUpdateTimestamp(AbstractRevision $revision)
 {
     $timestamp = $revision->getRevisionId()->getTimestampObj();
     if (!$revision instanceof PostRevision) {
         return $timestamp;
     }
     foreach ($revision->getChildren() as $child) {
         // go recursive, find timestamp of most recent child post
         $comparison = $this->getUpdateTimestamp($child);
         $diff = $comparison->diff($timestamp);
         // invert will be 1 if the diff is a negative time period from
         // child timestamp ($comparison) to $timestamp, which means that
         // $comparison is more recent than our current $timestamp
         if ($diff->invert) {
             $timestamp = $comparison;
         }
     }
     return $timestamp;
 }
 /**
  * @param AbstractRevision $revision
  * @return bool
  */
 private function excludeFromHistory(AbstractRevision $revision)
 {
     return (bool) $this->actions->getValue($revision->getChangeType(), 'exclude_from_history');
 }
 public function getModeratedRevision(AbstractRevision $revision)
 {
     if ($revision->isModerated()) {
         return $revision;
     } else {
         try {
             return Container::get('collection.cache')->getLastRevisionFor($revision);
         } catch (FlowException $e) {
             wfDebugLog('Flow', "Failed loading last revision for revid " . $revision->getRevisionId()->getAlphadecimal() . " with collection id " . $revision->getCollectionId()->getAlphadecimal());
             throw $e;
         }
     }
 }