Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 97
0.00% covered (danger)
0.00%
0 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
ToolbarBuilder
0.00% covered (danger)
0.00%
0 / 97
0.00% covered (danger)
0.00%
0 / 8
702
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 getGroup
0.00% covered (danger)
0.00%
0 / 36
0.00% covered (danger)
0.00%
0 / 1
210
 createContributionsPageAction
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 createEditPageAction
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
6
 createWatchPageAction
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
12
 getHistoryPageAction
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 getHistoryUrl
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 getLoginUrl
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
17 *
18 * @file
19 */
20
21namespace MediaWiki\Minerva\Menu\PageActions;
22
23use ExtensionRegistry;
24use MediaWiki\Config\ServiceOptions;
25use MediaWiki\Context\IContextSource;
26use MediaWiki\Minerva\LanguagesHelper;
27use MediaWiki\Minerva\Menu\Entries\IMenuEntry;
28use MediaWiki\Minerva\Menu\Entries\LanguageSelectorEntry;
29use MediaWiki\Minerva\Menu\Entries\SingleMenuEntry;
30use MediaWiki\Minerva\Menu\Group;
31use MediaWiki\Minerva\Permissions\IMinervaPagePermissions;
32use MediaWiki\Minerva\SkinOptions;
33use MediaWiki\Minerva\Skins\SkinUserPageHelper;
34use MediaWiki\SpecialPage\SpecialPage;
35use MediaWiki\Title\Title;
36use MediaWiki\User\User;
37use MediaWiki\User\UserIdentity;
38use MediaWiki\Watchlist\WatchlistManager;
39use SpecialMobileHistory;
40
41class ToolbarBuilder {
42
43    /** @var Title Article title user is currently browsing */
44    private Title $title;
45    /** @var User Currently logged in user */
46    private User $user;
47    private IContextSource $context;
48    private IMinervaPagePermissions $permissions;
49    private SkinOptions $skinOptions;
50    private SkinUserPageHelper $relevantUserPageHelper;
51    private LanguagesHelper $languagesHelper;
52    /** @var bool Correlates to $wgWatchlistExpiry feature flag. */
53    private bool $watchlistExpiryEnabled;
54    private WatchlistManager $watchlistManager;
55
56    /**
57     * ServiceOptions needed.
58     */
59    public const CONSTRUCTOR_OPTIONS = [
60        'WatchlistExpiry',
61    ];
62
63    /**
64     * Build Group containing icons for toolbar
65     * @param Title $title Article title user is currently browsing
66     * @param User $user Currently logged in user
67     * @param IContextSource $context
68     * @param IMinervaPagePermissions $permissions Minerva permissions system
69     * @param SkinOptions $skinOptions
70     * @param SkinUserPageHelper $relevantUserPageHelper User Page helper. The
71     * UserPageHelper passed should always be specific to the user page Title. If on a
72     * user talk page, UserPageHelper should be instantiated with the user page
73     * Title and NOT with the user talk page Title.
74     * @param LanguagesHelper $languagesHelper Helper to check title languages/variants
75     * @param ServiceOptions $options
76     * @param WatchlistManager $watchlistManager
77     */
78    public function __construct(
79        Title $title,
80        User $user,
81        IContextSource $context,
82        IMinervaPagePermissions $permissions,
83        SkinOptions $skinOptions,
84        SkinUserPageHelper $relevantUserPageHelper,
85        LanguagesHelper $languagesHelper,
86        ServiceOptions $options,
87        WatchlistManager $watchlistManager
88    ) {
89        $this->title = $title;
90        $this->user = $user;
91        $this->context = $context;
92        $this->permissions = $permissions;
93        $this->skinOptions = $skinOptions;
94        $this->relevantUserPageHelper = $relevantUserPageHelper;
95        $this->languagesHelper = $languagesHelper;
96        $this->watchlistExpiryEnabled = $options->get( 'WatchlistExpiry' );
97        $this->watchlistManager = $watchlistManager;
98    }
99
100    /**
101     * @param array $actions
102     * @param array $views
103     * @return Group
104     */
105    public function getGroup( array $actions, array $views ): Group {
106        $group = new Group( 'p-views' );
107        $permissions = $this->permissions;
108        $userPageOrUserTalkPageWithOverflowMode = $this->skinOptions->get( SkinOptions::TOOLBAR_SUBMENU )
109            && $this->relevantUserPageHelper->isUserPage();
110
111        if ( !$userPageOrUserTalkPageWithOverflowMode && $permissions->isAllowed(
112            IMinervaPagePermissions::SWITCH_LANGUAGE ) ) {
113            $group->insertEntry( new LanguageSelectorEntry(
114                $this->title,
115                $this->languagesHelper->doesTitleHasLanguagesOrVariants(
116                    $this->context->getOutput(),
117                    $this->title
118                ),
119                $this->context,
120                true
121            ) );
122        }
123
124        $watchKey = $key = isset( $actions['unwatch'] ) ? 'unwatch' : 'watch';
125        // The watchstar is typically not shown to anonymous users but it is in Minerva.
126        $watchData = $actions[ $watchKey ] ?? [
127            'icon' => 'star',
128            'class' => '',
129            'href' => $this->getLoginUrl( [ 'returnto' => $this->title ] ),
130            'text' => $this->context->msg( 'watch' ),
131        ];
132        if ( $permissions->isAllowed( IMinervaPagePermissions::WATCHABLE ) && $watchData ) {
133            $group->insertEntry( $this->createWatchPageAction( $watchKey, $watchData ) );
134        }
135
136        $historyView = $views[ 'history'] ?? [];
137        if ( $historyView && $permissions->isAllowed( IMinervaPagePermissions::HISTORY ) ) {
138            $group->insertEntry( $this->getHistoryPageAction( $historyView ) );
139        }
140
141        $user = $this->relevantUserPageHelper->getPageUser();
142        $isUserPageAccessible = $this->relevantUserPageHelper->isUserPageAccessibleToCurrentUser();
143        if ( $user && $isUserPageAccessible ) {
144            // T235681: Contributions icon should be added to toolbar on user pages
145            // and user talk pages for all users
146            $group->insertEntry( $this->createContributionsPageAction( $user ) );
147        }
148
149        // We want the edit icon/action(s) always to be the last element on the toolbar list
150        if ( $permissions->isAllowed( IMinervaPagePermissions::CONTENT_EDIT ) ) {
151            foreach ( $views as $key => $viewData ) {
152                if ( in_array( $key, [ 've-edit', 'viewsource', 'edit' ] ) ) {
153                    $group->insertEntry( $this->createEditPageAction( $key, $viewData ) );
154                }
155            }
156        }
157        return $group;
158    }
159
160    /**
161     * Create Contributions page action visible on user pages or user talk pages
162     * for given $user
163     *
164     * @param UserIdentity $user Determines what the contribution page action will link to
165     * @return IMenuEntry
166     */
167    protected function createContributionsPageAction( UserIdentity $user ): IMenuEntry {
168        $label = $this->context->msg( 'mobile-frontend-user-page-contributions' );
169
170        $entry = new SingleMenuEntry(
171            'page-actions-contributions',
172            $label->escaped(),
173            SpecialPage::getTitleFor( 'Contributions', $user->getName() )->getLocalURL() );
174        $entry->setTitle( $label )
175            ->trackClicks( 'contributions' )
176            ->setIcon( 'userContributions' );
177
178        return $entry;
179    }
180
181    /**
182     * Creates the "edit" page action: the well-known pencil icon that, when tapped, will open an
183     * editor with the lead section loaded.
184     *
185     * @param string $key
186     * @param array $editAction
187     * @return IMenuEntry An edit page actions menu entry
188     */
189    protected function createEditPageAction( string $key, array $editAction ): IMenuEntry {
190        $title = $this->title;
191
192        $id = $editAction['single-id'] ?? 'ca-edit';
193        $entry = new SingleMenuEntry(
194            'page-actions-' . $key,
195            $editAction['text'],
196            $editAction['href'],
197            'edit-page'
198        );
199        $iconFallback = $key === 'viewsource' ? 'editLock' : 'edit';
200        $icon = $editAction['icon'] ?? $iconFallback;
201        $entry->setIcon( $icon . '-base20' )
202            ->trackClicks( $key )
203            ->setTitle( $this->context->msg( 'tooltip-' . $id ) )
204            ->setNodeID( $id );
205        return $entry;
206    }
207
208    /**
209     * Creates the "watch" or "unwatch" action: the well-known star icon that, when tapped, will
210     * add the page to or remove the page from the user's watchlist; or, if the user is logged out,
211     * will direct the user's UA to Special:Login.
212     *
213     * @param string $watchKey either watch or unwatch
214     * @param array $watchData
215     * @return IMenuEntry An watch/unwatch page actions menu entry
216     */
217    protected function createWatchPageAction( string $watchKey, array $watchData ): IMenuEntry {
218        $entry = new SingleMenuEntry(
219            'page-actions-watch',
220            $watchData['text'],
221            $watchData['href'],
222            $watchData[ 'class' ],
223            $this->permissions->isAllowed( IMinervaPagePermissions::WATCH )
224        );
225        $icon = $watchData['icon'] ?? '';
226        if ( $icon ) {
227            $icon .= $watchKey === 'unwatch' ? '-progressive' : '-base20';
228        }
229        return $entry->trackClicks( $watchKey )
230            ->setIcon( $icon )
231            ->setTitle( $this->context->msg( $watchKey ) )
232            ->setNodeID( 'ca-watch' );
233    }
234
235    /**
236     * Creates a history action: An icon that links to the mobile history page.
237     *
238     * @param array $historyAction
239     * @return IMenuEntry A menu entry object that represents a map of HTML attributes
240     * and a 'text' property to be used with the pageActionMenu.mustache template.
241     */
242    protected function getHistoryPageAction( array $historyAction ): IMenuEntry {
243        $entry = new SingleMenuEntry(
244            'page-actions-history',
245            $historyAction['text'],
246            $historyAction['href'],
247        );
248        $icon = $historyAction['icon'] ?? 'history';
249        $entry->setIcon( $icon . '-base20' )
250            ->trackClicks( 'history' );
251        return $entry;
252    }
253
254    /**
255     * Get the URL for the history page for the given title using Special:History
256     * when available.
257     * FIXME: temporary duplicated code, same as SkinMinerva::getHistoryUrl()
258     * @param Title $title The Title object of the page being viewed
259     * @return string
260     */
261    protected function getHistoryUrl( Title $title ): string {
262        return ExtensionRegistry::getInstance()->isLoaded( 'MobileFrontend' ) &&
263               SpecialMobileHistory::shouldUseSpecialHistory( $title, $this->user ) ?
264            SpecialPage::getTitleFor( 'History', $title )->getLocalURL() :
265            $title->getLocalURL( [ 'action' => 'history' ] );
266    }
267
268    /**
269     * Prepares a url to the Special:UserLogin with query parameters
270     * @param array $query
271     * @return string
272     */
273    private function getLoginUrl( $query ): string {
274        return SpecialPage::getTitleFor( 'Userlogin' )->getLocalURL( $query );
275    }
276}