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