Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 93
0.00% covered (danger)
0.00%
0 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
EventsTablePager
0.00% covered (danger)
0.00%
0 / 93
0.00% covered (danger)
0.00%
0 / 11
812
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 formatValue
0.00% covered (danger)
0.00%
0 / 47
0.00% covered (danger)
0.00%
0 / 1
132
 getFieldNames
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 getIndexField
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getDefaultSort
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isFieldSortable
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getTableClass
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getCellAttrs
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 getModuleStyles
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 getModules
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSubqueryInfo
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
30
1<?php
2
3declare( strict_types=1 );
4
5namespace MediaWiki\Extension\CampaignEvents\Pager;
6
7use IContextSource;
8use LogicException;
9use MediaWiki\Cache\LinkBatchFactory;
10use MediaWiki\DAO\WikiAwareEntity;
11use MediaWiki\Extension\CampaignEvents\Database\CampaignsDatabaseHelper;
12use MediaWiki\Extension\CampaignEvents\Event\EventRegistration;
13use MediaWiki\Extension\CampaignEvents\Event\Store\EventStore;
14use MediaWiki\Extension\CampaignEvents\MWEntity\CampaignsPageFactory;
15use MediaWiki\Extension\CampaignEvents\MWEntity\CentralUser;
16use MediaWiki\Extension\CampaignEvents\MWEntity\PageURLResolver;
17use MediaWiki\Extension\CampaignEvents\Special\SpecialEditEventRegistration;
18use MediaWiki\Extension\CampaignEvents\Special\SpecialEventDetails;
19use MediaWiki\Linker\LinkRenderer;
20use MediaWiki\Pager\TablePager;
21use MediaWiki\SpecialPage\SpecialPage;
22use OOUI\ButtonWidget;
23
24/**
25 * This pager can be used to display a list of events organized by the given user, formatted as a table. In the future,
26 * this might be expanded to allow listing all events, not just those of a single user.
27 */
28class EventsTablePager extends TablePager {
29    use EventPagerTrait {
30        EventPagerTrait::getSubqueryInfo as getDefaultSubqueryInfo;
31    }
32
33    public const STATUS_ANY = 'any';
34    public const STATUS_OPEN = 'open';
35    public const STATUS_CLOSED = 'closed';
36
37    private const SORT_INDEXES = [
38        'event_start_utc' => [ 'event_start_utc', 'event_name', 'event_id' ],
39        'event_name' => [ 'event_name', 'event_start_utc', 'event_id' ],
40        'num_participants' => [ 'num_participants', 'event_start_utc', 'event_id' ],
41    ];
42
43    private CampaignsPageFactory $campaignsPageFactory;
44    private PageURLResolver $pageURLResolver;
45    private LinkBatchFactory $linkBatchFactory;
46
47    private CentralUser $centralUser;
48
49    private string $search;
50    private string $status;
51
52    /**
53     * @param IContextSource $context
54     * @param LinkRenderer $linkRenderer
55     * @param CampaignsDatabaseHelper $databaseHelper
56     * @param CampaignsPageFactory $campaignsPageFactory
57     * @param PageURLResolver $pageURLResolver
58     * @param LinkBatchFactory $linkBatchFactory
59     * @param string $search
60     * @param string $status One of the self::STATUS_* constants
61     * @param CentralUser $user
62     */
63    public function __construct(
64        IContextSource $context,
65        LinkRenderer $linkRenderer,
66        CampaignsDatabaseHelper $databaseHelper,
67        CampaignsPageFactory $campaignsPageFactory,
68        PageURLResolver $pageURLResolver,
69        LinkBatchFactory $linkBatchFactory,
70        string $search,
71        string $status,
72        CentralUser $user
73    ) {
74        // Set the database before calling the parent constructor, otherwise it'll use the local one.
75        $this->mDb = $databaseHelper->getDBConnection( DB_REPLICA );
76        parent::__construct( $context, $linkRenderer );
77        $this->campaignsPageFactory = $campaignsPageFactory;
78        $this->pageURLResolver = $pageURLResolver;
79        $this->linkBatchFactory = $linkBatchFactory;
80        $this->centralUser = $user;
81        $this->search = $search;
82        $this->status = $status;
83    }
84
85    /**
86     * @inheritDoc
87     */
88    public function formatValue( $name, $value ): string {
89        switch ( $name ) {
90            case 'event_start_utc':
91                return htmlspecialchars( $this->getLanguage()->userDate( $value, $this->getUser() ) );
92            case 'event_name':
93                return $this->getLinkRenderer()->makeKnownLink(
94                    SpecialPage::getTitleFor( SpecialEventDetails::PAGE_NAME, $this->mCurrentRow->event_id ),
95                    $value,
96                    [ 'class' => 'ext-campaignevents-eventspager-eventpage-link' ]
97                );
98            case 'event_location':
99                $meetingType = EventStore::getMeetingTypeFromDBVal( $this->mCurrentRow->event_meeting_type );
100                if ( $meetingType === EventRegistration::MEETING_TYPE_ONLINE ) {
101                    $msgKey = 'campaignevents-eventslist-location-online';
102                } elseif ( $meetingType === EventRegistration::MEETING_TYPE_IN_PERSON ) {
103                    $msgKey = 'campaignevents-eventslist-location-in-person';
104                } elseif ( $meetingType === EventRegistration::MEETING_TYPE_ONLINE_AND_IN_PERSON ) {
105                    $msgKey = 'campaignevents-eventslist-location-online-and-in-person';
106                } else {
107                    throw new LogicException( "Unexpected meeting type: $meetingType" );
108                }
109                return $this->msg( $msgKey )->escaped();
110            case 'num_participants':
111                return htmlspecialchars( $this->getLanguage()->formatNum( $value ) );
112            case 'manage_event':
113                $eventID = $this->mCurrentRow->event_id;
114                $btnLabel = $this->msg( 'campaignevents-eventslist-manage-btn-info' )->text();
115                // This will be replaced with a ButtonMenuSelectWidget in JS.
116                $btn = new ButtonWidget( [
117                    'framed' => false,
118                    'label' => $btnLabel,
119                    'title' => $btnLabel,
120                    'invisibleLabel' => true,
121                    'icon' => 'ellipsis',
122                    'href' => SpecialPage::getTitleFor(
123                        SpecialEditEventRegistration::PAGE_NAME,
124                        $eventID
125                    )->getLocalURL(),
126                    'classes' => [ 'ext-campaignevents-eventspager-manage-btn' ],
127                ] );
128                $eventStatus = EventStore::getEventStatusFromDBVal( $this->mCurrentRow->event_status );
129                $eventPage = $this->getEventPageFromRow( $this->mCurrentRow );
130                $btn->setAttributes( [
131                    'data-mw-event-id' => $eventID,
132                    'data-mw-event-name' => $this->mCurrentRow->event_name,
133                    'data-mw-is-closed' => $eventStatus === EventRegistration::STATUS_CLOSED ? 1 : 0,
134                    'data-mw-event-page-url' => $this->pageURLResolver->getUrl( $eventPage ),
135                    'data-mw-label' => $btnLabel,
136                    'data-mw-is-local-wiki' => $eventPage->getWikiId() === WikiAwareEntity::LOCAL,
137                ] );
138                return $btn->toString();
139            default:
140                throw new LogicException( "Unexpected name $name" );
141        }
142    }
143
144    /**
145     * @inheritDoc
146     */
147    protected function getFieldNames(): array {
148        return [
149            'event_start_utc' => $this->msg( 'campaignevents-eventslist-column-date' )->text(),
150            'event_name' => $this->msg( 'campaignevents-eventslist-column-name' )->text(),
151            'event_location' => $this->msg( 'campaignevents-eventslist-column-location' )->text(),
152            'num_participants' => $this->msg( 'campaignevents-eventslist-column-participants-number' )->text(),
153            'manage_event' => ''
154        ];
155    }
156
157    /**
158     * Overridden to provide additional columns to order by, since most columns are not unique.
159     * @inheritDoc
160     */
161    public function getIndexField(): array {
162        // XXX Work around T308697: TablePager and IndexPager seem to be incompatible and the correct
163        // index is not chosen automatically.
164        return [ self::SORT_INDEXES[$this->mSort] ];
165    }
166
167    /**
168     * @inheritDoc
169     */
170    public function getDefaultSort(): string {
171        return 'event_start_utc';
172    }
173
174    /**
175     * @inheritDoc
176     */
177    protected function isFieldSortable( $field ): bool {
178        return array_key_exists( $field, self::SORT_INDEXES );
179    }
180
181    /**
182     * @inheritDoc
183     */
184    protected function getTableClass() {
185        return parent::getTableClass() . ' ext-campaignevents-eventspager-table';
186    }
187
188    /**
189     * @inheritDoc
190     */
191    protected function getCellAttrs( $field, $value ) {
192        $ret = parent::getCellAttrs( $field, $value );
193        $addClass = null;
194        if ( $field === 'manage_event' ) {
195            $addClass = 'ext-campaignevents-eventspager-cell-manage';
196        }
197        if ( $addClass ) {
198            $ret['class'] = isset( $ret['class'] ) ? $ret['class'] . " $addClass" : $addClass;
199        }
200        return $ret;
201    }
202
203    /**
204     * @inheritDoc
205     */
206    public function getModuleStyles() {
207        return array_merge(
208            parent::getModuleStyles(),
209            [
210                // Avoid creating a new module for the pager only.
211                'ext.campaignEvents.specialeventslist.styles',
212                'oojs-ui.styles.icons-interactions'
213            ]
214        );
215    }
216
217    /**
218     * @return string[] An array of (non-style) RL modules.
219     */
220    public function getModules(): array {
221        // Avoid creating a new module for the pager only.
222        return [ 'ext.campaignEvents.specialeventslist' ];
223    }
224
225    /**
226     * @inheritDoc
227     */
228    public function getSubqueryInfo(): array {
229        $query = $this->getDefaultSubqueryInfo();
230        switch ( $this->status ) {
231            case self::STATUS_ANY:
232                break;
233            case self::STATUS_OPEN:
234                $query['conds']['event_status'] = EventStore::getEventStatusDBVal( EventRegistration::STATUS_OPEN );
235                break;
236            case self::STATUS_CLOSED:
237                $query['conds']['event_status'] = EventStore::getEventStatusDBVal( EventRegistration::STATUS_CLOSED );
238                break;
239            default:
240                // Invalid statuses can only be entered by messing with the HTML or query params, ignore.
241        }
242        // This should be abstracted at a later date.
243        // the current implementation ties presentation and data retrieval too closely
244        $query['join_conds']['ce_organizers'][1]['ceo_user_id'] = $this->centralUser->getCentralID();
245        return $query;
246    }
247}