Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 101
0.00% covered (danger)
0.00%
0 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
TopicListQuery
0.00% covered (danger)
0.00%
0 / 101
0.00% covered (danger)
0.00%
0 / 8
930
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 getResults
0.00% covered (danger)
0.00%
0 / 39
0.00% covered (danger)
0.00%
0 / 1
132
 getTopicIds
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 collectPostIds
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
20
 collectWatchStatus
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 collectSummary
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
20
 collectRevisions
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
12
 getCurrentRevision
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace Flow\Formatter;
4
5use Flow\Data\ManagerGroup;
6use Flow\Exception\FlowException;
7use Flow\Exception\InvalidDataException;
8use Flow\Model\AbstractRevision;
9use Flow\Model\PostRevision;
10use Flow\Model\PostSummary;
11use Flow\Model\TopicListEntry;
12use Flow\Model\UUID;
13use Flow\Repository\TreeRepository;
14use Flow\RevisionActionPermissions;
15use Flow\WatchedTopicItems;
16use FormatJson;
17
18class TopicListQuery extends AbstractQuery {
19
20    /** @var RevisionActionPermissions */
21    protected $permissions;
22    /** @var WatchedTopicItems */
23    protected $watchedTopicItems;
24
25    /**
26     * @param ManagerGroup $storage
27     * @param TreeRepository $treeRepository
28     * @param RevisionActionPermissions $permissions
29     * @param WatchedTopicItems $watchedTopicItems
30     */
31    public function __construct(
32        ManagerGroup $storage,
33        TreeRepository $treeRepository,
34        RevisionActionPermissions $permissions,
35        WatchedTopicItems $watchedTopicItems
36    ) {
37        parent::__construct( $storage, $treeRepository );
38        $this->permissions = $permissions;
39        $this->watchedTopicItems = $watchedTopicItems;
40    }
41
42    /**
43     * @param UUID[]|TopicListEntry[] $topicIdsOrEntries
44     * @return FormatterRow[]
45     * @suppress PhanUndeclaredMethod Types not inferred from instanceof
46     */
47    public function getResults( array $topicIdsOrEntries ) {
48        $topicIds = $this->getTopicIds( $topicIdsOrEntries );
49        $allPostIds = $this->collectPostIds( $topicIds );
50        $topicSummary = $this->collectSummary( $topicIds );
51        $posts = $this->collectRevisions( $allPostIds );
52        $watchStatus = $this->collectWatchStatus( $topicIds );
53
54        $missing = array_diff(
55            array_keys( $allPostIds ),
56            array_keys( $posts )
57        );
58        if ( $missing ) {
59            throw new InvalidDataException(
60                'Failed to load latest revision for post IDs: ' . FormatJson::encode( $missing ),
61                'fail-load-data'
62            );
63        }
64
65        $this->loadMetadataBatch( $posts );
66        $results = [];
67        $replies = [];
68        foreach ( $posts as $post ) {
69            try {
70                if ( !$this->permissions->isAllowed( $post, 'view' ) ) {
71                    continue;
72                }
73                $row = new TopicRow;
74                $this->buildResult( $post, null, $row );
75                /** @var PostRevision $revision */
76                $revision = $row->revision;
77                $replyToId = $revision->getReplyToId();
78                $replyToId = $replyToId ? $replyToId->getAlphadecimal() : null;
79                $postId = $revision->getPostId()->getAlphadecimal();
80                $replies[$replyToId] = $postId;
81                if ( $post->isTopicTitle() ) {
82                    // Attach the summary
83                    if ( isset( $topicSummary[$postId] ) ) {
84                        $row->summary = $this->buildResult( $topicSummary[$postId], 'rev_id' );
85                    }
86                    // Attach the watch status
87                    if ( isset( $watchStatus[$postId] ) && $watchStatus[$postId] ) {
88                        $row->isWatched = true;
89                    }
90                }
91                $results[] = $row;
92            } catch ( FlowException $e ) {
93                \MWExceptionHandler::logException( $e );
94            }
95        }
96
97        foreach ( $results as $result ) {
98            $alpha = $result->revision->getPostId()->getAlphadecimal();
99            $result->replies = $replies[$alpha] ?? [];
100        }
101
102        return $results;
103    }
104
105    /**
106     * @param TopicListEntry[]|UUID[] $topicsIdsOrEntries Topic IDs as UUID entries or
107     *  TopicListEntry objects
108     * @return UUID[]
109     */
110    protected function getTopicIds( array $topicsIdsOrEntries ) {
111        $topicIds = [];
112        foreach ( $topicsIdsOrEntries as $entry ) {
113            if ( $entry instanceof UUID ) {
114                $topicIds[] = $entry;
115            } elseif ( $entry instanceof TopicListEntry ) {
116                $topicIds[] = $entry->getId();
117            }
118        }
119        return $topicIds;
120    }
121
122    /**
123     * @param UUID[] $topicIds
124     * @return UUID[] Indexed by alphadecimal representation
125     */
126    protected function collectPostIds( array $topicIds ) {
127        if ( !$topicIds ) {
128            return [];
129        }
130        // Get the full list of postId's necessary
131        $nodeList = $this->treeRepository->fetchSubtreeNodeList( $topicIds );
132
133        // Merge all the children from the various posts into one array
134        if ( !$nodeList ) {
135            // It should have returned at least $topicIds
136            wfDebugLog( 'Flow', __METHOD__ .
137                ': No result received from TreeRepository::fetchSubtreeNodeList' );
138            $postIds = $topicIds;
139        } elseif ( count( $nodeList ) === 1 ) {
140            $postIds = reset( $nodeList );
141        } else {
142            $postIds = array_merge( ...array_values( $nodeList ) );
143        }
144
145        // re-index by alphadecimal id
146        return array_combine(
147            array_map(
148                static function ( UUID $x ) {
149                    return $x->getAlphadecimal();
150                },
151                $postIds
152            ),
153            $postIds
154        );
155    }
156
157    /**
158     * @param UUID[] $topicIds
159     * @return array
160     */
161    protected function collectWatchStatus( $topicIds ) {
162        $ids = [];
163        foreach ( $topicIds as $topicId ) {
164            $ids[] = $topicId->getAlphadecimal();
165        }
166        return $this->watchedTopicItems->getWatchStatus( $ids );
167    }
168
169    /**
170     * @param UUID[] $topicIds
171     * @return PostSummary[]
172     */
173    protected function collectSummary( $topicIds ) {
174        if ( !$topicIds ) {
175            return [];
176        }
177        $conds = [];
178        foreach ( $topicIds as $topicId ) {
179            $conds[] = [ 'rev_type_id' => $topicId ];
180        }
181        $found = $this->storage->findMulti( 'PostSummary', $conds, [
182            'sort' => 'rev_id',
183            'order' => 'DESC',
184            'limit' => 1,
185        ] );
186        $result = [];
187        foreach ( $found as $row ) {
188            $summary = reset( $row );
189            $result[$summary->getSummaryTargetId()->getAlphadecimal()] = $summary;
190        }
191        return $result;
192    }
193
194    /**
195     * @param UUID[] $postIds
196     * @return PostRevision[] Indexed by alphadecimal post id
197     */
198    protected function collectRevisions( array $postIds ) {
199        $queries = [];
200        foreach ( $postIds as $postId ) {
201            $queries[] = [ 'rev_type_id' => $postId ];
202        }
203        $found = $this->storage->findMulti( 'PostRevision', $queries, [
204            'sort' => 'rev_id',
205            'order' => 'DESC',
206            'limit' => 1,
207        ] );
208
209        // index results by post id for later filtering
210        $result = [];
211        foreach ( $found as $row ) {
212            $revision = reset( $row );
213            $result[$revision->getPostId()->getAlphadecimal()] = $revision;
214        }
215
216        return $result;
217    }
218
219    /**
220     * Override parent, we only load the most recent version, so just
221     * return self.
222     * @param AbstractRevision $revision
223     * @return AbstractRevision
224     */
225    protected function getCurrentRevision( AbstractRevision $revision ) {
226        return $revision;
227    }
228}