Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 77
0.00% covered (danger)
0.00%
0 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
TopicListFormatter
0.00% covered (danger)
0.00%
0 / 77
0.00% covered (danger)
0.00%
0 / 8
420
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
 setContentFormat
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 buildEmptyResult
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 formatApi
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 buildResult
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 1
90
 buildApiActions
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 generateTopicMetadata
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
12
 buildLinks
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3namespace Flow\Formatter;
4
5use Flow\Data\Pager\PagerPage;
6use Flow\Model\UUID;
7use Flow\Model\Workflow;
8use Flow\UrlGenerator;
9use MediaWiki\Context\IContextSource;
10
11class TopicListFormatter extends BaseTopicListFormatter {
12    /**
13     * @var UrlGenerator
14     */
15    protected $urlGenerator;
16
17    /**
18     * @var RevisionFormatter
19     */
20    protected $serializer;
21
22    public function __construct( UrlGenerator $urlGenerator, RevisionFormatter $serializer ) {
23        $this->urlGenerator = $urlGenerator;
24        $this->serializer = $serializer;
25    }
26
27    public function setContentFormat( $contentFormat, ?UUID $revisionId = null ) {
28        $this->serializer->setContentFormat( $contentFormat, $revisionId );
29    }
30
31    public function buildEmptyResult( Workflow $workflow ) {
32        $title = $workflow->getArticleTitle();
33        return [
34            'workflowId' => $workflow->getId()->getAlphadecimal(),
35            'title' => $title->getPrefixedText(),
36            'actions' => $this->buildApiActions( $workflow ),
37            'links' => $this->buildLinks( $workflow ),
38        ] + parent::buildEmptyResult( $workflow );
39    }
40
41    public function formatApi(
42        Workflow $listWorkflow,
43        array $workflows,
44        array $found,
45        PagerPage $page,
46        IContextSource $ctx
47    ) {
48        $res = $this->buildResult( $workflows, $found, $ctx ) +
49            $this->buildEmptyResult( $listWorkflow );
50        $pagingOption = $page->getPagingLinksOptions();
51        $res['links']['pagination'] = $this->buildPaginationLinks(
52            $listWorkflow,
53            $pagingOption
54        );
55
56        return $res;
57    }
58
59    /**
60     * Method is called from static::formatApi & ApiFlowSearch::formatApi, to ensure
61     * both have similar output.
62     *
63     * @param Workflow[] $workflows
64     * @param FormatterRow[] $found
65     * @param IContextSource $ctx
66     * @return array
67     */
68    public function buildResult( array $workflows, array $found, IContextSource $ctx ) {
69        $revisions = $posts = $replies = [];
70        foreach ( $found as $formatterRow ) {
71            $serialized = $this->serializer->formatApi( $formatterRow, $ctx );
72            if ( !$serialized ) {
73                continue;
74            }
75            $revisions[$serialized['revisionId']] = $serialized;
76            $posts[$serialized['postId']][] = $serialized['revisionId'];
77            $replies[$serialized['replyToId']][] = $serialized['postId'];
78        }
79
80        foreach ( $revisions as $i => $serialized ) {
81            $alpha = $serialized['postId'];
82            $revisions[$i]['replies'] = $replies[$alpha] ?? [];
83        }
84
85        $list = [];
86        if ( $workflows ) {
87            $orig = $workflows;
88            $workflows = [];
89            foreach ( $orig as $workflow ) {
90                $alpha = $workflow->getId()->getAlphadecimal();
91                if ( isset( $posts[$alpha] ) ) {
92                    $list[] = $alpha;
93                    $workflows[$alpha] = $workflow;
94                } else {
95                    wfDebugLog( 'Flow', __METHOD__ . ": No matching root post for workflow $alpha" );
96                }
97            }
98
99            foreach ( $list as $alpha ) {
100                // Metadata that requires everything to be serialied first
101                $metadata = $this->generateTopicMetadata( $posts, $revisions, $workflows, $alpha, $ctx );
102                foreach ( $posts[$alpha] as $revId ) {
103                    $revisions[$revId] += $metadata;
104                }
105            }
106        }
107
108        return [
109            // array_values must be used to ensure 0-indexed array
110            'roots' => $list,
111            'posts' => $posts,
112            'revisions' => $revisions,
113        ];
114    }
115
116    /**
117     * @param Workflow $workflow
118     * @return array
119     */
120    protected function buildApiActions( Workflow $workflow ) {
121        $actions = [];
122
123        if ( !$workflow->isDeleted() ) {
124            $actions['newtopic'] = $this->urlGenerator->newTopicAction( $workflow->getArticleTitle() );
125        }
126
127        return $actions;
128    }
129
130    protected function generateTopicMetadata( array $posts, array $revisions, array $workflows, $postAlphaId, IContextSource $ctx ) {
131        $language = $ctx->getLanguage();
132        $user = $ctx->getUser();
133
134        $replies = -1;
135        $stack = new \SplStack;
136        $stack->push( $revisions[$posts[$postAlphaId][0]] );
137        do {
138            $data = $stack->pop();
139            $replies++;
140            foreach ( $data['replies'] as $postId ) {
141                $stack->push( $revisions[$posts[$postId][0]] );
142            }
143        } while ( !$stack->isEmpty() );
144
145        /** @var Workflow|null $workflow */
146        $workflow = $workflows[$postAlphaId] ?? null;
147        $ts = $workflow ? $workflow->getLastUpdatedObj()->getTimestamp() : 0;
148        return [
149            'reply_count' => $replies,
150            'last_updated_readable' => $language->userTimeAndDate( $ts, $user ),
151            // ms timestamp
152            'last_updated' => $ts * 1000,
153        ];
154    }
155
156    /**
157     * @param Workflow $workflow
158     * @return array
159     */
160    private function buildLinks( Workflow $workflow ) {
161        $links = [];
162
163        if ( !$workflow->isDeleted() ) {
164            $title = $workflow->getArticleTitle();
165            $saveSortBy = true;
166            $links['board-sort']['updated'] = $this->urlGenerator->boardLink( $title, 'updated', $saveSortBy )->getLinkURL();
167            $links['board-sort']['newest'] = $this->urlGenerator->boardLink( $title, 'newest', $saveSortBy )->getLinkURL();
168
169            // Link to designated new-topic page, for no-JS users
170            $links['newtopic'] = $this->urlGenerator->newTopicAction( $title, $workflow->getId() )->getLinkURL();
171        }
172
173        return $links;
174    }
175}