public function format(FormatterRow $row) { if (!$this->permissions->isAllowed($row->revision, 'view')) { return ''; } $topic = Linker::link($row->workflow->getArticleTitle(), htmlspecialchars($row->revision->getContent('plaintext')), array('class' => 'mw-title')); $board = Linker::link($row->workflow->getOwnerTitle()); return wfMessage('flow-rc-topic-of-board')->rawParams($topic, $board)->escaped(); }
/** * Usually the revisions's content can just be displayed. In the event * of moderation, however, that info should not be exposed. * * If a specific i18n message is available for a certain moderation level, * that message will be returned (well, unless the user actually has the * required permissions to view the full content). Otherwise, in normal * cases, the full content will be returned. * * The content-type of the return value varies on the $format parameter. * Further processing in the final output stage must escape all formats * other than the default 'html'. * * @param AbstractRevision $revision Revision to display content for * @param string[optional] $format Format to output content in (fixed-html|html|wikitext|plaintext) * @return string HTML if requested, otherwise plain text * @throws InvalidInputException */ public function getContent(AbstractRevision $revision, $format = 'fixed-html') { if (!in_array($format, array('fixed-html', 'html', 'plaintext', 'wikitext'))) { throw new InvalidInputException('Invalid format: ' . $format); } $allowed = $this->permissions->isAllowed($revision, 'view'); // Posts require view access to the topic title as well if ($allowed && $revision instanceof PostRevision && !$revision->isTopicTitle()) { $allowed = $this->permissions->isAllowed($revision->getRootPost(), 'view'); } if (!$allowed) { // failsafe - never output content if permissions aren't satisfied! return ''; } try { if ($format === 'fixed-html') { // Parsoid doesn't render redlinks & doesn't strip bad images $content = $this->contentFixer->getContent($revision); } else { // plaintext = wikitext $format = $format === 'plaintext' ? 'wikitext' : $format; $content = $revision->getContent($format); } } catch (\Exception $e) { wfDebugLog('Flow', __METHOD__ . ': Failed to get content for rev_id = ' . $revision->getRevisionId()->getAlphadecimal()); \MWExceptionHandler::logException($e); $content = wfMessage('flow-stub-post-content')->parse(); if (!in_array($format, array('html', 'fixed-html'))) { $content = strip_tags($content); } } return $content; }
/** * Returns HTML links to the page title and (if the action is topic-related) * the topic. * * @param array $data * @param FormatterRow $row * @param IContextSource $ctx * @return string HTML linking to topic & board */ protected function getTitleLink(array $data, FormatterRow $row, IContextSource $ctx) { $ownerLink = Linker::link($row->workflow->getOwnerTitle(), null, array('class' => 'mw-title')); if (!isset($data['links']['topic']) || !$row->revision instanceof PostRevision) { return $ownerLink; } /** @var Anchor $topic */ $topic = $data['links']['topic']; // generated link has generic link text, should be actual topic title $root = $row->revision->getRootPost(); if ($root && $this->permissions->isAllowed($root, 'view')) { $topic->setMessage(Container::get('templating')->getContent($root, 'wikitext')); } return $ctx->msg('flow-rc-topic-of-board')->rawParams($topic->toHtml(), $ownerLink)->escaped(); }
public function getUndoDiffResult($startUndoId, $endUndoId) { $start = $this->createRevision($startUndoId); if (!$start) { throw new InvalidInputException('Could not find revision: ' . $startUndoId, 'missing-revision'); } $end = $this->createRevision($endUndoId); if (!$end) { throw new InvalidInputException('Could not find revision: ' . $endUndoId, 'missing-revision'); } // the two revision must have the same revision type id if (!$start->getCollectionId()->equals($end->getCollectionId())) { throw new InvalidInputException('start and end are not from the same set'); } $current = $start->getCollection()->getLastRevision(); if (!$this->permissions->isAllowed($start, 'view') || !$this->permissions->isAllowed($end, 'view') || !$this->permissions->isAllowed($current, 'view')) { throw new PermissionException('Insufficient permission to undo revisions', 'insufficient-permission'); } $this->loadMetadataBatch(array($start, $end, $current)); return array($this->buildResult($start, null), $this->buildResult($end, null), $this->buildResult($current, null)); }
/** * @dataProvider permissionsProvider */ public function testPermissions(User $user, $permissionAction, $actions) { $permissions = new RevisionActionPermissions($this->actions, $user); // we'll have to process this in 2 steps: first do all of the actions, // so we have a full tree of moderated revisions $revision = null; $revisions = array(); $debug = array(); foreach ($actions as $action) { $expect = current($action); $action = key($action); $debug[] = $action . ':' . ($expect ? 'true' : 'false'); $revisions[] = $revision = $this->generateRevision($action, $revision); } // commit pending db transaction Container::get('db.factory')->getDB(DB_MASTER)->commit(__METHOD__, 'flush'); $debug = implode(' ', $debug); // secondly, iterate all revisions & see if expected permissions line up foreach ($actions as $action) { $expected = current($action); $revision = array_shift($revisions); $this->assertEquals($expected, $permissions->isAllowed($revision, $permissionAction), 'User ' . $user->getName() . ' should ' . ($expected ? '' : 'not ') . 'be allowed action ' . $permissionAction . ' on revision ' . key($action) . ' : ' . $debug . ' : ' . json_encode($revision::toStorageRow($revision))); } }
/** * Mimic Echo parameter formatting * * @param string $param The requested i18n parameter * @param AbstractRevision|AbstractRevision[] $revision The revision or * revisions to format or an array of revisions * @param UUID $workflowId The UUID of the workflow $revision belongs tow * @param IContextSource $ctx * @param FormatterRow|null $row * @return mixed A valid parameter for a core Message instance. These * parameters will be used with Message::parse * @throws FlowException */ public function processParam($param, $revision, UUID $workflowId, IContextSource $ctx, FormatterRow $row = null) { switch ($param) { case 'creator-text': if ($revision instanceof PostRevision) { return $this->usernames->getFromTuple($revision->getCreatorTuple()); } else { return ''; } case 'user-text': return $this->usernames->getFromTuple($revision->getUserTuple()); case 'user-links': return Message::rawParam($this->templating->getUserLinks($revision)); case 'summary': if (!$this->permissions->isAllowed($revision, 'view')) { return ''; } /* * Fetch in HTML; unparsed wikitext in summary is pointless. * Larger-scale wikis will likely also store content in html, so no * Parsoid roundtrip is needed then (and if it *is*, it'll already * be needed to render Flow discussions, so this is manageable) */ $content = $this->templating->getContent($revision, 'fixed-html'); // strip html tags and decode to plaintext $content = Utils::htmlToPlaintext($content, 140, $ctx->getLanguage()); return Message::plaintextParam($content); case 'wikitext': if (!$this->permissions->isAllowed($revision, 'view')) { return ''; } $content = $this->templating->getContent($revision, 'wikitext'); // This must be escaped and marked raw to prevent special chars in // content, like $1, from changing the i18n result return Message::plaintextParam($content); // This is potentially two networked round trips, much too expensive for // the rendering loop // This is potentially two networked round trips, much too expensive for // the rendering loop case 'prev-wikitext': if ($revision->isFirstRevision()) { return ''; } if ($row === null) { $previousRevision = $revision->getCollection()->getPrevRevision($revision); } else { $previousRevision = $row->previousRevision; } if (!$previousRevision) { return ''; } if (!$this->permissions->isAllowed($previousRevision, 'view')) { return ''; } $content = $this->templating->getContent($previousRevision, 'wikitext'); return Message::plaintextParam($content); case 'workflow-url': return $this->urlGenerator->workflowLink(null, $workflowId)->getFullUrl(); case 'post-url': if (!$revision instanceof PostRevision) { throw new FlowException('Expected PostRevision but received' . get_class($revision)); } return $this->urlGenerator->postLink(null, $workflowId, $revision->getPostId())->getFullUrl(); case 'moderated-reason': // don-t parse wikitext in the moderation reason return Message::plaintextParam($revision->getModeratedReason()); case 'topic-of-post': if (!$revision instanceof PostRevision) { throw new FlowException('Expected PostRevision but received ' . get_class($revision)); } $root = $revision->getRootPost(); if (!$this->permissions->isAllowed($root, 'view')) { return ''; } $content = $this->templating->getContent($root, 'wikitext'); return Message::plaintextParam($content); case 'post-of-summary': if (!$revision instanceof PostSummary) { throw new FlowException('Expected PostSummary but received ' . get_class($revision)); } /** @var PostRevision $post */ $post = $revision->getCollection()->getPost()->getLastRevision(); if (!$this->permissions->isAllowed($post, 'view')) { return ''; } if ($post->isTopicTitle()) { return Message::plaintextParam($this->templating->getContent($post, 'wikitext')); } else { return Message::rawParam($this->templating->getContent($post, 'fixed-html')); } case 'bundle-count': return Message::numParam(count($revision)); default: wfWarn(__METHOD__ . ': Unknown formatter parameter: ' . $param); return ''; } }
/** * Retrieves the post creators from a set of posts. * @param array $posts Array of UUIDs or hex representations * @return array Associative array, of user ID => User object. */ protected static function getCreatorsFromPostIDs(array $posts) { $users = array(); /** @var ManagerGroup $storage */ $storage = Container::get('storage'); $user = new User(); $actionPermissions = new RevisionActionPermissions(Container::get('flow_actions'), $user); foreach ($posts as $postId) { $post = $storage->find('PostRevision', array('rev_type_id' => UUID::create($postId)), array('sort' => 'rev_id', 'order' => 'DESC', 'limit' => 1)); $post = reset($post); if ($post && $actionPermissions->isAllowed($post, 'view')) { $userid = $post->getCreatorId(); if ($userid) { $users[$userid] = User::newFromId($userid); } } } return $users; }
/** * @dataProvider permissionsProvider */ public function testPermissions(User $user, PostRevision $revision = null, $action, $expected) { $permissions = new RevisionActionPermissions($this->actions, $user); $this->assertEquals($expected, $permissions->isRevisionAllowed($revision, $action)); }
/** * @var IContextSource $context * @var string $action */ public function init(IContextSource $context, $action) { $this->context = $context; $this->action = $action; $this->permissions = Container::get('permissions'); if (!$context->getUser()->equals($this->permissions->getUser())) { throw new PermissionException('Formatting for wrong user: '******' instead of ' . $this->permissions->getUser()->getName()); } }