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());
     }
 }