protected function processParam($event, $param, $message, $user)
 {
     $extra = $event->getExtra();
     if ($param === 'subject') {
         if (isset($extra['topic-title']) && $extra['topic-title']) {
             $this->processParamEscaped($message, trim($extra['topic-title']));
         } else {
             $message->params('');
         }
     } elseif ($param === 'commentText') {
         if (isset($extra['content']) && $extra['content']) {
             // @todo assumes content is html, make explicit
             $message->params(Utils::htmlToPlaintext($extra['content'], 200));
         } else {
             $message->params('');
         }
     } elseif ($param === 'post-permalink') {
         $anchor = $this->getPostLinkAnchor($event, $user);
         if ($anchor) {
             $message->params($anchor->getFullUrl());
         } else {
             $message->params('');
         }
     } elseif ($param === 'topic-permalink') {
         // link to individual new-topic
         if (isset($extra['topic-workflow'])) {
             $title = Workflow::getFromTitleCache(wfWikiId(), NS_TOPIC, $extra['topic-workflow']->getAlphadecimal());
         } else {
             $title = $event->getTitle();
         }
         $anchor = $this->getUrlGenerator()->workflowLink($title, $extra['topic-workflow']);
         $anchor->query['fromnotif'] = 1;
         $message->params($anchor->getFullUrl());
     } elseif ($param === 'new-topics-permalink') {
         // link to board sorted by newest topics
         $anchor = $this->getUrlGenerator()->boardLink($event->getTitle(), 'newest');
         $anchor->query['fromnotif'] = 1;
         $message->params($anchor->getFullUrl());
     } elseif ($param == 'flow-title') {
         $title = $event->getTitle();
         if ($title) {
             $formatted = $this->formatTitle($title);
         } else {
             $formatted = $this->getMessage('echo-no-title')->text();
         }
         $message->params($formatted);
     } elseif ($param == 'old-subject') {
         $this->processParamEscaped($message, trim($extra['old-subject']));
     } elseif ($param == 'new-subject') {
         $this->processParamEscaped($message, trim($extra['new-subject']));
     } else {
         parent::processParam($event, $param, $message, $user);
     }
 }
 /**
  * @param FormatterRow $row With properties workflow, revision, previous_revision
  * @param IContextSource $ctx
  * @return string|false HTML for contributions entry, or false on failure
  * @throws FlowException
  */
 public function format(FormatterRow $row, IContextSource $ctx)
 {
     $this->serializer->setIncludeHistoryProperties(true);
     $data = $this->serializer->formatApi($row, $ctx, 'contributions');
     if (!$data) {
         return false;
     }
     $charDiff = ChangesList::showCharacterDifference($data['size']['old'], $data['size']['new']);
     $separator = $this->changeSeparator();
     $links = array();
     $links[] = $this->getDiffAnchor($data['links'], $ctx);
     $links[] = $this->getHistAnchor($data['links'], $ctx);
     $description = $this->formatDescription($data, $ctx);
     // Put it all together
     return $this->formatTimestamp($data) . ' ' . $this->formatAnchorsAsPipeList($links, $ctx) . $separator . $charDiff . $separator . $this->getTitleLink($data, $row, $ctx) . (Utils::htmlToPlaintext($description) ? $separator . $description : '') . $this->getHideUnhide($data, $row, $ctx);
 }
 /**
  * @param RecentChangesRow $row
  * @param IContextSource $ctx
  * @param bool $linkOnly
  * @return string|false Output line, or false on failure
  * @throws FlowException
  */
 public function format(RecentChangesRow $row, IContextSource $ctx, $linkOnly = false)
 {
     $this->serializer->setIncludeHistoryProperties(true);
     $this->serializer->setIncludeContent(false);
     $data = $this->serializer->formatApi($row, $ctx, 'recentchanges');
     if (!$data) {
         return false;
     }
     if ($linkOnly) {
         return $this->getTitleLink($data, $row, $ctx);
     }
     // The ' . . ' text between elements
     $separator = $this->changeSeparator();
     $links = array();
     $links[] = $this->getDiffAnchor($data['links'], $ctx);
     $links[] = $this->getHistAnchor($data['links'], $ctx);
     $description = $this->formatDescription($data, $ctx);
     $unpatrolledFlag = '';
     if (ChangesList::isUnpatrolled($row->recentChange, $ctx->getUser())) {
         $unpatrolledFlag = ChangesList::flag('unpatrolled') . ' ';
     }
     return $this->formatAnchorsAsPipeList($links, $ctx) . $separator . $unpatrolledFlag . $this->getTitleLink($data, $row, $ctx) . $ctx->msg('semicolon-separator')->escaped() . ' ' . $this->formatTimestamp($data, 'time') . $separator . ChangesList::showCharacterDifference($data['size']['old'], $data['size']['new'], $ctx) . (Utils::htmlToPlaintext($description) ? $separator . $description : '') . $this->getEditSummary($row, $ctx, $data);
 }
 /**
  * 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 '';
     }
 }
 /**
  * Called when a new Post is added, whether it be a new topic or a reply.
  * Do not call directly, use notifyPostChange for new replies.
  * @param  array $data Associative array of parameters, all required:
  * * title: Title for the page on which the new Post sits.
  * * user: User who created the new Post.
  * * post: The Post that was created.
  * * topic-title: The title for the Topic.
  * @return array Array of created EchoEvent objects.
  * @throws FlowException When $data contains unexpected types/values
  */
 protected function notifyNewPost($data)
 {
     // Handle mentions.
     $newRevision = $data['post'];
     if ($newRevision !== null && !$newRevision instanceof PostRevision) {
         throw new FlowException('Expected PostRevision but received ' . get_class($newRevision));
     }
     $topicRevision = $data['topic-title'];
     if (!$topicRevision instanceof PostRevision) {
         throw new FlowException('Expected PostRevision but received ' . get_class($topicRevision));
     }
     $title = $data['title'];
     if (!$title instanceof \Title) {
         throw new FlowException('Expected Title but received ' . get_class($title));
     }
     $user = $data['user'];
     $topicWorkflow = $data['topic-workflow'];
     if (!$topicWorkflow instanceof Workflow) {
         throw new FlowException('Expected Workflow but received ' . get_class($topicWorkflow));
     }
     $events = array();
     $mentionedUsers = $newRevision ? $this->getMentionedUsers($newRevision, $title) : array();
     if (!$topicRevision instanceof PostRevision) {
         throw new FlowException('Expected PostRevision but received: ' . get_class($topicRevision));
     }
     if (count($mentionedUsers)) {
         $events[] = EchoEvent::create(array('type' => 'flow-mention', 'title' => $title, 'extra' => array('content' => $newRevision ? Utils::htmlToPlaintext($newRevision->getContent(), 200, $this->language) : null, 'topic-title' => $this->language->truncate(trim($topicRevision->getContent('wikitext')), 200), 'post-id' => $newRevision ? $newRevision->getPostId() : null, 'mentioned-users' => $mentionedUsers, 'topic-workflow' => $topicWorkflow->getId(), 'target-page' => $topicWorkflow->getArticleTitle()->getArticleID(), 'reply-to' => isset($data['reply-to']) ? $data['reply-to'] : null), 'agent' => $user));
     }
     return $events;
 }
 /**
  * The native LogFormatter::getActionText provides no clean way of handling
  * the Flow action text in a plain text format (e.g. as used by CheckUser)
  *
  * @return string
  */
 public function getActionText()
 {
     $text = $this->getActionMessage();
     return $this->plaintext ? Utils::htmlToPlaintext($text) : $text;
 }