Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
10.29% |
7 / 68 |
|
0.00% |
0 / 5 |
CRAP | |
0.00% |
0 / 1 |
CNChoiceDataResourceLoaderModule | |
10.29% |
7 / 68 |
|
0.00% |
0 / 5 |
200.80 | |
0.00% |
0 / 1 |
getChoices | |
58.33% |
7 / 12 |
|
0.00% |
0 / 1 |
3.65 | |||
getFromApi | |
0.00% |
0 / 27 |
|
0.00% |
0 / 1 |
20 | |||
getScript | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 | |||
getDependencies | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
42 | |||
getDefinitionSummary | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | use MediaWiki\Html\Html; |
4 | use MediaWiki\MediaWikiServices; |
5 | use MediaWiki\ResourceLoader as RL; |
6 | |
7 | /** |
8 | * ResourceLoader module for sending banner choices to the client. |
9 | * |
10 | * Note: This class has been intentionally left stateless, due to how |
11 | * ResourceLoader works. This class has no expectation of having getScript() or |
12 | * getModifiedHash() called in the same request. |
13 | */ |
14 | class CNChoiceDataResourceLoaderModule extends RL\Module { |
15 | |
16 | private const API_REQUEST_TIMEOUT = 20; |
17 | |
18 | protected function getChoices( RL\Context $context ) { |
19 | $config = $this->getConfig(); |
20 | $project = $config->get( 'NoticeProject' ); |
21 | $language = $context->getLanguage(); |
22 | |
23 | // Only fetch the data via the API if $wgCentralNoticeApiUrl is set. |
24 | // Otherwise, use the DB. |
25 | $apiUrl = $config->get( 'CentralNoticeApiUrl' ); |
26 | if ( $apiUrl ) { |
27 | $choices = $this->getFromApi( $project, $language ); |
28 | |
29 | if ( !$choices ) { |
30 | wfLogWarning( 'Couldn\'t fetch banner choice data via API. ' . |
31 | 'wgCentralNoticeApiUrl = ' . $apiUrl ); |
32 | |
33 | return []; |
34 | } |
35 | } else { |
36 | $choices = ChoiceDataProvider::getChoices( $project, $language ); |
37 | } |
38 | |
39 | return $choices; |
40 | } |
41 | |
42 | /** |
43 | * Get the banner choices data via an API call to the infrastructure wiki. |
44 | * If the call fails, we return false. |
45 | * |
46 | * @param string $project |
47 | * @param string $language |
48 | * |
49 | * @return array|bool |
50 | */ |
51 | private function getFromApi( $project, $language ) { |
52 | $cnApiUrl = $this->getConfig()->get( 'CentralNoticeApiUrl' ); |
53 | |
54 | // Make the URL |
55 | $q = [ |
56 | 'action' => 'centralnoticechoicedata', |
57 | 'project' => $project, |
58 | 'language' => $language, |
59 | 'format' => 'json', |
60 | 'formatversion' => 2 // Prevents stripping of false values 8p |
61 | ]; |
62 | |
63 | $url = wfAppendQuery( $cnApiUrl, $q ); |
64 | |
65 | $apiResult = MediaWikiServices::getInstance()->getHttpRequestFactory()->get( |
66 | $url, |
67 | [ 'timeout' => self::API_REQUEST_TIMEOUT * 0.8 ], |
68 | __METHOD__ |
69 | ); |
70 | |
71 | if ( !$apiResult ) { |
72 | wfLogWarning( 'Couldn\'t get banner choice data via API.' ); |
73 | return false; |
74 | } |
75 | |
76 | $parsedApiResult = FormatJson::parse( $apiResult, FormatJson::FORCE_ASSOC ); |
77 | |
78 | if ( !$parsedApiResult->isGood() ) { |
79 | wfLogWarning( 'Couldn\'t parse banner choice data from API.' ); |
80 | return false; |
81 | } |
82 | |
83 | $result = $parsedApiResult->getValue(); |
84 | |
85 | if ( isset( $result['error'] ) ) { |
86 | wfLogWarning( 'Error fetching banner choice data via API: ' . |
87 | $result['error']['info'] . ': ' . $result['error']['code'] ); |
88 | |
89 | return false; |
90 | } |
91 | |
92 | return $result['choices']; |
93 | } |
94 | |
95 | /** |
96 | * @inheritDoc |
97 | */ |
98 | public function getScript( RL\Context $context ) { |
99 | $choices = $this->getChoices( $context ); |
100 | if ( !$choices ) { |
101 | // If there are no choices, this module will have no dependencies, |
102 | // but other modules that create mw.centralNotice may be brought |
103 | // in elsewhere. Let's the check for its existence here, too, for |
104 | // robustness. |
105 | return 'mw.centralNotice = ( mw.centralNotice || {} );' . |
106 | 'mw.centralNotice.choiceData = [];'; |
107 | } else { |
108 | |
109 | // If there are choices, this module should depend on (at least) |
110 | // ext.centralNotice.display, which will create mw.centralNotice. |
111 | // However, RL may experience errors that cause these dynamic |
112 | // dependencies to not be set as expected; so we check, just in case. |
113 | // In such an error state, ext.centralNotice.startUp.js logs to the |
114 | // console. |
115 | return 'mw.centralNotice = ( mw.centralNotice || {} );' . |
116 | 'mw.centralNotice.choiceData = ' . |
117 | Html::encodeJsVar( $choices ) . ';'; |
118 | } |
119 | } |
120 | |
121 | /** |
122 | * @inheritDoc |
123 | */ |
124 | public function getDependencies( RL\Context $context = null ) { |
125 | $cnCampaignMixins = $this->getConfig()->get( 'CentralNoticeCampaignMixins' ); |
126 | |
127 | // If this method is called with no context argument (the old method |
128 | // signature) emit a warning, but don't stop the show. |
129 | if ( !$context ) { |
130 | wfLogWarning( '$context is required for campaign mixins.' ); |
131 | return []; |
132 | } |
133 | |
134 | // Get the choices (possible campaigns and banners) for this user |
135 | $choices = $this->getChoices( $context ); |
136 | if ( !$choices ) { |
137 | // If there are no choices, no dependencies |
138 | return []; |
139 | } |
140 | |
141 | // Run through the choices to get all needed mixin RL modules |
142 | $dependencies = []; |
143 | foreach ( $choices as $choice ) { |
144 | foreach ( $choice['mixins'] as $mixinName => $mixinParams ) { |
145 | if ( !$cnCampaignMixins[$mixinName]['subscribingModule'] ) { |
146 | throw new ConfigException( |
147 | "No subscribing module for found campaign mixin {$mixinName}" ); |
148 | } |
149 | |
150 | $dependencies[] = |
151 | $cnCampaignMixins[$mixinName]['subscribingModule']; |
152 | } |
153 | } |
154 | |
155 | // The display module is needed to process choices |
156 | $dependencies[] = 'ext.centralNotice.display'; |
157 | |
158 | // Since campaigns targeting the user could have the same mixin RL |
159 | // modules, remove any duplicates. |
160 | return array_unique( $dependencies ); |
161 | } |
162 | |
163 | /** |
164 | * @inheritDoc |
165 | */ |
166 | public function getDefinitionSummary( RL\Context $context ) { |
167 | $summary = parent::getDefinitionSummary( $context ); |
168 | $summary[] = [ |
169 | 'choices' => $this->getChoices( $context ), |
170 | ]; |
171 | return $summary; |
172 | } |
173 | } |