Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
41.67% covered (danger)
41.67%
20 / 48
20.00% covered (danger)
20.00%
1 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
Oracle
41.67% covered (danger)
41.67%
20 / 48
20.00% covered (danger)
20.00%
1 / 5
99.40
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 shouldUseParsoid
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
30
 isParsoidDefaultFor
90.48% covered (success)
90.48%
19 / 21
0.00% covered (danger)
0.00%
0 / 1
9.07
 showingMobileView
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 isContentModelAllowed
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3namespace MediaWiki\Extension\ParserMigration;
4
5use ExtensionRegistry;
6use MediaWiki\Config\Config;
7use MediaWiki\Request\WebRequest;
8use MediaWiki\Title\Title;
9use MediaWiki\User\Options\UserOptionsManager;
10use MediaWiki\User\User;
11use MobileContext;
12
13class Oracle {
14
15    public const USERPREF_ALWAYS = 1;
16    public const USERPREF_DEFAULT = 0;
17    public const USERPREF_NEVER = 2;
18
19    public function __construct(
20        private readonly Config $mainConfig,
21        private readonly UserOptionsManager $userOptionsManager,
22        private readonly ?MobileContext $mobileContext,
23    ) {
24    }
25
26    /**
27     * Determine whether to use Parsoid for read views on this request,
28     * request, based on the user's preferences and the URL query string.
29     *
30     * @param User $user
31     * @param WebRequest $request
32     * @param Title $title
33     * @return bool True if Parsoid should be used for this request
34     */
35    public function shouldUseParsoid( User $user, WebRequest $request, Title $title ): bool {
36        // Check if the content model is allowed for parser migration
37        if ( !$this->isContentModelAllowed( $title ) ) {
38            return false;
39        }
40
41        // Find out if the user has opted in to Parsoid Read Views by default
42        $userPref = intval( $this->userOptionsManager->getOption(
43            $user,
44            'parsermigration-parsoid-readviews'
45        ) );
46        $userOptIn = $this->isParsoidDefaultFor( $title );
47
48        // Override the default if a preference is set
49        if ( $userPref === self::USERPREF_ALWAYS ) {
50            $userOptIn = true;
51        }
52        if ( $userPref === self::USERPREF_NEVER ) {
53            $userOptIn = false;
54        }
55
56        // Allow disabling query string handling via config change to manage
57        // parser cache usage.
58        $queryStringEnabled = $this->mainConfig->get(
59            'ParserMigrationEnableQueryString'
60        );
61        if ( !$queryStringEnabled ) {
62            // Ignore query string and use Parsoid read views if and only
63            // if the user has opted in.
64            return $userOptIn;
65        }
66
67        // Otherwise, use the user's opt-in status to set the default for
68        // query string processing.
69        return $request->getFuzzyBool( 'useparsoid', $userOptIn );
70    }
71
72    /**
73     * Determine whether Parsoid should be used by default on this page,
74     * based on per-wiki configuration.  User preferences and query
75     * string parameters are not consulted.
76     * @param Title $title
77     * @return bool
78     */
79    public function isParsoidDefaultFor( Title $title ): bool {
80        $articlePagesEnabled = $this->mainConfig->get(
81            'ParserMigrationEnableParsoidArticlePages'
82        );
83        // This enables Parsoid on all talk pages, which isn't *exactly*
84        // the same as "the set of pages where DiscussionTools is enabled",
85        // but it will do for now.
86        $talkPagesEnabled = $this->mainConfig->get(
87            'ParserMigrationEnableParsoidDiscussionTools'
88        );
89
90        $isEnabled = $title->isTalkPage() ? $talkPagesEnabled : $articlePagesEnabled;
91
92        // Exclude mobile domains by default, regardless of the namespace settings
93        // above, if the config isn't on
94        $disableOnMobile =
95            !$this->mainConfig->get( 'ParserMigrationEnableParsoidMobileFrontend' );
96        if (
97            $title->isTalkPage() &&
98            !$this->mainConfig->get( 'ParserMigrationEnableParsoidMobileFrontendTalkPages' )
99        ) {
100            $disableOnMobile = true;
101        }
102        if (
103            $disableOnMobile && $this->showingMobileView()
104        ) {
105            $isEnabled = false;
106        }
107
108        // Incremental deploys (T391881)
109        // (avoid md5 hash unless needed for incremental deploy)
110        $percentage = $this->mainConfig->get( 'ParserMigrationEnableParsoidPercentage' );
111        if ( $isEnabled && ( $percentage < 100 ) ) {
112            $key = $title->getNamespace() . ':' . $title->getDBkey();
113            $hash = hexdec( substr( md5( $key ), 0, 8 ) ) & 0x7fffffff;
114            if ( ( $hash % 100 ) >= $percentage ) {
115                $isEnabled = false;
116            }
117        }
118
119        return $isEnabled;
120    }
121
122    /** Proxy MobileContext::shouldDisplayMobileView() */
123    public function showingMobileView(): bool {
124        return $this->mobileContext &&
125            $this->mobileContext->shouldDisplayMobileView();
126    }
127
128    /**
129     * Check if a content model is allowed for parser migration.
130     * This includes wikitext and any content models registered by extensions
131     * via the AllowedContentModels attribute.
132     *
133     * @param Title $title
134     * @return bool
135     */
136    private function isContentModelAllowed( Title $title ): bool {
137        // Always allow wikitext
138        if ( $title->hasContentModel( CONTENT_MODEL_WIKITEXT ) ) {
139            return true;
140        }
141
142        // Get content models registered by extensions
143        $extensionContentModels = ExtensionRegistry::getInstance()
144            ->getAttribute( 'ParserMigrationAllowedContentModels' );
145
146        // Check if the title's content model is in the registered list
147        if ( in_array( $title->getContentModel(), $extensionContentModels, true ) ) {
148            return true;
149        }
150
151        return false;
152    }
153}