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