Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 54
0.00% covered (danger)
0.00%
0 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
Hooks
0.00% covered (danger)
0.00%
0 / 54
0.00% covered (danger)
0.00%
0 / 9
812
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 onGetPreferences
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 onBeforePageDisplay
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
 onSkinTemplateNavigation__Universal
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 skinConfigViewsLinks
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
30
 showIcon
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 getUserTalkPage
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
110
 onListDefinedTags
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onChangeTagsListActive
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3// phpcs:disable MediaWiki.NamingConventions.LowerCamelFunctionsName.FunctionName
4
5namespace MediaWiki\Extension\WikiLove;
6
7use ApiMessage;
8use IApiMessage;
9use MediaWiki\ChangeTags\Hook\ChangeTagsListActiveHook;
10use MediaWiki\ChangeTags\Hook\ListDefinedTagsHook;
11use MediaWiki\Config\Config;
12use MediaWiki\Hook\BeforePageDisplayHook;
13use MediaWiki\Hook\SkinTemplateNavigation__UniversalHook;
14use MediaWiki\MediaWikiServices;
15use MediaWiki\Output\OutputPage;
16use MediaWiki\Preferences\Hook\GetPreferencesHook;
17use MediaWiki\Title\Title;
18use MediaWiki\User\Options\UserOptionsLookup;
19use MediaWiki\User\User;
20use Skin;
21use SkinTemplate;
22
23/**
24 * Hooks for WikiLove extension
25 *
26 * @file
27 * @ingroup Extensions
28 */
29
30class Hooks implements
31    GetPreferencesHook,
32    SkinTemplateNavigation__UniversalHook,
33    BeforePageDisplayHook,
34    ListDefinedTagsHook,
35    ChangeTagsListActiveHook
36{
37
38    /** @var Config */
39    private $config;
40
41    /** @var UserOptionsLookup */
42    private $userOptionsLookup;
43
44    /**
45     * @param Config $config
46     * @param UserOptionsLookup $userOptionsLookup
47     */
48    public function __construct(
49        Config $config,
50        UserOptionsLookup $userOptionsLookup
51    ) {
52        $this->config = $config;
53        $this->userOptionsLookup = $userOptionsLookup;
54    }
55
56    /**
57     * Add the preference in the user preferences with the GetPreferences hook.
58     *
59     * @param User $user
60     * @param array &$preferences
61     */
62    public function onGetPreferences( $user, &$preferences ) {
63        if ( !$this->config->get( 'WikiLoveGlobal' ) ) {
64            $preferences['wikilove-enabled'] = [
65                'type' => 'check',
66                'section' => 'editing/advancedediting',
67                'label-message' => 'wikilove-enable-preference',
68            ];
69        }
70    }
71
72    /**
73     * Adds the required module if we are on a user (talk) page.
74     *
75     * @param OutputPage $out
76     * @param Skin $skin
77     */
78    public function onBeforePageDisplay( $out, $skin ): void {
79        if (
80            !$this->config->get( 'WikiLoveGlobal' ) &&
81            !$this->userOptionsLookup->getOption( $out->getUser(), 'wikilove-enabled' )
82        ) {
83            return;
84        }
85
86        $title = self::getUserTalkPage( $skin->getTitle(), $skin->getUser() );
87        // getUserTalkPage() returns an ApiMessage on error
88        if ( !$title instanceof ApiMessage ) {
89            $recipient = $title->getBaseText();
90
91            $out->addJsConfigVars( [ 'wikilove-recipient' => $recipient ] );
92
93            $out->addModules( 'ext.wikiLove.init' );
94            $out->addModuleStyles( 'ext.wikiLove.icon' );
95        }
96    }
97
98    /**
99     * Add a tab or an icon the new way (MediaWiki 1.18+)
100     *
101     * @param SkinTemplate $skin
102     * @param array &$links Navigation links
103     */
104    public function onSkinTemplateNavigation__Universal( $skin, &$links ): void {
105        if ( $this->showIcon( $skin ) ) {
106            $this->skinConfigViewsLinks( $skin, $links['views'] );
107        } else {
108            $this->skinConfigViewsLinks( $skin, $links['actions'] );
109        }
110    }
111
112    /**
113     * Configure views links.
114     *
115     * Helper function for SkinTemplateNavigation hooks
116     * to configure views links.
117     *
118     * @param Skin $skin
119     * @param array &$views
120     */
121    private function skinConfigViewsLinks( $skin, &$views ) {
122        // If WikiLove is turned off for this user, don't display tab.
123        if (
124            !$this->config->get( 'WikiLoveGlobal' ) &&
125            !$this->userOptionsLookup->getOption( $skin->getUser(), 'wikilove-enabled' )
126        ) {
127            return;
128        }
129
130        // getUserTalkPage() returns an ApiMessage on error
131        if ( !self::getUserTalkPage( $skin->getTitle(), $skin->getUser() ) instanceof ApiMessage ) {
132            $views['wikilove'] = [
133                'text' => $skin->msg( 'wikilove-tab-text' )->text(),
134                'href' => '#',
135            ];
136            if ( $this->showIcon( $skin ) ) {
137                $views['wikilove']['icon'] = 'heart';
138                $views['wikilove']['button'] = true;
139                $views['wikilove']['primary'] = true;
140            }
141        }
142    }
143
144    /**
145     * Only show an icon when the global preference is enabled and the current skin isn't CologneBlue.
146     *
147     * @param Skin $skin
148     * @return bool
149     */
150    private function showIcon( $skin ) {
151        return $this->config->get( 'WikiLoveTabIcon' ) &&
152            $skin->getSkinName() !== 'cologneblue';
153    }
154
155    /**
156     * Find the editable talk page of the user we want to send WikiLove to. This
157     * function also does some sense-checking to make sure we will actually
158     * be able to send WikiLove to the target.
159     *
160     * Phan false positives are suppressed
161     *
162     * @param Title $title The title of a user page or user talk page
163     * @param User $user the current user
164     * @return Title|IApiMessage Returns either the Title object for the talk page or an error message
165     * @suppress PhanPossiblyUndeclaredVariable,PhanTypeMismatchReturnNullable,PhanTypeMismatchArgumentNullable
166     */
167    public static function getUserTalkPage( $title, $user ) {
168        // Exit early if the sending user isn't logged in
169        if ( !$user->isRegistered() || $user->isTemp() ) {
170            return ApiMessage::create( 'wikilove-err-not-logged-in', 'notloggedin' );
171        }
172
173        // Exit early if the page is in the wrong namespace
174        $ns = $title->getNamespace();
175        if ( $ns !== NS_USER && $ns !== NS_USER_TALK ) {
176            return ApiMessage::create( 'wikilove-err-invalid-username', 'invalidusername' );
177        }
178
179        // If we're on a subpage, get the root page title
180        $baseTitle = $title->getRootTitle();
181
182        // Users can't send WikiLove to themselves
183        if ( $user->getName() === $baseTitle->getText() ) {
184            return ApiMessage::create( 'wikilove-err-no-self-wikilove', 'no-self-wikilove' );
185        }
186
187        // Get the user talk page
188        if ( $ns === NS_USER_TALK ) {
189            // We're already on the user talk page
190            $talkTitle = $baseTitle;
191        } elseif ( $ns === NS_USER ) {
192            // We're on the user page, so retrieve the user talk page instead
193            $talkTitle = $baseTitle->getTalkPage();
194        }
195
196        // If it's a redirect, exit. We don't follow redirects since it might confuse the user or
197        // lead to an endless loop (like if the talk page redirects to the user page or a subpage).
198        // This means that the WikiLove tab will not appear on user pages or user talk pages if
199        // the user talk page is a redirect.
200        if ( $talkTitle->isRedirect() ) {
201            return ApiMessage::create( 'wikilove-err-redirect', 'isredirect' );
202        }
203
204        // Make sure we can edit the page
205        if ( !MediaWikiServices::getInstance()->getPermissionManager()
206            ->quickUserCan( 'edit', $user, $talkTitle ) ) {
207            return ApiMessage::create( 'wikilove-err-cannot-edit', 'cannotedit' );
208        }
209
210        return $talkTitle;
211    }
212
213    /**
214     * ListDefinedTags hook handler
215     *
216     * @see https://www.mediawiki.org/wiki/Manual:Hooks/ListDefinedTags
217     * @param array &$tags
218     */
219    public function onListDefinedTags( &$tags ) {
220        $tags[] = 'wikilove';
221    }
222
223    /**
224     * ChangeTagsListActive hook handler
225     *
226     * @see https://www.mediawiki.org/wiki/Manual:Hooks/ChangeTagsListActive
227     * @param array &$tags
228     */
229    public function onChangeTagsListActive( &$tags ) {
230        $tags[] = 'wikilove';
231    }
232
233}