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