/** * @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; } } }