Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 87
0.00% covered (danger)
0.00%
0 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
AbstractFormatter
0.00% covered (danger)
0.00%
0 / 87
0.00% covered (danger)
0.00%
0 / 12
1260
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getHistoryType
n/a
0 / 0
n/a
0 / 0
0
 formatTimestamp
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
30
 formatAnchorsAsPipeList
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
90
 getDiffAnchor
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getDiffPrevAnchor
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getDiffCurAnchor
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getHistAnchor
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 changeSeparator
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getDescription
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 getDescriptionParams
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 formatDescription
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getTitleLink
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
30
1<?php
2
3namespace Flow\Formatter;
4
5use Flow\Container;
6use Flow\FlowActions;
7use Flow\Model\Anchor;
8use Flow\Model\PostRevision;
9use Flow\RevisionActionPermissions;
10use MediaWiki\Context\IContextSource;
11use MediaWiki\Html\Html;
12use MediaWiki\MediaWikiServices;
13use MediaWiki\Message\Message;
14
15/**
16 * This is a "utility" class that might come in useful to generate
17 * some output per Flow entry, e.g. for RecentChanges, Contributions, ...
18 * These share a lot of common characteristics (like displaying a date, links to
19 * the posts, some description of the action, ...)
20 *
21 * Just extend from this class to use these common util methods, and make sure
22 * to pass the correct parameters to these methods. Basically, you'll need to
23 * create a new method that'll accept the objects for your specific
24 * implementation (like ChangesList & RecentChange objects for RecentChanges, or
25 * ContribsPager and a DB row for Contributions). From those rows, you should be
26 * able to derive the objects needed to pass to these utility functions (mainly
27 * Workflow, AbstractRevision, Title, User and Language objects) and return the
28 * output.
29 *
30 * For implementation examples, check Flow\RecentChanges\Formatter or
31 * Flow\Contributions\Formatter.
32 */
33abstract class AbstractFormatter {
34    /**
35     * @var RevisionActionPermissions
36     */
37    protected $permissions;
38
39    /**
40     * @var RevisionFormatter
41     */
42    protected $serializer;
43
44    public function __construct(
45        RevisionActionPermissions $permissions,
46        RevisionFormatter $serializer
47    ) {
48        $this->permissions = $permissions;
49        $this->serializer = $serializer;
50    }
51
52    abstract protected function getHistoryType();
53
54    /**
55     * @see RevisionFormatter::buildLinks
56     * @see RevisionFormatter::getDateFormats
57     *
58     * @param array $data Expects an array with keys 'dateFormats', 'isModeratedNotLocked'
59     *  and 'links'. The former should be an array having the key $key being
60     *  tossed in here; the latter an array of links in the [key => [href, msg]]
61     *  format, where 'key' corresponds with a $linksKeys value. The central is
62     *  a boolean.
63     * @param string $key Date format to use - any of the keys in the array
64     *  returned by RevisionFormatter::getDateFormats
65     * @param string[] $linkKeys Link key(s) to use as link for the timestamp;
66     *  the first available key will be used (but accepts an array of multiple
67     *  keys for when different kinds of data are tossed in, which may not all
68     *  have the same kind of links available)
69     * @return string HTML
70     * @return-taint escaped
71     */
72    protected function formatTimestamp(
73        array $data,
74        $key = 'timeAndDate',
75        array $linkKeys = [ 'header-revision', 'topic-revision', 'post-revision', 'summary-revision' ]
76    ) {
77        // Format timestamp: add link
78        $formattedTime = $data['dateFormats'][$key];
79
80        // Find the first available link to attach to the timestamp
81        $anchor = null;
82        foreach ( $linkKeys as $linkKey ) {
83            if ( isset( $data['links'][$linkKey] ) ) {
84                $anchor = $data['links'][$linkKey];
85                break;
86            }
87        }
88
89        $class = [ 'mw-changeslist-date' ];
90        if ( $data['isModeratedNotLocked'] ) {
91            $class[] = 'history-deleted';
92        }
93
94        if ( $anchor instanceof Anchor ) {
95            return Html::rawElement(
96                'span',
97                [ 'class' => $class ],
98                $anchor->toHtml( $formattedTime )
99            );
100        } else {
101            return Html::element( 'span', [ 'class' => $class ], $formattedTime );
102        }
103    }
104
105    /**
106     * Generate HTML for "(foo | bar | baz)"  based on the links provided by
107     * RevisionFormatter.
108     *
109     * @param (Anchor|Message)[] $links
110     * @param IContextSource $ctx
111     * @param string[]|null $request List of link names to be allowed in result output
112     * @return string Html valid for user output
113     */
114    protected function formatAnchorsAsPipeList(
115        array $links,
116        IContextSource $ctx,
117        ?array $request = null
118    ) {
119        if ( $request === null ) {
120            $request = array_keys( $links );
121        } elseif ( !$request ) {
122            // empty array was passed
123            return '';
124        }
125        $have = array_combine( $request, $request );
126
127        $formatted = [];
128        foreach ( $links as $key => $link ) {
129            if ( isset( $have[$key] ) ) {
130                if ( $link instanceof Anchor ) {
131                    $formatted[] = $link->toHtml();
132                } elseif ( $link instanceof Message ) {
133                    $formatted[] = $link->escaped();
134                }
135            }
136        }
137
138        if ( $formatted ) {
139            $content = $ctx->getLanguage()->pipeList( $formatted );
140            if ( $content ) {
141                return $ctx->msg( 'parentheses' )->rawParams( $content )->escaped();
142            }
143        }
144
145        return '';
146    }
147
148    /**
149     * Gets the "diff" link; linking to the diff against the previous revision,
150     * in a format that can be wrapped in an array and passed to
151     * formatLinksAsPipeList.
152     *
153     * @param Anchor[] $input
154     * @param IContextSource $ctx
155     * @return Anchor|Message
156     */
157    protected function getDiffAnchor( array $input, IContextSource $ctx ) {
158        if ( !isset( $input['diff'] ) ) {
159            // plain text with no link
160            return $ctx->msg( 'diff' );
161        }
162
163        return $input['diff'];
164    }
165
166    /**
167     * Gets the "prev" link; linking to the diff against the previous revision,
168     * in a format that can be wrapped in an array and passed to
169     * formatLinksAsPipeList.
170     *
171     * @param Anchor[] $input
172     * @param IContextSource $ctx
173     * @return Anchor|Message
174     */
175    protected function getDiffPrevAnchor( array $input, IContextSource $ctx ) {
176        if ( !isset( $input['diff-prev'] ) ) {
177            // plain text with no link
178            return $ctx->msg( 'last' );
179        }
180
181        return $input['diff-prev'];
182    }
183
184    /**
185     * Gets the "cur" link; linking to the diff against the current revision,
186     * in a format that can be wrapped in an array and passed to
187     * formatLinksAsPipeList.
188     *
189     * @param Anchor[] $input
190     * @param IContextSource $ctx
191     * @return Anchor|Message
192     */
193    protected function getDiffCurAnchor( array $input, IContextSource $ctx ) {
194        if ( !isset( $input['diff-cur'] ) ) {
195            // plain text with no link
196            return $ctx->msg( 'cur' );
197        }
198
199        return $input['diff-cur'];
200    }
201
202    /**
203     * Gets the "hist" link; linking to the history of a certain element, in a
204     * format that can be wrapped in an array and passed to
205     * formatLinksAsPipeList.
206     *
207     * @param Anchor[] $input
208     * @param IContextSource $ctx
209     * @return Anchor|Message
210     */
211    protected function getHistAnchor( array $input, IContextSource $ctx ) {
212        $anchor = $input['post-history'] ??
213            $input['topic-history'] ??
214            $input['board-history'] ?? null;
215
216        if ( $anchor instanceof Anchor ) {
217            $anchor->setMessage( $ctx->msg( 'hist' ) );
218            return $anchor;
219        } else {
220            // plain text with no link
221            return $ctx->msg( 'hist' );
222        }
223    }
224
225    /**
226     * @return string Html valid for user output
227     */
228    protected function changeSeparator() {
229        return ' <span class="mw-changeslist-separator">. .</span> ';
230    }
231
232    /**
233     * @param array $data
234     * @param IContextSource $ctx
235     * @return Message
236     */
237    protected function getDescription( array $data, IContextSource $ctx ) {
238        // Build description message, piggybacking on history i18n
239        $changeType = $data['changeType'];
240        $actions = $this->permissions->getActions();
241
242        $key = $actions->getValue( $changeType, 'history', 'i18n-message' );
243        // find specialized message for this particular formatter type
244        // E.g. the -irc messages.
245        $msg = $ctx->msg( $key . '-' . $this->getHistoryType() );
246        if ( !$msg->exists() ) {
247            // fallback to default msg
248            $msg = $ctx->msg( $key );
249        }
250
251        return $msg->params( ...$this->getDescriptionParams( $data, $actions, $changeType ) );
252    }
253
254    /**
255     * @param array $data
256     * @param FlowActions $actions
257     * @param string $changeType
258     * @return array
259     */
260    protected function getDescriptionParams( array $data, FlowActions $actions, $changeType ) {
261        $source = $actions->getValue( $changeType, 'history', 'i18n-params' );
262        $params = [];
263        foreach ( $source as $param ) {
264            if ( isset( $data['properties'][$param] ) ) {
265                $params[] = $data['properties'][$param];
266            } else {
267                wfDebugLog( 'Flow', __METHOD__ .
268                    ": Missing expected parameter $param for change type $changeType" );
269                $params[] = '';
270            }
271        }
272
273        return $params;
274    }
275
276    /**
277     * Generate an HTML revision description.
278     *
279     * @param array $data
280     * @param IContextSource $ctx
281     * @return string Html valid for user output
282     */
283    protected function formatDescription( array $data, IContextSource $ctx ) {
284        $msg = $this->getDescription( $data, $ctx );
285        return '<span class="plainlinks">' . $msg->parse() . '</span>';
286    }
287
288    /**
289     * Returns HTML links to the page title and (if the action is topic-related)
290     * the topic.
291     *
292     * @param array $data
293     * @param FormatterRow $row
294     * @param IContextSource $ctx
295     * @return string HTML linking to topic & board
296     */
297    protected function getTitleLink( array $data, FormatterRow $row, IContextSource $ctx ) {
298        $ownerLink = MediaWikiServices::getInstance()->getLinkRenderer()->makeLink(
299            $row->workflow->getOwnerTitle(),
300            null,
301            [ 'class' => 'mw-title' ]
302        );
303
304        if ( !isset( $data['links']['topic'] ) || !$row->revision instanceof PostRevision ) {
305            return $ownerLink;
306        }
307        /** @var Anchor $topic */
308        $topic = $data['links']['topic'];
309
310        // generated link has generic link text, should be actual topic title
311        // @phan-suppress-next-line PhanUndeclaredMethod $row->revision being PostRevision is not inferred
312        $root = $row->revision->getRootPost();
313        if ( $root && $this->permissions->isAllowed( $root, 'view' ) ) {
314            $topicDisplayText = Container::get( 'templating' )
315                ->getContent( $root, 'topic-title-plaintext' );
316            $topic->setMessage( $topicDisplayText );
317        }
318
319        return $ctx->msg( 'flow-rc-topic-of-board' )->rawParams(
320            $topic->toHtml(),
321            $ownerLink
322        )->escaped();
323    }
324}