Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
19.51% covered (danger)
19.51%
8 / 41
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
Hooks
19.51% covered (danger)
19.51%
8 / 41
0.00% covered (danger)
0.00%
0 / 5
186.94
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 onGetPreferences
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 onPageSaveComplete
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 maybeSendNotification
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
132
 isTwlEligible
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
2.01
1<?php
2
3namespace MediaWiki\Extension\TheWikipediaLibrary;
4
5use MediaWiki\Config\Config;
6use MediaWiki\Config\ConfigFactory;
7use MediaWiki\Deferred\DeferredUpdates;
8use MediaWiki\Extension\CentralAuth\User\CentralAuthUser;
9use MediaWiki\Permissions\PermissionManager;
10use MediaWiki\Preferences\Hook\GetPreferencesHook;
11use MediaWiki\Registration\ExtensionRegistry;
12use MediaWiki\Revision\RevisionRecord;
13use MediaWiki\Storage\EditResult;
14use MediaWiki\Storage\Hook\PageSaveCompleteHook;
15use MediaWiki\Title\Title;
16use MediaWiki\User\User;
17use MediaWiki\User\UserFactory;
18use MediaWiki\User\UserIdentity;
19use WikiPage;
20
21/**
22 * TheWikipediaLibrary extension hooks
23 *
24 * @file
25 * @ingroup Extensions
26 * @license MIT
27 */
28class Hooks implements
29    PageSaveCompleteHook,
30    GetPreferencesHook
31{
32    private Config $config;
33    private PermissionManager $permissionManager;
34    private UserFactory $userFactory;
35
36    public function __construct(
37        ConfigFactory $configFactory,
38        PermissionManager $permissionManager,
39        UserFactory $userFactory
40    ) {
41        $this->config = $configFactory->makeConfig( 'TheWikipediaLibrary' );
42        $this->permissionManager = $permissionManager;
43        $this->userFactory = $userFactory;
44    }
45
46    /**
47     * Add API preference tracking whether the user has been notified already.
48     * @param User $user
49     * @param array &$preferences
50     */
51    public function onGetPreferences( $user, &$preferences ) {
52        $preferences['twl-notified'] = [
53            'type' => 'api'
54        ];
55    }
56
57    /**
58     * Send a Wikipedia Library notification if the user has reached the required age and editcount.
59     *
60     * @see https://www.mediawiki.org/wiki/Manual:Hooks/PageSaveComplete
61     *
62     * @note parameters include classes not available before 1.35, so for those typehints
63     * are not used. The variable name reflects the class
64     *
65     * @param WikiPage $wikiPage
66     * @param UserIdentity $userIdentity
67     * @param string $summary
68     * @param int $flags
69     * @param RevisionRecord $revisionRecord
70     * @param EditResult $editResult
71     */
72    public function onPageSaveComplete(
73        $wikiPage,
74        $userIdentity,
75        $summary,
76        $flags,
77        $revisionRecord,
78        $editResult
79    ) {
80        // Need CentralAuth extension.
81        if ( !ExtensionRegistry::getInstance()->isLoaded( 'CentralAuth' ) ) {
82            return;
83        }
84        if ( $this->config->get( 'TwlSendNotifications' ) ) {
85            $title = $wikiPage->getTitle();
86            $this->maybeSendNotification( $userIdentity, $title );
87        }
88    }
89
90    /**
91     * Decide whether to notify the user based on central auth eligibility and global preference state
92     *
93     * @param UserIdentity $userIdentity
94     * @param Title $title
95     */
96    private function maybeSendNotification( UserIdentity $userIdentity, Title $title ) {
97        // Wrap in a POSTSEND deferred update to avoid blocking the HTTP response
98        DeferredUpdates::addCallableUpdate( function () use ( $userIdentity, $title ) {
99            $user = $this->userFactory->newFromUserIdentity( $userIdentity );
100            // Only proceed if we're dealing with an authenticated non-system account
101            if ( $user->isAnon() || $user->isTemp() || $user->isSystemUser() ) {
102                return;
103            }
104            // Only proceed if we're dealing with a non-bot account
105            if ( $this->permissionManager->userHasRight( $user, 'bot' ) ) {
106                return;
107            }
108            // Only proceed if we're dealing with an SUL account
109            $centralAuthUser = CentralAuthUser::getInstance( $user );
110            if ( !$centralAuthUser->isAttached() ) {
111                return;
112            }
113            // Only proceed if we haven't already notified this user
114            $twlNotifiedPref = PreferenceHelper::getGlobalPreference( $user, 'twl-notified' );
115            if ( $twlNotifiedPref === 'yes' ) {
116                return;
117            }
118            // Only proceed if we're dealing with an eligible account
119            if ( !$this->isTwlEligible( $centralAuthUser ) ) {
120                return;
121            }
122            // Set the twl-notified preference to 'no' if we haven't notified this user
123            // We've added this extra step to ensure that global preferences may be modified
124            // to avoid multiple notifications in case the preference isn't saved before the next edit
125            if ( $twlNotifiedPref === null ) {
126                PreferenceHelper::setGlobalPreference( $user, 'twl-notified', 'no' );
127                return;
128            }
129            // Notify the user if:
130            // - they haven't been notified yet
131            // - we can successfully set the preference
132            if (
133                $twlNotifiedPref === 'no'
134                && PreferenceHelper::setGlobalPreference( $user, 'twl-notified', 'yes' )
135            ) {
136                EchoHelper::send( $user, $title );
137            }
138        } );
139    }
140
141    /**
142     * Determine Twl Eligibility
143     *
144     * @param CentralAuthUser $centralAuthUser
145     * @return bool
146     *
147     */
148    public function isTwlEligible( CentralAuthUser $centralAuthUser ) {
149        $twlRegistrationDays = $this->config->get( 'TwlRegistrationDays' );
150        $minimumAge = $twlRegistrationDays * 24 * 3600;
151        $accountAge = (int)wfTimestamp( TS_UNIX ) -
152            (int)wfTimestamp( TS_UNIX, $centralAuthUser->getRegistration() );
153        if ( $accountAge < $minimumAge ) {
154            return false;
155        }
156
157        $twlEditCount = $this->config->get( 'TwlEditCount' );
158        $globalEditCount = $centralAuthUser->getGlobalEditCount();
159
160        return $globalEditCount >= $twlEditCount;
161    }
162}