Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
25.00% covered (danger)
25.00%
18 / 72
50.00% covered (danger)
50.00%
3 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
RevisionSliderHooks
25.00% covered (danger)
25.00%
18 / 72
50.00% covered (danger)
50.00%
3 / 6
109.92
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 onDifferenceEngineViewHeader
19.23% covered (danger)
19.23%
5 / 26
0.00% covered (danger)
0.00%
0 / 1
18.17
 isDisabled
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 isSamePage
33.33% covered (danger)
33.33%
2 / 6
0.00% covered (danger)
0.00%
0 / 1
8.74
 getContainerHtml
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 1
6
 onGetPreferences
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace MediaWiki\Extension\RevisionSlider;
4
5use MediaWiki\Config\Config;
6use MediaWiki\Config\ConfigFactory;
7use MediaWiki\Diff\Hook\DifferenceEngineViewHeaderHook;
8use MediaWiki\Html\Html;
9use MediaWiki\MainConfigNames;
10use MediaWiki\Message\Message;
11use MediaWiki\Preferences\Hook\GetPreferencesHook;
12use MediaWiki\Revision\RevisionRecord;
13use MediaWiki\User\Options\UserOptionsLookup;
14use MediaWiki\User\User;
15use OOUI\ButtonWidget;
16use Wikimedia\Stats\Metrics\CounterMetric;
17use Wikimedia\Stats\StatsFactory;
18
19/**
20 * RevisionSlider extension hooks
21 *
22 * @file
23 * @ingroup Extensions
24 * @license GPL-2.0-or-later
25 */
26class RevisionSliderHooks implements DifferenceEngineViewHeaderHook, GetPreferencesHook {
27
28    private Config $config;
29    private UserOptionsLookup $userOptionsLookup;
30    private CounterMetric $eventsCounter;
31
32    public function __construct(
33        ConfigFactory $configFactory,
34        UserOptionsLookup $userOptionsLookup,
35        StatsFactory $statsFactory
36    ) {
37        $this->config = $configFactory->makeConfig( 'revisionslider' );
38        $this->userOptionsLookup = $userOptionsLookup;
39        $this->eventsCounter = $statsFactory->getCounter( 'RevisionSlider_events_total' )
40            ->setLabel( 'event', 'n/a' );
41    }
42
43    /**
44     * @inheritDoc
45     */
46    public function onDifferenceEngineViewHeader( $differenceEngine ) {
47        $oldRevRecord = $differenceEngine->getOldRevision();
48        $newRevRecord = $differenceEngine->getNewRevision();
49
50        /**
51         * If the user is logged in and has explictly requested to disable the extension don't load.
52         */
53        $user = $differenceEngine->getUser();
54        if ( $this->isDisabled( $user ) || !$this->isSamePage( $oldRevRecord, $newRevRecord ) ) {
55            return;
56        }
57
58        $this->eventsCounter->setLabel( 'event', 'hookinit' )
59            ->copyToStatsdAt( 'RevisionSlider.event.hookinit' )
60            ->increment();
61
62        $timeOffset = 0;
63        if ( $this->config->get( MainConfigNames::Localtimezone ) !== null ) {
64            $timeOffset = $this->config->get( MainConfigNames::LocalTZoffset ) ?? 0;
65        }
66
67        $autoExpand = $this->userOptionsLookup->getBoolOption( $user, 'userjs-revslider-autoexpand' );
68
69        $out = $differenceEngine->getOutput();
70        // Load styles on page load to avoid FOUC
71        $out->addModuleStyles( 'ext.RevisionSlider.lazyCss' );
72        if ( $autoExpand ) {
73            $out->addModules( 'ext.RevisionSlider.init' );
74            $this->eventsCounter->setLabel( 'event', 'load' )
75                ->copyToStatsdAt( 'RevisionSlider.event.load' )
76                ->increment();
77        } else {
78            $out->addModules( 'ext.RevisionSlider.lazyJs' );
79            $this->eventsCounter->setLabel( 'event', 'lazyload' )
80                ->copyToStatsdAt( 'RevisionSlider.event.lazyload' )
81                ->increment();
82        }
83        $out->addJsConfigVars( 'extRevisionSliderTimeOffset', $timeOffset );
84        $out->enableOOUI();
85
86        $out->prependHTML( $this->getContainerHtml( $autoExpand ) );
87    }
88
89    public function isDisabled( User $user ): bool {
90        return $user->isNamed() &&
91            $this->userOptionsLookup->getBoolOption( $user, 'revisionslider-disable' );
92    }
93
94    private function isSamePage( ?RevisionRecord $oldRevRecord, ?RevisionRecord $newRevRecord ): bool {
95        // sometimes the old revision can be null (e.g. missing rev), and perhaps also the
96        // new one (T167359)
97        if ( !$oldRevRecord || !$newRevRecord ) {
98            return false;
99        }
100
101        /**
102         * Do not show the RevisionSlider when revisions from two different pages are being compared
103         *
104         * Since RevisionRecord::getPageAsLinkTarget only returns a LinkTarget, which doesn't
105         * have an equals method, compare manually by namespace and text
106         */
107        $oldTitle = $oldRevRecord->getPageAsLinkTarget();
108        $newTitle = $newRevRecord->getPageAsLinkTarget();
109        return $oldTitle->getNamespace() === $newTitle->getNamespace() &&
110            $oldTitle->getDBKey() === $newTitle->getDBKey();
111    }
112
113    private function getContainerHtml( bool $autoExpand ): string {
114        $toggleButton = new ButtonWidget( [
115            'label' => ( new Message( 'revisionslider-toggle-label' ) )->text(),
116            'indicator' => 'down',
117            'classes' => [ 'mw-revslider-toggle-button' ],
118            'infusable' => true,
119            'framed' => false,
120            'title' => ( new Message( 'revisionslider-toggle-title-expand' ) )->text(),
121        ] );
122        $toggleButton->setAttributes( [ 'style' => 'width: 100%; text-align: center;' ] );
123
124        $loadingSpinner = Html::rawElement(
125            'div', [ 'class' => 'mw-revslider-spinner' ],
126            Html::element(
127                'div', [ 'class' => 'mw-revslider-bounce' ]
128            )
129        );
130
131        return Html::rawElement( 'div',
132            [ 'class' => 'mw-revslider-container' ],
133            $toggleButton .
134            Html::rawElement( 'div',
135                [
136                    'class' => 'mw-revslider-slider-wrapper',
137                    'style' => $autoExpand ? null : 'display: none;',
138                ],
139                Html::rawElement( 'div',
140                    [ 'class' => 'mw-revslider-placeholder' ],
141                    $loadingSpinner
142                )
143            )
144        );
145    }
146
147    /**
148     * @inheritDoc
149     */
150    public function onGetPreferences( $user, &$preferences ) {
151        $preferences['revisionslider-disable'] = [
152            'type' => 'toggle',
153            'label-message' => 'revisionslider-preference-disable',
154            'section' => 'rendering/diffs',
155        ];
156    }
157
158}