Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 158
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecialNotifications
0.00% covered (danger)
0.00%
0 / 158
0.00% covered (danger)
0.00%
0 / 4
306
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 143
0.00% covered (danger)
0.00%
0 / 1
210
 buildSubtitle
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
2
 getGroupName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace MediaWiki\Extension\Notifications\Special;
4
5use MediaWiki\Extension\Notifications\DataOutputFormatter;
6use MediaWiki\Extension\Notifications\NotifUser;
7use MediaWiki\Extension\Notifications\OOUI\LabelIconWidget;
8use MediaWiki\Extension\Notifications\SeenTime;
9use MediaWiki\Html\Html;
10use MediaWiki\SpecialPage\SpecialPage;
11use OOUI;
12
13class SpecialNotifications extends SpecialPage {
14
15    /**
16     * Number of notification records to display per page/load
17     */
18    private const DISPLAY_NUM = 20;
19
20    public function __construct() {
21        parent::__construct( 'Notifications' );
22    }
23
24    /**
25     * @param string|null $par
26     */
27    public function execute( $par ) {
28        $this->setHeaders();
29
30        $out = $this->getOutput();
31        $out->setPageTitleMsg( $this->msg( 'echo-specialpage' ) );
32
33        $this->addHelpLink( 'Help:Notifications/Special:Notifications' );
34
35        $out->addJsConfigVars( 'wgNotificationsSpecialPageLinks', [
36            'preferences' => SpecialPage::getTitleFor( 'Preferences', false, 'mw-prefsection-echo' )->getLinkURL(),
37        ] );
38
39        $user = $this->getUser();
40        if ( !$user->isRegistered() ) {
41            // Redirect to login page and inform user of the need to login
42            $this->requireLogin( 'echo-notification-loginrequired' );
43            return;
44        }
45
46        $out->addSubtitle( $this->buildSubtitle() );
47
48        $out->enableOOUI();
49
50        $pager = new NotificationPager( $this->getContext() );
51        $pager->setOffset( $this->getRequest()->getVal( 'offset' ) );
52        $pager->setLimit( $this->getRequest()->getInt( 'limit', self::DISPLAY_NUM ) );
53        $notifications = $pager->getNotifications();
54
55        $noJSDiv = new OOUI\Tag();
56        $noJSDiv->addClasses( [ 'mw-echo-special-nojs' ] );
57
58        // If there are no notifications, display a message saying so
59        if ( !$notifications ) {
60            // Wrap this with nojs so it is still hidden if JS is loading
61            $noJSDiv->appendContent(
62                new OOUI\LabelWidget( [ 'label' => $this->msg( 'echo-none' )->text() ] )
63            );
64            $out->addHTML( $noJSDiv );
65            $out->addModules( [ 'ext.echo.special' ] );
66            return;
67        }
68
69        $notif = [];
70        foreach ( $notifications as $notification ) {
71            $output = DataOutputFormatter::formatOutput( $notification, 'special', $user, $this->getLanguage() );
72            if ( $output ) {
73                $notif[] = $output;
74            }
75        }
76
77        // Add the notifications to the page (interspersed with date headers)
78        $dateHeader = '';
79        $anyUnread = false;
80        $seenTime = SeenTime::newFromUser( $user )->getTime();
81        $notifArray = [];
82        foreach ( $notif as $row ) {
83            if ( !$row['*'] ) {
84                continue;
85            }
86
87            $classes = [ 'mw-echo-notification' ];
88
89            if ( $seenTime !== null && $row['timestamp']['mw'] > $seenTime ) {
90                $classes[] = 'mw-echo-notification-unseen';
91            }
92
93            // Output the date header if it has not been displayed
94            if ( $dateHeader !== $row['timestamp']['date'] ) {
95                $dateHeader = $row['timestamp']['date'];
96            }
97            // Collect unread IDs
98            if ( !isset( $row['read'] ) ) {
99                $classes[] = 'mw-echo-notification-unread';
100                $anyUnread = true;
101                $notifArray[ $dateHeader ][ 'unread' ][] = $row['id'];
102            }
103
104            $li = new OOUI\Tag( 'li' );
105            $li
106                ->addClasses( $classes )
107                ->setAttributes( [
108                    'data-notification-category' => $row['category'],
109                    'data-notification-event' => $row['id'],
110                    'data-notification-type' => $row['type']
111                ] )
112                ->appendContent( new OOUI\HtmlSnippet( $row['*'] ) );
113
114            // Store
115            $notifArray[ $dateHeader ][ 'notices' ][] = $li;
116        }
117
118        $markAllAsReadFormWrapper = '';
119        // Ensure there are some unread notifications
120        if ( $anyUnread ) {
121            $markReadSpecialPage = new SpecialNotificationsMarkRead();
122            $markReadSpecialPage->setContext( $this->getContext() );
123            $notifUser = NotifUser::newFromUser( $user );
124            $unreadCount = $notifUser->getAlertCount() + $notifUser->getMessageCount();
125
126            $markAllAsReadText = $this
127                ->msg( 'echo-mark-all-as-read' )
128                ->numParams( $unreadCount )
129                ->text();
130            $markAllAsReadLabelIcon = new LabelIconWidget( [
131                'label' => $markAllAsReadText,
132                'icon' => 'checkAll',
133            ] );
134
135            $markAllAsReadForm = $markReadSpecialPage->getMinimalForm(
136                [ 'ALL' ],
137                $markAllAsReadText,
138                true,
139                $markAllAsReadLabelIcon->toString()
140            );
141
142            // First submission attempt
143            $formHtml = $markAllAsReadForm->prepareForm()->getHTML( false );
144
145            $markAllAsReadFormWrapper = new OOUI\Tag();
146            $markAllAsReadFormWrapper
147                ->addClasses( [ 'mw-echo-special-markAllReadButton' ] )
148                ->appendContent( new OOUI\HtmlSnippet( $formHtml ) );
149        }
150
151        // Build the list
152        $notices = new OOUI\Tag( 'ul' );
153        $notices->addClasses( [ 'mw-echo-special-notifications' ] );
154
155        $markReadSpecialPage = new SpecialNotificationsMarkRead();
156        $markReadSpecialPage->setContext( $this->getContext() );
157        foreach ( $notifArray as $section => $data ) {
158            $heading = ( new OOUI\Tag( 'li' ) )->addClasses( [ 'mw-echo-date-section' ] );
159
160            $dateTitle = new OOUI\LabelWidget( [
161                'classes' => [ 'mw-echo-date-section-text' ],
162                'label' => $section
163            ] );
164
165            $heading->appendContent( $dateTitle );
166
167            // Mark all read button
168            if ( isset( $data[ 'unread' ] ) ) {
169                // tell the UI to show 'unread' notifications only (instead of 'all')
170                $out->addJsConfigVars( 'wgEchoReadState', 'unread' );
171
172                $markReadSectionText = $this->msg( 'echo-specialpage-section-markread' )->text();
173                $markAsReadLabelIcon = new LabelIconWidget( [
174                    'label' => $markReadSectionText,
175                    'icon' => 'checkAll',
176                ] );
177
178                // There are unread notices. Add the 'mark section as read' button
179                $markSectionAsReadForm = $markReadSpecialPage->getMinimalForm(
180                    $data[ 'unread' ],
181                    $markReadSectionText,
182                    true,
183                    $markAsReadLabelIcon->toString()
184                );
185
186                // First submission attempt
187                $formHtml = $markSectionAsReadForm->prepareForm()->getHTML( false );
188
189                $formWrapper = new OOUI\Tag();
190                $formWrapper
191                    ->addClasses( [ 'mw-echo-markAsReadSectionButton' ] )
192                    ->appendContent( new OOUI\HtmlSnippet( $formHtml ) );
193
194                $heading->appendContent( $formWrapper );
195            }
196
197            // These two must be separate, because $data[ 'notices' ]
198            // is an array
199            $notices
200                ->appendContent( $heading )
201                // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset https://github.com/phan/phan/issues/4735
202                ->appendContent( $data[ 'notices' ] );
203        }
204
205        $navBar = $pager->getNavigationBar();
206
207        $navTop = new OOUI\Tag();
208        $navBottom = new OOUI\Tag();
209        $container = new OOUI\Tag();
210
211        $navTop
212            ->addClasses( [ 'mw-echo-special-navbar-top' ] )
213            ->appendContent( new OOUI\HtmlSnippet( $navBar ) );
214        $navBottom
215            ->addClasses( [ 'mw-echo-special-navbar-bottom' ] )
216            ->appendContent( new OOUI\HtmlSnippet( $navBar ) );
217
218        // Put it all together
219        $container
220            ->addClasses( [ 'mw-echo-special-container' ] )
221            ->appendContent(
222                $navTop,
223                $markAllAsReadFormWrapper,
224                $notices,
225                $navBottom
226            );
227
228        // Wrap with nojs div
229        $noJSDiv->appendContent( $container );
230
231        $out->addHTML( $noJSDiv );
232
233        $out->addModules( [ 'ext.echo.special' ] );
234
235        // For no-js support
236        $out->addModuleStyles( [
237            'ext.echo.styles.notifications',
238            'ext.echo.styles.special',
239            'oojs-ui.styles.icons-alerts',
240            'oojs-ui.styles.icons-interactions',
241        ] );
242    }
243
244    /**
245     * Build the subtitle (more info and preference links)
246     * @return string HTML for the subtitle
247     */
248    public function buildSubtitle() {
249        $lang = $this->getLanguage();
250        $subtitleLinks = [];
251        // Preferences link
252        $subtitleLinks[] = Html::element(
253            'a',
254            [
255                'href' => SpecialPage::getTitleFor( 'Preferences', false, 'mw-prefsection-echo' )->getLinkURL(),
256                'id' => 'mw-echo-pref-link',
257                'class' => 'mw-echo-special-header-link',
258                'title' => $this->msg( 'preferences' )->text()
259            ],
260            $this->msg( 'preferences' )->text()
261        );
262        // using pipeList to make it easier to add some links in the future
263        return $lang->pipeList( $subtitleLinks );
264    }
265
266    protected function getGroupName() {
267        return 'login';
268    }
269}