Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 100
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
EventPagerTrait
0.00% covered (danger)
0.00%
0 / 100
0.00% covered (danger)
0.00%
0 / 4
90
0.00% covered (danger)
0.00%
0 / 1
 getSubqueryInfo
0.00% covered (danger)
0.00%
0 / 51
0.00% covered (danger)
0.00%
0 / 1
2
 getQueryInfo
0.00% covered (danger)
0.00%
0 / 27
0.00% covered (danger)
0.00%
0 / 1
6
 preprocessResults
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
20
 getEventPageFromRow
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2declare( strict_types=1 );
3
4namespace MediaWiki\Extension\CampaignEvents\Pager;
5
6use MediaWiki\Cache\LinkBatchFactory;
7use MediaWiki\Extension\CampaignEvents\MWEntity\CampaignsPageFactory;
8use MediaWiki\Extension\CampaignEvents\MWEntity\ICampaignsPage;
9use MediaWiki\WikiMap\WikiMap;
10use stdClass;
11use Wikimedia\Rdbms\IExpression;
12use Wikimedia\Rdbms\IReadableDatabase;
13use Wikimedia\Rdbms\IResultWrapper;
14use Wikimedia\Rdbms\LikeValue;
15use Wikimedia\Rdbms\Subquery;
16
17/**
18 * @property string $search;
19 * @property string $status;
20 * @property IReadableDatabase $mDb;
21 * @property LinkBatchFactory $linkBatchFactory;
22 * @property CampaignsPageFactory $campaignsPageFactory;
23 */
24trait EventPagerTrait {
25    /** @var array<int,ICampaignsPage> Cache of event page objects, keyed by event ID */
26    private array $eventPageCache = [];
27
28    /**
29     * @todo The joins and grouping below are not used by EventsListPager (which wouldn't even need a subquery), and
30     * they just slow the query down. We should either implement those features in the list pager, or move the
31     * complexity to EventsTablePager.
32     * @return array
33     */
34    public function getSubqueryInfo(): array {
35        $options = [
36            'GROUP BY' => [
37                'cep_event_id',
38                'event_id',
39                'event_name',
40                'event_page_namespace',
41                'event_page_title',
42                'event_page_prefixedtext',
43                'event_page_wiki',
44                'event_status',
45                'event_start_utc',
46                'event_end_utc',
47                'event_meeting_type'
48            ] ];
49        $join_conds = [
50            'ce_participants' => [
51                'LEFT JOIN',
52                [
53                    'event_id=cep_event_id',
54                    'cep_unregistered_at' => null,
55                ]
56            ],
57            'ce_organizers' => [
58                'JOIN',
59                [
60                    'event_id=ceo_event_id',
61                    'ceo_deleted_at' => null,
62                ]
63            ]
64        ];
65        return [
66            'tables' => [ 'campaign_events', 'ce_participants', 'ce_organizers' ],
67            'fields' => [
68                'event_id',
69                'event_name',
70                'event_page_namespace',
71                'event_page_title',
72                'event_page_prefixedtext',
73                'event_page_wiki',
74                'event_status',
75                'event_start_utc',
76                'event_end_utc',
77                'event_meeting_type',
78                'num_participants' => 'COUNT(cep_id)'
79            ],
80            'conds' => [
81                    'event_deleted_at' => null,
82            ],
83            'options' => $options,
84            'join_conds' => $join_conds
85        ];
86    }
87
88    /**
89     * @inheritDoc
90     */
91    public function getQueryInfo() {
92        // Use a subquery and a temporary table to work around IndexPager not using HAVING for aggregates (T308694)
93        // and to support postgres (which doesn't allow aliases in HAVING).
94        $subqueryInfo = $this->getSubqueryInfo();
95        $subquery = $this->mDb->newSelectQueryBuilder()
96            ->queryInfo( $subqueryInfo )
97            ->caller( __METHOD__ );
98        $conds = [];
99        if ( $this->search !== '' ) {
100            // TODO Make this case-insensitive. Not easy right now because the name is a binary string and the DBAL does
101            // not provide a method for converting it to a non-binary value on which LOWER can be applied.
102            $conds[] = $this->mDb->expr( 'event_name', IExpression::LIKE,
103                new LikeValue( $this->mDb->anyString(), $this->search, $this->mDb->anyString() ) );
104        }
105
106        return [
107            'tables' => [ 'tmp' => new Subquery( $subquery->getSQL() ) ],
108            'fields' => [
109                'event_id',
110                'event_name',
111                'event_page_namespace',
112                'event_page_title',
113                'event_page_prefixedtext',
114                'event_page_wiki',
115                'event_status',
116                'event_start_utc',
117                'event_end_utc',
118                'event_meeting_type',
119                'num_participants'
120            ],
121            'conds' => $conds,
122            'options' => [],
123            'join_conds' => []
124        ];
125    }
126
127    /**
128     * Add event pages to a LinkBatch to improve performance and not make one query per page.
129     * This code was stolen from AbuseFilter's pager et al.
130     * @param IResultWrapper $result
131     */
132    protected function preprocessResults( $result ): void {
133        // Error suppressed, method is declared in inheritor
134        // @phan-suppress-next-line PhanUndeclaredMethod
135        if ( $this->getNumRows() === 0 ) {
136            return;
137        }
138        $linkBatchFactory = $this->linkBatchFactory;
139        $lb = $linkBatchFactory->newLinkBatch();
140        $lb->setCaller( __METHOD__ );
141        $curWikiID = WikiMap::getCurrentWikiId();
142        foreach ( $result as $row ) {
143            // XXX LinkCache only supports local pages, and it's not used in foreign instances of PageStore.
144            if ( $row->event_page_wiki === $curWikiID ) {
145                $lb->add( (int)$row->event_page_namespace, $row->event_page_title );
146            }
147        }
148        $lb->execute();
149        $result->seek( 0 );
150    }
151
152    /**
153     * @param stdClass $eventRow
154     * @return ICampaignsPage
155     */
156    private function getEventPageFromRow( stdClass $eventRow ): ICampaignsPage {
157        $eventID = $eventRow->event_id;
158        $eventPageCache = $this->eventPageCache;
159        $campaignsPageFactory = $this->campaignsPageFactory;
160        if ( !isset( $eventPageCache[$eventID] ) ) {
161            $eventPageCache[$eventID] = $campaignsPageFactory->newPageFromDB(
162                (int)$eventRow->event_page_namespace,
163                $eventRow->event_page_title,
164                $eventRow->event_page_prefixedtext,
165                $eventRow->event_page_wiki
166            );
167        }
168        return $eventPageCache[$eventID];
169    }
170}