Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
92.54% |
62 / 67 |
|
80.00% |
8 / 10 |
CRAP | |
0.00% |
0 / 1 |
CampaignContent | |
92.54% |
62 / 67 |
|
80.00% |
8 / 10 |
23.22 | |
0.00% |
0 / 1 |
getGlobalConfigAnchorLinkTarget | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
copyWithNewText | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
overrideValidationStatus | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
setServices | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
initServices | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
getValidationStatus | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
3 | |||
isValid | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getData | |
77.78% |
14 / 18 |
|
0.00% |
0 / 1 |
4.18 | |||
newCampaignRecord | |
94.12% |
16 / 17 |
|
0.00% |
0 / 1 |
8.01 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\MediaUploader\Campaign; |
4 | |
5 | use MediaWiki\Extension\MediaUploader\MediaUploaderServices; |
6 | use MediaWiki\Linker\LinkTarget; |
7 | use MediaWiki\Page\PageReference; |
8 | use MediaWiki\Status\Status; |
9 | use MWException; |
10 | use Symfony\Component\Yaml\Exception\ParseException; |
11 | use Symfony\Component\Yaml\Yaml; |
12 | use TextContent; |
13 | use TitleValue; |
14 | |
15 | /** |
16 | * Represents the configuration of an Upload Campaign |
17 | */ |
18 | class CampaignContent extends TextContent { |
19 | |
20 | /** |
21 | * DB key of the page where the global config is anchored. |
22 | * The page is always in the Campaign: namespace. |
23 | * |
24 | * This page records the templates used by the global config, which allows |
25 | * the config to be reparsed when any of the used templates change. |
26 | */ |
27 | public const GLOBAL_CONFIG_ANCHOR_DBKEY = '-'; |
28 | |
29 | public const MODEL_ID = 'Campaign'; |
30 | |
31 | public static function getGlobalConfigAnchorLinkTarget(): LinkTarget { |
32 | return new TitleValue( NS_CAMPAIGN, self::GLOBAL_CONFIG_ANCHOR_DBKEY ); |
33 | } |
34 | |
35 | /** @var Validator */ |
36 | private $validator; |
37 | |
38 | /** @var Status */ |
39 | private $yamlParse; |
40 | |
41 | /** @var Status */ |
42 | private $realYamlParse; |
43 | |
44 | /** @var Status */ |
45 | private $validationStatus; |
46 | |
47 | /** @var Status */ |
48 | private $realValidationStatus; |
49 | |
50 | /** @var bool Whether the services were initialized */ |
51 | private $initializedServices = false; |
52 | |
53 | /** |
54 | * CampaignContent constructor. |
55 | * |
56 | * See CampaignContentHandler::preSaveTransform for a usage of the second and |
57 | * third arguments. |
58 | * |
59 | * @param string $text |
60 | * |
61 | * @throws MWException |
62 | */ |
63 | public function __construct( string $text ) { |
64 | parent::__construct( $text, CONTENT_MODEL_CAMPAIGN ); |
65 | } |
66 | |
67 | /** |
68 | * Make a copy of this content instance with new text. |
69 | * |
70 | * This carries on the services and validation statuses. |
71 | * |
72 | * @param string $text |
73 | * |
74 | * @return CampaignContent |
75 | * @throws MWException |
76 | */ |
77 | public function copyWithNewText( string $text ): CampaignContent { |
78 | $content = new CampaignContent( $text ); |
79 | |
80 | // Carry on the validation statuses |
81 | $content->yamlParse = $this->yamlParse; |
82 | $content->validationStatus = $this->validationStatus; |
83 | $content->realYamlParse = $this->realYamlParse; |
84 | $content->realValidationStatus = $this->realValidationStatus; |
85 | |
86 | // And the services as well |
87 | $content->setServices( $this->validator ); |
88 | return $content; |
89 | } |
90 | |
91 | /** |
92 | * Overrides the parsing and schema checks. Should only be used when saving an edit by the system user. |
93 | */ |
94 | public function overrideValidationStatus() { |
95 | $this->realValidationStatus = $this->getValidationStatus(); |
96 | $this->realYamlParse = $this->getData(); |
97 | $this->yamlParse = Status::newGood(); |
98 | $this->validationStatus = $this->yamlParse; |
99 | } |
100 | |
101 | /** |
102 | * Set services for unit testing purposes. |
103 | * |
104 | * @param Validator|null $validator |
105 | */ |
106 | public function setServices( ?Validator $validator = null ) { |
107 | $this->validator = $validator; |
108 | $this->initializedServices = true; |
109 | } |
110 | |
111 | /** |
112 | * Initialize services from global state. |
113 | */ |
114 | private function initServices() { |
115 | if ( $this->initializedServices ) { |
116 | return; |
117 | } |
118 | |
119 | $this->setServices( |
120 | MediaUploaderServices::getCampaignValidator() |
121 | ); |
122 | } |
123 | |
124 | /** |
125 | * Checks user input YAML to make sure that it produces a valid campaign object. |
126 | * |
127 | * @return Status |
128 | */ |
129 | public function getValidationStatus(): Status { |
130 | $this->initServices(); |
131 | |
132 | if ( $this->validationStatus ) { |
133 | return $this->validationStatus; |
134 | } |
135 | |
136 | // First, check if the syntax is valid |
137 | $yamlParse = $this->getData(); |
138 | if ( !$yamlParse->isGood() ) { |
139 | $this->validationStatus = $yamlParse; |
140 | return $this->validationStatus; |
141 | } |
142 | |
143 | $this->validationStatus = $this->validator->validate( |
144 | $yamlParse->getValue() |
145 | ); |
146 | |
147 | return $this->validationStatus; |
148 | } |
149 | |
150 | /** |
151 | * @return bool Whether content validates against campaign JSON Schema. |
152 | */ |
153 | public function isValid() { |
154 | return $this->getValidationStatus()->isGood(); |
155 | } |
156 | |
157 | /** |
158 | * Returns the data contained on the page in array representation. |
159 | * The value is wrapped in the Status object. |
160 | * |
161 | * The data is guaranteed to come from a syntactically valid YAML, but may |
162 | * not validate against the schema. Use isValid() to check if it does. |
163 | * |
164 | * @return Status |
165 | */ |
166 | public function getData(): Status { |
167 | if ( $this->yamlParse ) { |
168 | return $this->yamlParse; |
169 | } |
170 | |
171 | try { |
172 | $data = Yaml::parse( |
173 | $this->getText(), |
174 | Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE |
175 | ); |
176 | if ( is_array( $data ) ) { |
177 | $this->yamlParse = Status::newGood( $data ); |
178 | } else { |
179 | $this->yamlParse = Status::newFatal( |
180 | 'mediauploader-yaml-parse-error', |
181 | 'unknown error' |
182 | ); |
183 | } |
184 | return $this->yamlParse; |
185 | } catch ( ParseException $e ) { |
186 | return Status::newFatal( |
187 | 'mediauploader-yaml-parse-error', |
188 | $e->getMessage() |
189 | ); |
190 | } |
191 | } |
192 | |
193 | /** |
194 | * @param PageReference $page |
195 | * @param int|null $pageId |
196 | * |
197 | * @return CampaignRecord |
198 | */ |
199 | public function newCampaignRecord( PageReference $page, ?int $pageId = null ): CampaignRecord { |
200 | $yamlParse = $this->realYamlParse ?: $this->getData(); |
201 | if ( !$yamlParse->isGood() ) { |
202 | $validity = CampaignRecord::CONTENT_INVALID_FORMAT; |
203 | } else { |
204 | $status = $this->realValidationStatus ?: $this->getValidationStatus(); |
205 | if ( !$status->isGood() ) { |
206 | $validity = CampaignRecord::CONTENT_INVALID_SCHEMA; |
207 | } else { |
208 | $validity = CampaignRecord::CONTENT_VALID; |
209 | } |
210 | } |
211 | |
212 | $content = $yamlParse->getValue(); |
213 | // Content can be null, when YAML is invalid and we're force-saving |
214 | // with the system user. Fall back to empty array, so that the config |
215 | // factory doesn't do a backflip. |
216 | if ( $content === null && $validity === CampaignRecord::CONTENT_VALID ) { |
217 | $content = []; |
218 | } |
219 | |
220 | return new CampaignRecord( |
221 | $pageId, |
222 | ( $content ?: [] )['enabled'] ?? false, |
223 | $validity, |
224 | $content, |
225 | $page |
226 | ); |
227 | } |
228 | } |