Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 128
0.00% covered (danger)
0.00%
0 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecialMobileEditWatchlist
0.00% covered (danger)
0.00%
0 / 128
0.00% covered (danger)
0.00%
0 / 11
600
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 outputSubtitle
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getLineHtml
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 1
12
 execute
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 getPageOffset
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 getPagesToDisplay
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getEmptyListHtml
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
12
 getNextPage
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 executeViewEditWatchlist
0.00% covered (danger)
0.00%
0 / 46
0.00% covered (danger)
0.00%
0 / 1
42
 getViewHtml
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 getAssociatedNavigationLinks
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3use MediaWiki\Html\Html;
4use MediaWiki\Language\Language;
5use MediaWiki\MediaWikiServices;
6use MediaWiki\SpecialPage\SpecialPage;
7use MediaWiki\Specials\SpecialEditWatchlist;
8use MediaWiki\Title\Title;
9use MobileFrontend\Hooks\HookRunner;
10use MobileFrontend\Models\MobileCollection;
11use MobileFrontend\Models\MobilePage;
12
13/**
14 * The mobile version of the watchlist editing page.
15 * @deprecated in future this should be the core SpecialEditWatchlist page (T109277)
16 */
17class SpecialMobileEditWatchlist extends SpecialEditWatchlist {
18    private const WATCHLIST_TAB_PATHS = [
19        'Special:Watchlist',
20        'Special:EditWatchlist'
21    ];
22    private const LIMIT = 50;
23
24    /** @var string The name of the title to begin listing the watchlist from */
25    protected $offsetTitle;
26
27    public function __construct() {
28        $req = $this->getRequest();
29        $this->offsetTitle = $req->getVal( 'from', '' );
30        $watchStoreItem = MediaWikiServices::getInstance()->getWatchedItemStore();
31        parent::__construct( $watchStoreItem );
32    }
33
34    /**
35     * Renders the subheader.
36     */
37    protected function outputSubtitle() {
38        $user = $this->getUser();
39    }
40
41    /**
42     * Gets the HTML fragment for a watched page. The client uses a very different
43     * structure for client-rendered items in PageListItem.hogan.
44     *
45     * @param MobilePage $mp a definition of the page to be rendered.
46     * @return string
47     */
48    protected function getLineHtml( MobilePage $mp ) {
49        $thumb = $mp->getSmallThumbnailHtml( true );
50        $title = $mp->getTitle();
51        if ( !$thumb ) {
52            $thumb = Html::rawElement( 'div', [
53                'class' => 'list-thumb list-thumb-placeholder'
54                ], Html::element( 'span', [
55                    'class' => 'mf-icon-image'
56                ] )
57            );
58        }
59        $timestamp = $mp->getLatestTimestamp();
60        $user = $this->getUser();
61        $titleText = $title->getPrefixedText();
62        if ( $timestamp ) {
63            $className = 'title';
64        } else {
65            $className = 'title new';
66        }
67
68        $html =
69            Html::openElement( 'li', [
70                'class' => 'page-summary',
71                'title' => $titleText,
72                'data-id' => $title->getArticleID()
73            ] ) .
74            Html::openElement( 'a', [ 'href' => $title->getLocalURL(), 'class' => $className ] );
75        $html .= $thumb;
76        $html .=
77            Html::element( 'h3', [], $titleText );
78
79        $html .= Html::closeElement( 'a' ) .
80            Html::closeElement( 'li' );
81
82        return $html;
83    }
84
85    /**
86     * The main entry point for the page.
87     *
88     * @param string|null $mode Whether the user is viewing, editing, or clearing their
89     *  watchlist
90     */
91    public function execute( $mode ) {
92        // Anons don't get a watchlist edit
93        $this->requireLogin( 'mobile-frontend-watchlist-purpose' );
94
95        $out = $this->getOutput();
96        $out->addBodyClasses( 'mw-mf-special-page' );
97        parent::execute( $mode );
98        $out->setPageTitleMsg( $this->msg( 'watchlist' ) );
99    }
100
101    /**
102     * Finds the offset of the page given in this->offsetTitle
103     * If doesn't exist returns 0 to show from beginning of array of pages.
104     *
105     * @param array $pages
106     * @return int where the index of the next page to be shown.
107     */
108    private function getPageOffset( $pages ) {
109        // Deal with messiness of mediawiki
110        $pages = array_keys( $pages );
111
112        if ( $this->offsetTitle ) {
113            // PHP is stupid. strict test to avoid issues when page '0' is watched.
114            $offset = array_search( $this->offsetTitle, $pages, true );
115            // Deal with cases where invalid title given
116            if ( $offset === false ) {
117                $offset = 0;
118            }
119        } else {
120            $offset = 0;
121        }
122        return $offset;
123    }
124
125    /**
126     * Create paginated view of entire watchlist
127     *
128     * @param array $pages
129     * @return array of pages that should be displayed in current view
130     */
131    private function getPagesToDisplay( $pages ) {
132        $offset = $this->getPageOffset( $pages );
133        // Get the slice we are going to display and display it
134        return array_slice( $pages, $offset, self::LIMIT, true );
135    }
136
137    /**
138     * Get the HTML needed to show if a user doesn't watch any page, show information
139     * how to watch pages where no pages have been watched.
140     * @param bool $feed Render as feed (true) or list (false) view?
141     * @param Language $lang The language of the current mode
142     * @return string
143     */
144    public static function getEmptyListHtml( $feed, $lang ) {
145        $dir = $lang->isRTL() ? 'rtl' : 'ltr';
146
147        $config = MediaWikiServices::getInstance()->getService( 'MobileFrontend.Config' );
148        $imgUrl = $config->get( 'ExtensionAssetsPath' ) .
149            "/MobileFrontend/images/emptywatchlist-page-actions-$dir.png";
150
151        if ( $feed ) {
152            $msg = Html::element( 'p', [], wfMessage( 'mobile-frontend-watchlist-feed-empty' )->plain() );
153        } else {
154            $msg = Html::element( 'p', [],
155                wfMessage( 'mobile-frontend-watchlist-a-z-empty-howto' )->plain()
156            );
157            $msg .= Html::element( 'img', [
158                'src' => $imgUrl,
159                'alt' => wfMessage( 'mobile-frontend-watchlist-a-z-empty-howto-alt' )->plain(),
160            ] );
161        }
162
163        return Html::openElement( 'div', [ 'class' => 'info empty-page' ] ) .
164            $msg .
165            Html::element( 'a',
166                [ 'class' => 'button', 'href' => Title::newMainPage()->getLocalURL() ],
167                wfMessage( 'mobile-frontend-watchlist-back-home' )->plain()
168            ) .
169            Html::closeElement( 'div' );
170    }
171
172    /**
173     * Identify the next page to be shown
174     *
175     * @param array $pages
176     * @return string|bool representing title of next page to show or
177     *  false if there isn't another page to show.
178     */
179    private function getNextPage( $pages ) {
180        $total = count( $pages );
181        $offset = $this->getPageOffset( $pages );
182        $limit = self::LIMIT;
183
184        // Work out if we need a more button and where to start from
185        if ( $total > $offset + $limit ) {
186            $pageKeys = array_keys( $pages );
187            $from = $pageKeys[$offset + $limit];
188        } else {
189            $from = false;
190        }
191        return $from;
192    }
193
194    /**
195     * Renders the view/edit (normal) mode of the watchlist.
196     */
197    protected function executeViewEditWatchlist() {
198        $ns = NS_MAIN;
199        $images = [];
200
201        $watchlist = $this->getWatchlistInfo();
202
203        if ( isset( $watchlist[$ns] ) ) {
204            $allPages = $watchlist[$ns];
205            $from = $this->getNextPage( $allPages );
206            $allPages = $this->getPagesToDisplay( $allPages );
207        } else {
208            $allPages = [];
209            $from = false;
210        }
211
212        // Begin rendering of watchlist.
213        $watchlist = [ $ns => $allPages ];
214        $services = MediaWikiServices::getInstance();
215        ( new HookRunner( $services->getHookContainer() ) )
216            ->onSpecialMobileEditWatchlist__images(
217                $this->getContext(), $watchlist, $images
218            );
219
220        // create list of pages
221        $mobilePages = new MobileCollection();
222        $pageKeys = array_keys( $watchlist[$ns] );
223        $repoGroup = $services->getRepoGroup();
224        foreach ( $pageKeys as $dbkey ) {
225            if ( isset( $images[$ns][$dbkey] ) ) {
226                $page = new MobilePage(
227                    Title::makeTitleSafe( $ns, $dbkey ),
228                    $repoGroup->findFile( $images[$ns][$dbkey] )
229                );
230            } else {
231                $page = new MobilePage( Title::makeTitleSafe( $ns, $dbkey ) );
232            }
233            $mobilePages->add( $page );
234        }
235
236        if ( $mobilePages->isEmpty() ) {
237            $html = self::getEmptyListHtml( false, $this->getLanguage() );
238        } else {
239            $html = $this->getViewHtml( $mobilePages );
240        }
241        if ( $from ) {
242            // show more link if there are more items to show
243            $qs = [ 'from' => $from ];
244            $html .= Html::element( 'a',
245                [
246                    'class' => 'mw-mf-watchlist-more',
247                    'href' => SpecialPage::getTitleFor( 'EditWatchlist' )->getLocalURL( $qs ),
248                ],
249                $this->msg( 'mobile-frontend-watchlist-more' )->text() );
250        }
251        $out = $this->getOutput();
252        $out->addHTML( $html );
253        $out->addModules( 'mobile.special.watchlist.scripts' );
254        $out->addModuleStyles(
255            [
256                'mobile.pagelist.styles',
257                'mobile.pagesummary.styles'
258            ]
259        );
260    }
261
262    /**
263     * @param MobileCollection $collection Collection of pages to get view for
264     * @return string html representation of collection in watchlist view
265     */
266    protected function getViewHtml( MobileCollection $collection ) {
267        $html = Html::openElement( 'ul', [ 'class' => 'content-unstyled mw-mf-page-list thumbs'
268            . ' page-summary-list mw-mf-watchlist-page-list' ] );
269        foreach ( $collection as $mobilePage ) {
270            $html .= $this->getLineHtml( $mobilePage );
271        }
272        $html .= Html::closeElement( 'ul' );
273        return $html;
274    }
275
276    /**
277     * @inheritDoc
278     */
279    public function getAssociatedNavigationLinks() {
280        return self::WATCHLIST_TAB_PATHS;
281    }
282}