Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
61.34% covered (warning)
61.34%
73 / 119
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
PageToolsComponent
61.34% covered (warning)
61.34%
73 / 119
0.00% covered (danger)
0.00%
0 / 5
86.51
0.00% covered (danger)
0.00%
0 / 1
 __construct
92.98% covered (success)
92.98%
53 / 57
0.00% covered (danger)
0.00%
0 / 1
8.02
 getDiscussionSwitch
44.44% covered (danger)
44.44%
8 / 18
0.00% covered (danger)
0.00%
0 / 1
12.17
 getLastEdit
17.65% covered (danger)
17.65%
3 / 17
0.00% covered (danger)
0.00%
0 / 1
18.96
 getContentNavButtons
42.86% covered (danger)
42.86%
9 / 21
0.00% covered (danger)
0.00%
0 / 1
33.58
 getButtonForContentAction
0.00% covered (danger)
0.00%
0 / 6
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 *
17 * @file
18 */
19namespace MediaWiki\Skin\WikimediaApiPortal\Component;
20
21use MediaWiki\Context\IContextSource;
22use MediaWiki\Permissions\PermissionManager;
23use MediaWiki\Title\Title;
24use MediaWiki\User\User;
25use OOUI\ButtonGroupWidget;
26use OOUI\ButtonWidget;
27use Wikimedia\Message\IMessageFormatterFactory;
28
29class PageToolsComponent extends MessageComponent {
30    // Allowed page actions with config overrides
31    private const PAGE_TOOLS_ALLOWED_LIST = [
32        'views' => [
33            'edit' => [
34                'visible' => [ 'view' ],
35                'icon' => 'edit',
36                'group' => 'primary',
37            ],
38        ],
39        'actions' => [
40            'move' => [
41                'visible' => [ 'view' ],
42                'group' => 'secondary',
43
44            ],
45            'delete' => [
46                'visible' => [ 'view' ],
47                'flags' => 'destructive',
48                'group' => 'secondary',
49            ],
50        ],
51    ];
52
53    /**
54     * @param IMessageFormatterFactory $messageFormatterFactory
55     * @param IContextSource $contextSource
56     * @param PermissionManager $permissionManager
57     * @param string $requestedAction
58     * @param array $actions
59     * @param bool $mobile
60     */
61    public function __construct(
62        IMessageFormatterFactory $messageFormatterFactory,
63        IContextSource $contextSource,
64        PermissionManager $permissionManager,
65        string $requestedAction,
66        array $actions,
67        bool $mobile
68    ) {
69        parent::__construct(
70            $mobile ? 'PageToolsMobile' : 'PageTools',
71            $messageFormatterFactory,
72            $contextSource
73        );
74
75        if ( $requestedAction !== 'view' && $requestedAction !== 'history' ) {
76            $this->args = null;
77            return;
78        }
79
80        $title = $contextSource->getTitle();
81
82        if ( $title->isSpecialPage() ) {
83            $this->args = null;
84            return;
85        }
86
87        $user = $contextSource->getUser();
88
89        if ( $mobile ) {
90            $buttons = $this->getContentNavButtons(
91                $title,
92                $user,
93                $permissionManager,
94                $requestedAction,
95                'all',
96                $actions,
97                [ 'framed' => true ]
98            );
99
100            $this->args = [
101                'html-discussionSwitch' => $this->getDiscussionSwitch(
102                    $title,
103                    $requestedAction,
104                    $actions
105                ),
106                'html-lastEdit' => $this->getLastEdit( $requestedAction, $contextSource, $actions ) ?: '',
107                'html-buttons' => new ButtonGroupWidget( [ 'items' => $buttons ] ),
108            ];
109
110        } else {
111            $primary = $this->getContentNavButtons(
112                $title,
113                $user,
114                $permissionManager,
115                $requestedAction,
116                'primary', $actions
117            );
118            $secondary = $this->getContentNavButtons(
119                $title,
120                $user,
121                $permissionManager,
122                $requestedAction,
123                'secondary',
124                $actions
125            );
126
127            $this->args = [
128                'html-discussionSwitch' => $this->getDiscussionSwitch(
129                    $title,
130                    $requestedAction,
131                    $actions
132                ) ?: '',
133                'html-lastEdit' => $this->getLastEdit( $requestedAction, $contextSource, $actions ),
134                'html-primaryButtons' => new ButtonGroupWidget( [ 'items' => $primary ] ),
135                'secondaryButtons' => $secondary,
136            ];
137        }
138    }
139
140    /**
141     * @param Title $title
142     * @param string $requestedAction
143     * @param array $actions
144     * @return ?ButtonWidget
145     */
146    private function getDiscussionSwitch(
147        Title $title,
148        string $requestedAction,
149        array $actions
150    ): ?ButtonWidget {
151        if ( $requestedAction === 'view' ) {
152            if ( $title->isTalkPage() ) {
153                if ( isset( $actions['namespaces']['main'] ) ) {
154                    return $this->getButtonForContentAction( $actions['namespaces']['main'], [
155                        'icon' => 'arrowPrevious',
156                        'label' => $this->formatMessage( 'wikimediaapiportal-skin-return-to-page-label' )
157                    ] );
158                }
159            } elseif ( isset( $actions['namespaces']['talk'] ) ) {
160                return $this->getButtonForContentAction(
161                    $actions['namespaces']['talk'],
162                    [ 'icon' => 'speechBubbles' ]
163                );
164            }
165        } elseif ( $requestedAction === 'history' ) {
166            return $this->getButtonForContentAction( $actions['views']['view'], [
167                'icon' => 'arrowPrevious',
168                'label' => $this->formatMessage( 'wikimediaapiportal-skin-return-to-page-label' )
169            ] );
170        }
171        return null;
172    }
173
174    /**
175     * @param string $requestedAction
176     * @param IContextSource $contextSource
177     * @param array $actions
178     * @return ?ButtonWidget
179     */
180    private function getLastEdit(
181        string $requestedAction,
182        IContextSource $contextSource,
183        array $actions
184    ): ?ButtonWidget {
185        if ( $requestedAction !== 'view' ) {
186            return null;
187        }
188
189        if ( !isset( $actions['views']['history'] ) ) {
190            return null;
191        }
192
193        $title = $contextSource->getTitle();
194
195        if ( !$title->exists() ) {
196            return null;
197        }
198
199        $lastTouched = $title->getTouched();
200        if ( $lastTouched === null ) {
201            return null;
202        }
203
204        return $this->getButtonForContentAction( $actions['views']['history'], [
205            'icon' => 'history',
206            'label' => $this->formatMessage(
207                'wikimediaapiportal-skin-updated-ts-label',
208                [ $contextSource->getLanguage()->userDate( $lastTouched, $contextSource->getUser() ) ]
209            )
210        ] );
211    }
212
213    /**
214     * Get OOUI widgets for available content actions. Helper for PageToolsComponent.
215     * @param Title $title
216     * @param User $user
217     * @param PermissionManager $permissionManager
218     * @param string $requestedAction
219     * @param string $group One of 'primary', 'secondary', or 'all'
220     * @param array $actions
221     * @param array $oouiOptions
222     * @return ButtonWidget[]
223     */
224    private function getContentNavButtons(
225        Title $title,
226        User $user,
227        PermissionManager $permissionManager,
228        string $requestedAction,
229        string $group,
230        array $actions,
231        array $oouiOptions = []
232    ): array {
233        if ( !$actions ) {
234            return [];
235        }
236
237        if ( !$permissionManager->userHasRight( $user, 'edit-docs' ) ) {
238            $actions['actions'] = [];
239        }
240
241        if ( !$permissionManager->userHasRight( $user, 'edit-docs' ) && !$title->isTalkPage() ) {
242            return [];
243        }
244
245        $buttons = [];
246        foreach ( $actions as $sectionKey => $section ) {
247            foreach ( $section as $actionKey => $action ) {
248                if ( !isset( self::PAGE_TOOLS_ALLOWED_LIST[$sectionKey][$actionKey] ) ) {
249                    continue;
250                }
251
252                $allowedListData = self::PAGE_TOOLS_ALLOWED_LIST[$sectionKey][$actionKey];
253                if ( $group !== 'all' && $group !== $allowedListData['group'] ) {
254                    continue;
255                }
256                if ( !in_array( $requestedAction, $allowedListData['visible'] ) ) {
257                    continue;
258                }
259
260                $buttons[] = $this->getButtonForContentAction( $action, array_merge(
261                    $allowedListData,
262                    $oouiOptions
263                ) );
264            }
265        }
266
267        return $buttons;
268    }
269
270    /**
271     * Helper for PageToolsComponent.
272     *
273     * @param array $action Data from 'content_navigation' entry
274     * @param array $oouiOptions Override
275     * @return ButtonWidget
276     */
277    private function getButtonForContentAction(
278        array $action,
279        $oouiOptions = []
280    ): ButtonWidget {
281        return new ButtonWidget( $oouiOptions + [
282            'id' => $action['id'],
283            'href' => $action['href'],
284            'label' => $action['text'],
285            'framed' => false,
286        ] );
287    }
288}