Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
84.26% |
182 / 216 |
|
70.00% |
7 / 10 |
CRAP | |
0.00% |
0 / 1 |
CargoFieldDescription | |
84.26% |
182 / 216 |
|
70.00% |
7 / 10 |
119.89 | |
0.00% |
0 / 1 |
newFromString | |
88.14% |
52 / 59 |
|
0.00% |
0 / 1 |
20.67 | |||
newFromDBArray | |
100.00% |
28 / 28 |
|
100.00% |
1 / 1 |
14 | |||
getDelimiter | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setDelimiter | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isDateOrDatetime | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getFieldSize | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
4 | |||
toDBArray | |
100.00% |
26 / 26 |
|
100.00% |
1 / 1 |
12 | |||
prepareAndValidateValue | |
100.00% |
66 / 66 |
|
100.00% |
1 / 1 |
27 | |||
prettyPrintType | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
prettyPrintTypeAndAttributes | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
56 |
1 | <?php |
2 | |
3 | /** |
4 | * CargoFieldDescription - holds the attributes of a single field as defined |
5 | * in the #cargo_declare parser function. |
6 | * |
7 | * @author Yaron Koren |
8 | * @ingroup Cargo |
9 | */ |
10 | class CargoFieldDescription { |
11 | public $mType; |
12 | public $mSize; |
13 | public $mDependentOn = []; |
14 | public $mIsList = false; |
15 | private $mDelimiter; |
16 | public $mAllowedValues = null; |
17 | public $mIsMandatory = false; |
18 | public $mIsUnique = false; |
19 | public $mRegex = null; |
20 | public $mIsHidden = false; |
21 | public $mIsHierarchy = false; |
22 | public $mHierarchyStructure = null; |
23 | public $mOtherParams = []; |
24 | |
25 | /** |
26 | * Initializes from a string within the #cargo_declare function. |
27 | * |
28 | * @param string $fieldDescriptionStr |
29 | * @return \CargoFieldDescription|null |
30 | */ |
31 | public static function newFromString( $fieldDescriptionStr ) { |
32 | $fieldDescription = new CargoFieldDescription(); |
33 | |
34 | if ( strpos( strtolower( $fieldDescriptionStr ), 'list' ) === 0 ) { |
35 | $matches = []; |
36 | $foundMatch = preg_match( '/[Ll][Ii][Ss][Tt] \((.*)\) [Oo][Ff] (.*)/is', $fieldDescriptionStr, $matches ); |
37 | if ( !$foundMatch ) { |
38 | // Return a true error message here? |
39 | return null; |
40 | } |
41 | $fieldDescription->mIsList = true; |
42 | $fieldDescription->mDelimiter = $matches[1]; |
43 | $fieldDescriptionStr = $matches[2]; |
44 | } |
45 | |
46 | CargoUtils::validateFieldDescriptionString( $fieldDescriptionStr ); |
47 | |
48 | // There may be additional parameters, in/ parentheses. |
49 | $matches = []; |
50 | $foundMatch2 = preg_match( '/([^(]*)\s*\((.*)\)/s', $fieldDescriptionStr, $matches ); |
51 | $allowedValuesParam = ""; |
52 | if ( $foundMatch2 ) { |
53 | $fieldDescriptionStr = trim( $matches[1] ); |
54 | $extraParamsString = $matches[2]; |
55 | $extraParams = explode( ';', $extraParamsString ); |
56 | foreach ( $extraParams as $extraParam ) { |
57 | $extraParamParts = explode( '=', $extraParam, 2 ); |
58 | if ( count( $extraParamParts ) == 1 ) { |
59 | $paramKey = strtolower( trim( $extraParamParts[0] ) ); |
60 | if ( $paramKey == 'hierarchy' ) { |
61 | $fieldDescription->mIsHierarchy = true; |
62 | } |
63 | $fieldDescription->mOtherParams[$paramKey] = true; |
64 | } else { |
65 | $paramKey = strtolower( trim( $extraParamParts[0] ) ); |
66 | $paramValue = trim( $extraParamParts[1] ); |
67 | if ( $paramKey == 'allowed values' ) { |
68 | // we do not assign allowed values to fieldDescription here, |
69 | // because we don't know yet if it's a hierarchy or an enumeration |
70 | $allowedValuesParam = $paramValue; |
71 | } elseif ( $paramKey == 'size' ) { |
72 | $fieldDescription->mSize = $paramValue; |
73 | } elseif ( $paramKey == 'dependent on' ) { |
74 | $fieldDescription->mDependentOn = array_map( 'trim', explode( ',', $paramValue ) ); |
75 | } else { |
76 | $fieldDescription->mOtherParams[$paramKey] = $paramValue; |
77 | } |
78 | } |
79 | } |
80 | if ( $allowedValuesParam !== "" ) { |
81 | $allowedValuesArray = []; |
82 | if ( $fieldDescription->mIsHierarchy == true ) { |
83 | // $paramValue contains "*" hierarchy structure |
84 | CargoUtils::validateHierarchyStructure( trim( $allowedValuesParam ) ); |
85 | $fieldDescription->mHierarchyStructure = trim( $allowedValuesParam ); |
86 | // now make the allowed values param similar to the syntax |
87 | // used by other fields |
88 | $hierarchyNodesArray = explode( "\n", $allowedValuesParam ); |
89 | foreach ( $hierarchyNodesArray as $node ) { |
90 | // Remove prefix of multiple "*" |
91 | $allowedValuesArray[] = trim( preg_replace( '/^[*]*/', '', $node ) ); |
92 | } |
93 | } else { |
94 | // Replace the comma/delimiter |
95 | // substitution with a character |
96 | // that has no chance of being |
97 | // included in the values list - |
98 | // namely, the ASCII beep. |
99 | |
100 | // The delimiter can't be a |
101 | // semicolon, because that's |
102 | // already used to separate |
103 | // "extra parameters", so just |
104 | // hardcode it to a semicolon. |
105 | $delimiter = ','; |
106 | $allowedValuesStr = str_replace( "\\$delimiter", "\a", $allowedValuesParam ); |
107 | $allowedValuesTempArray = explode( $delimiter, $allowedValuesStr ); |
108 | foreach ( $allowedValuesTempArray as $value ) { |
109 | if ( $value == '' ) { |
110 | continue; |
111 | } |
112 | // Replace beep back with delimiter, trim. |
113 | $value = str_replace( "\a", $delimiter, trim( $value ) ); |
114 | $allowedValuesArray[] = $value; |
115 | } |
116 | } |
117 | $fieldDescription->mAllowedValues = $allowedValuesArray; |
118 | } |
119 | } |
120 | |
121 | // What's left will be the type, hopefully. |
122 | // Allow any capitalization of the type. |
123 | $type = ucfirst( strtolower( $fieldDescriptionStr ) ); |
124 | // The 'URL' type has special capitalization. |
125 | if ( $type == 'Url' ) { |
126 | $type = 'URL'; |
127 | } |
128 | $fieldDescription->mType = $type; |
129 | |
130 | // Validation. |
131 | if ( $fieldDescription->mType == 'Text' && array_key_exists( 'unique', $fieldDescription->mOtherParams ) ) { |
132 | throw new MWException( "'unique' is not allowed for fields of type 'Text'." ); |
133 | } |
134 | if ( $fieldDescription->mType == 'Boolean' && $fieldDescription->mIsList == true ) { |
135 | throw new MWException( "Error: 'list' is not allowed for fields of type 'Boolean'." ); |
136 | } |
137 | |
138 | return $fieldDescription; |
139 | } |
140 | |
141 | /** |
142 | * @param array $descriptionData |
143 | * @return \CargoFieldDescription |
144 | */ |
145 | public static function newFromDBArray( $descriptionData ) { |
146 | $fieldDescription = new CargoFieldDescription(); |
147 | foreach ( $descriptionData as $param => $value ) { |
148 | if ( $param == 'type' ) { |
149 | $fieldDescription->mType = $value; |
150 | } elseif ( $param == 'size' ) { |
151 | $fieldDescription->mSize = $value; |
152 | } elseif ( $param == 'dependent on' ) { |
153 | $fieldDescription->mDependentOn = $value; |
154 | } elseif ( $param == 'isList' ) { |
155 | $fieldDescription->mIsList = true; |
156 | } elseif ( $param == 'delimiter' ) { |
157 | $fieldDescription->mDelimiter = $value; |
158 | } elseif ( $param == 'allowedValues' ) { |
159 | $fieldDescription->mAllowedValues = $value; |
160 | } elseif ( $param == 'mandatory' ) { |
161 | $fieldDescription->mIsMandatory = true; |
162 | } elseif ( $param == 'unique' ) { |
163 | $fieldDescription->mIsUnique = true; |
164 | } elseif ( $param == 'regex' ) { |
165 | $fieldDescription->mRegex = $value; |
166 | } elseif ( $param == 'hidden' ) { |
167 | $fieldDescription->mIsHidden = true; |
168 | } elseif ( $param == 'hierarchy' ) { |
169 | $fieldDescription->mIsHierarchy = true; |
170 | } elseif ( $param == 'hierarchyStructure' ) { |
171 | $fieldDescription->mHierarchyStructure = $value; |
172 | } else { |
173 | $fieldDescription->mOtherParams[$param] = $value; |
174 | } |
175 | } |
176 | return $fieldDescription; |
177 | } |
178 | |
179 | public function getDelimiter() { |
180 | // Make "\n" represent a newline. |
181 | return str_replace( '\n', "\n", $this->mDelimiter ); |
182 | } |
183 | |
184 | public function setDelimiter( $delimiter ) { |
185 | $this->mDelimiter = $delimiter; |
186 | } |
187 | |
188 | public function isDateOrDatetime() { |
189 | return in_array( $this->mType, [ 'Date', 'Start date', 'End date', 'Datetime', 'Start datetime', 'End datetime' ] ); |
190 | } |
191 | |
192 | public function getFieldSize() { |
193 | if ( $this->isDateOrDatetime() ) { |
194 | return null; |
195 | } elseif ( in_array( $this->mType, [ 'Integer', 'Float', 'Rating', 'Boolean', 'Text', 'Wikitext', 'Searchtext' ] ) ) { |
196 | return null; |
197 | // This leaves String, Page, etc. - see CargoUtils::fieldTypeToSQLType(). |
198 | } elseif ( $this->mSize != null ) { |
199 | return $this->mSize; |
200 | } else { |
201 | global $wgCargoDefaultStringBytes; |
202 | return $wgCargoDefaultStringBytes; |
203 | } |
204 | } |
205 | |
206 | /** |
207 | * @return array |
208 | */ |
209 | public function toDBArray() { |
210 | $descriptionData = []; |
211 | $descriptionData['type'] = $this->mType; |
212 | if ( $this->mSize != null ) { |
213 | $descriptionData['size'] = $this->mSize; |
214 | } |
215 | if ( $this->mDependentOn != null ) { |
216 | $descriptionData['dependent on'] = $this->mDependentOn; |
217 | } |
218 | if ( $this->mIsList ) { |
219 | $descriptionData['isList'] = true; |
220 | } |
221 | if ( $this->mDelimiter != null ) { |
222 | $descriptionData['delimiter'] = $this->mDelimiter; |
223 | } |
224 | if ( $this->mAllowedValues != null ) { |
225 | $descriptionData['allowedValues'] = $this->mAllowedValues; |
226 | } |
227 | if ( $this->mIsMandatory ) { |
228 | $descriptionData['mandatory'] = true; |
229 | } |
230 | if ( $this->mIsUnique ) { |
231 | $descriptionData['unique'] = true; |
232 | } |
233 | if ( $this->mRegex != null ) { |
234 | $descriptionData['regex'] = $this->mRegex; |
235 | } |
236 | if ( $this->mIsHidden ) { |
237 | $descriptionData['hidden'] = true; |
238 | } |
239 | if ( $this->mIsHierarchy ) { |
240 | $descriptionData['hierarchy'] = true; |
241 | $descriptionData['hierarchyStructure'] = $this->mHierarchyStructure; |
242 | } |
243 | foreach ( $this->mOtherParams as $otherParam => $value ) { |
244 | $descriptionData[$otherParam] = $value; |
245 | } |
246 | |
247 | return $descriptionData; |
248 | } |
249 | |
250 | public function prepareAndValidateValue( $fieldValue ) { |
251 | // @TODO - also set, and return, an error message and/or code |
252 | // if the returned value is different from the incoming value. |
253 | // @TODO - it might make sense to create a new class around |
254 | // this function, like "CargoFieldValue" - |
255 | // CargoStore::getDateValueAndPrecision() could move there too. |
256 | $fieldValue = trim( $fieldValue ); |
257 | if ( $fieldValue == '' ) { |
258 | if ( $this->isDateOrDatetime() ) { |
259 | // If it's a date field, it has to be null, |
260 | // not blank, for DB storage to work correctly. |
261 | // Possibly this is true for other types as well. |
262 | return [ 'value' => null ]; |
263 | } |
264 | return [ 'value' => $fieldValue ]; |
265 | } |
266 | |
267 | $fieldType = $this->mType; |
268 | if ( $this->mAllowedValues != null ) { |
269 | $allowedValues = $this->mAllowedValues; |
270 | if ( $this->mIsList ) { |
271 | $delimiter = $this->getDelimiter(); |
272 | $individualValues = explode( $delimiter, $fieldValue ); |
273 | $valuesToBeKept = []; |
274 | foreach ( $individualValues as $individualValue ) { |
275 | $realIndividualVal = trim( $individualValue ); |
276 | if ( in_array( $realIndividualVal, $allowedValues ) ) { |
277 | $valuesToBeKept[] = $realIndividualVal; |
278 | } |
279 | } |
280 | // FIXME: This is dead code, it's overwritten immediately |
281 | $newValue = implode( $delimiter, $valuesToBeKept ); |
282 | } else { |
283 | if ( in_array( $fieldValue, $allowedValues ) ) { |
284 | // FIXME: This is dead code, it's overwritten immediately |
285 | $newValue = $fieldValue; |
286 | } |
287 | } |
288 | } |
289 | |
290 | $precision = null; |
291 | if ( $this->isDateOrDatetime() ) { |
292 | if ( $this->mIsList ) { |
293 | $delimiter = $this->getDelimiter(); |
294 | $individualValues = explode( $delimiter, $fieldValue ); |
295 | // There's unfortunately only one precision |
296 | // value per field, even if it holds more than |
297 | // one date - store the most "precise" of the |
298 | // precision values. |
299 | $maxPrecision = CargoStore::YEAR_ONLY; |
300 | $dateValues = []; |
301 | foreach ( $individualValues as $individualValue ) { |
302 | $realIndividualVal = trim( $individualValue ); |
303 | if ( $realIndividualVal == '' ) { |
304 | continue; |
305 | } |
306 | list( $dateValue, $curPrecision ) = CargoStore::getDateValueAndPrecision( $realIndividualVal, $fieldType ); |
307 | $dateValues[] = $dateValue; |
308 | if ( $curPrecision < $maxPrecision ) { |
309 | $maxPrecision = $curPrecision; |
310 | } |
311 | } |
312 | $newValue = implode( $delimiter, $dateValues ); |
313 | $precision = $maxPrecision; |
314 | } else { |
315 | list( $newValue, $precision ) = CargoStore::getDateValueAndPrecision( $fieldValue, $fieldType ); |
316 | } |
317 | } elseif ( $fieldType == 'Integer' ) { |
318 | // Remove digit-grouping character. |
319 | global $wgCargoDigitGroupingCharacter; |
320 | if ( $this->mIsList ) { |
321 | $delimiter = $this->getDelimiter(); |
322 | if ( $delimiter != $wgCargoDigitGroupingCharacter ) { |
323 | $fieldValue = str_replace( $wgCargoDigitGroupingCharacter, '', $fieldValue ); |
324 | } |
325 | $individualValues = explode( $delimiter, $fieldValue ); |
326 | foreach ( $individualValues as &$individualValue ) { |
327 | if ( !is_int( $individualValue ) ) { |
328 | $individualValue = round( $individualValue ); |
329 | } |
330 | } |
331 | $newValue = implode( $delimiter, $individualValues ); |
332 | } else { |
333 | $newValue = str_replace( $wgCargoDigitGroupingCharacter, '', $fieldValue ); |
334 | if ( !is_int( $newValue ) ) { |
335 | $newValue = round( $newValue ); |
336 | } |
337 | } |
338 | } elseif ( $fieldType == 'Float' || $fieldType == 'Rating' ) { |
339 | // Remove digit-grouping character, and change |
340 | // decimal mark to '.' if it's anything else. |
341 | global $wgCargoDigitGroupingCharacter; |
342 | global $wgCargoDecimalMark; |
343 | $newValue = str_replace( $wgCargoDigitGroupingCharacter, '', $fieldValue ); |
344 | $newValue = str_replace( $wgCargoDecimalMark, '.', $newValue ); |
345 | } elseif ( $fieldType == 'Boolean' ) { |
346 | // True = 1, "yes" |
347 | // False = 0, "no" |
348 | $msgForNo = wfMessage( 'htmlform-no' )->text(); |
349 | if ( $fieldValue === 0 |
350 | || $fieldValue === '0' |
351 | || strtolower( $fieldValue ) === 'no' |
352 | || strtolower( $fieldValue ) == strtolower( $msgForNo ) ) { |
353 | $newValue = '0'; |
354 | } else { |
355 | $newValue = '1'; |
356 | } |
357 | } else { |
358 | $newValue = $fieldValue; |
359 | } |
360 | |
361 | $valueArray = [ 'value' => $newValue ]; |
362 | if ( $precision !== null ) { |
363 | $valueArray['precision'] = $precision; |
364 | } |
365 | |
366 | return $valueArray; |
367 | } |
368 | |
369 | public function prettyPrintType() { |
370 | $typeDesc = '<tt>' . $this->mType . '</tt>'; |
371 | if ( $this->mIsList ) { |
372 | $delimiter = '<tt>' . $this->mDelimiter . '</tt>'; |
373 | $typeDesc = wfMessage( 'cargo-cargotables-listof', |
374 | $typeDesc, $delimiter )->parse(); |
375 | } |
376 | return $typeDesc; |
377 | } |
378 | |
379 | public function prettyPrintTypeAndAttributes() { |
380 | $text = $this->prettyPrintType(); |
381 | |
382 | $attributesStrings = []; |
383 | if ( $this->mIsMandatory ) { |
384 | $attributesStrings[] = [ wfMessage( 'cargo-cargotables-mandatory' )->text() ]; |
385 | } |
386 | if ( $this->mIsUnique ) { |
387 | $attributesStrings[] = [ wfMessage( 'cargo-cargotables-unique' )->text() ]; |
388 | } |
389 | if ( $this->mAllowedValues !== null ) { |
390 | $allowedValuesStr = implode( ' · ', $this->mAllowedValues ); |
391 | $attributesStrings[] = [ wfMessage( 'cargo-cargotables-allowedvalues' )->text(), |
392 | $allowedValuesStr ]; |
393 | } |
394 | if ( count( $attributesStrings ) == 0 ) { |
395 | return $text; |
396 | } |
397 | |
398 | $attributeDisplayStrings = []; |
399 | foreach ( $attributesStrings as $attributesStrs ) { |
400 | $displayString = '<span class="cargoFieldName">' . |
401 | $attributesStrs[0] . '</span>'; |
402 | if ( count( $attributesStrs ) > 1 ) { |
403 | $displayString .= ' ' . $attributesStrs[1]; |
404 | } |
405 | $attributeDisplayStrings[] = $displayString; |
406 | } |
407 | $text .= ' (' . implode( '; ', $attributeDisplayStrings ) . ')'; |
408 | |
409 | return $text; |
410 | } |
411 | |
412 | } |