Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
51.14% |
135 / 264 |
|
0.00% |
0 / 6 |
CRAP | |
0.00% |
0 / 1 |
CargoStore | |
51.14% |
135 / 264 |
|
0.00% |
0 / 6 |
1035.02 | |
0.00% |
0 / 1 |
run | |
64.10% |
25 / 39 |
|
0.00% |
0 / 1 |
23.07 | |||
storeTable | |
57.50% |
23 / 40 |
|
0.00% |
0 / 1 |
17.68 | |||
blankOrRejectBadData | |
32.00% |
8 / 25 |
|
0.00% |
0 / 1 |
107.87 | |||
getDateValueAndPrecision | |
0.00% |
0 / 24 |
|
0.00% |
0 / 1 |
132 | |||
storeAllData | |
55.65% |
64 / 115 |
|
0.00% |
0 / 1 |
96.38 | |||
doesRowAlreadyExist | |
71.43% |
15 / 21 |
|
0.00% |
0 / 1 |
12.33 |
1 | <?php |
2 | /** |
3 | * Class for the #cargo_store function. |
4 | * |
5 | * @author Yaron Koren |
6 | * @ingroup Cargo |
7 | */ |
8 | |
9 | use MediaWiki\MediaWikiServices; |
10 | |
11 | class CargoStore { |
12 | |
13 | public static $settings = []; |
14 | |
15 | public const DATE_AND_TIME = 0; |
16 | public const DATE_ONLY = 1; |
17 | public const MONTH_ONLY = 2; |
18 | public const YEAR_ONLY = 3; |
19 | |
20 | // This can be removed when support for Cargo < 3.0 is dropped. |
21 | public const PARAMS_OPTIONAL = true; |
22 | |
23 | /** |
24 | * Handles the #cargo_store parser function - saves data for one |
25 | * template call. |
26 | * |
27 | * @param Parser $parser |
28 | * @throws MWException |
29 | */ |
30 | public static function run( $parser, $frame, $args ) { |
31 | // Get page-related information early on, so we can exit |
32 | // quickly if there's a problem. |
33 | $params = []; |
34 | foreach ( $args as $arg ) { |
35 | $params[] = trim( $frame->expand( $arg ) ); |
36 | } |
37 | |
38 | $tableName = null; |
39 | $tableFieldValues = []; |
40 | |
41 | foreach ( $params as $param ) { |
42 | $parts = explode( '=', $param, 2 ); |
43 | |
44 | if ( count( $parts ) != 2 ) { |
45 | continue; |
46 | } |
47 | $key = trim( $parts[0] ); |
48 | $value = trim( $parts[1] ); |
49 | if ( $key == '_table' ) { |
50 | $tableName = $value; |
51 | } else { |
52 | $fieldName = $key; |
53 | // Since we don't know whether any empty |
54 | // value is meant to be blank or null, let's |
55 | // go with null. |
56 | if ( $value == '' ) { |
57 | $value = null; |
58 | } |
59 | $fieldValue = $value; |
60 | $tableFieldValues[$fieldName] = $fieldValue; |
61 | } |
62 | } |
63 | |
64 | if ( $tableName == '' ) { |
65 | $templateTitle = $frame->title; |
66 | [ $tableName, $isDeclared ] = CargoUtils::getTableNameForTemplate( $templateTitle ); |
67 | } |
68 | |
69 | if ( $tableName == '' ) { |
70 | return; |
71 | } |
72 | |
73 | try { |
74 | $tableSchemas = CargoUtils::getTableSchemas( [ $tableName ] ); |
75 | } catch ( MWException $e ) { |
76 | // Most likely, this table was never created - just exit. |
77 | return; |
78 | } |
79 | |
80 | $fieldDescriptions = $tableSchemas[$tableName]->mFieldDescriptions; |
81 | $fieldNames = array_keys( $fieldDescriptions ); |
82 | |
83 | if ( $GLOBALS["wgCargoStoreUseTemplateArgsFallback"] ) { |
84 | // Go through all the fields for this table, setting any that |
85 | // were not explicitly set in the #cargo_store call. |
86 | foreach ( $fieldNames as $fieldName ) { |
87 | // Skip it if it's already being handled. |
88 | if ( array_key_exists( $fieldName, $tableFieldValues ) ) { |
89 | continue; |
90 | } |
91 | // Look for a template parameter with the same name |
92 | // as this field, both with underscores and with spaces. |
93 | $curFieldValue = $frame->getArgument( $fieldName ); |
94 | |
95 | if ( $curFieldValue === false ) { |
96 | $unescapedFieldName = str_replace( '_', ' ', $fieldName ); |
97 | $curFieldValue = $frame->getArgument( $unescapedFieldName ); |
98 | } |
99 | |
100 | // We don't want to unintentionally add false values in wrongly typed-fields |
101 | // in case strict mode is being used |
102 | if ( $curFieldValue !== false ) { |
103 | $tableFieldValues[$fieldName] = $curFieldValue; |
104 | } |
105 | } |
106 | } |
107 | |
108 | self::storeTable( $parser, $tableName, $tableFieldValues ); |
109 | } |
110 | |
111 | /** |
112 | * Implements cargo_store functionality which is shared among parser function and lua |
113 | * |
114 | * @param Parser $parser |
115 | * @param string $tableName |
116 | * @param array $tableFieldValues |
117 | */ |
118 | public static function storeTable( $parser, $tableName, $tableFieldValues ) { |
119 | // Get page-related information early on, so we can exit |
120 | // quickly if there's a problem. |
121 | $title = $parser->getTitle(); |
122 | $pageID = $title->getArticleID(); |
123 | if ( $pageID <= 0 ) { |
124 | // This will most likely happen if the title is a |
125 | // "special" page. |
126 | wfDebugLog( 'cargo', "CargoStore::run() - skipping; not called from a wiki page.\n" ); |
127 | return; |
128 | } |
129 | |
130 | // This function does actual DB modifications - so only proceed |
131 | // if this is called via either a page save or a "recreate |
132 | // data" action for a template that this page calls. |
133 | if ( count( self::$settings ) == 0 ) { |
134 | wfDebugLog( 'cargo', "CargoStore::run() - skipping; no settings defined.\n" ); |
135 | return; |
136 | } elseif ( !array_key_exists( 'origin', self::$settings ) ) { |
137 | wfDebugLog( 'cargo', "CargoStore::run() - skipping; no origin defined.\n" ); |
138 | return; |
139 | } |
140 | |
141 | if ( self::$settings['origin'] == 'template' ) { |
142 | // It came from a template "recreate data" action - |
143 | // make sure it passes various criteria. |
144 | if ( self::$settings['dbTableName'] != $tableName ) { |
145 | wfDebugLog( 'cargo', "CargoStore::run() - skipping; dbTableName not set.\n" ); |
146 | return; |
147 | } |
148 | } |
149 | |
150 | // Always store data in the replacement table if it exists. |
151 | $cdb = CargoUtils::getDB(); |
152 | $cdb->begin(); |
153 | if ( $cdb->tableExists( $tableName . '__NEXT' ) ) { |
154 | $tableName .= '__NEXT'; |
155 | } |
156 | |
157 | // Get the declaration of the table. |
158 | $dbr = CargoUtils::getMainDBForRead(); |
159 | $res = $dbr->select( 'cargo_tables', 'table_schema', [ 'main_table' => $tableName ] ); |
160 | $row = $res->fetchRow(); |
161 | if ( $row == '' ) { |
162 | // This table probably has not been created yet - |
163 | // just exit silently. |
164 | wfDebugLog( 'cargo', "CargoStore::run() - skipping; Cargo table ($tableName) does not exist.\n" ); |
165 | $cdb->rollback(); |
166 | return; |
167 | } |
168 | $tableSchema = CargoTableSchema::newFromDBString( $row['table_schema'] ); |
169 | |
170 | $errors = self::blankOrRejectBadData( $cdb, $title, $tableName, $tableFieldValues, $tableSchema ); |
171 | $cdb->commit(); |
172 | |
173 | if ( $errors ) { |
174 | $parserOutput = $parser->getOutput(); |
175 | $parserOutput->setPageProperty( 'CargoStorageError', $errors ); |
176 | wfDebugLog( 'cargo', "CargoStore::run() - skipping; storage error encountered.\n" ); |
177 | return; |
178 | } |
179 | |
180 | self::storeAllData( $title, $tableName, $tableFieldValues, $tableSchema ); |
181 | |
182 | // Finally, add a record of this to the cargo_pages table, if |
183 | // necessary. |
184 | $res = $dbr->select( 'cargo_pages', 'page_id', |
185 | [ 'table_name' => $tableName, 'page_id' => $pageID ] ); |
186 | if ( !$res->fetchRow() ) { |
187 | $dbw = CargoUtils::getMainDBForWrite(); |
188 | $dbw->insert( 'cargo_pages', [ 'table_name' => $tableName, 'page_id' => $pageID ] ); |
189 | } |
190 | } |
191 | |
192 | /** |
193 | * Deal with data that is considered invalid, for one reason or |
194 | * another. For the most part we simply ignore the data (if it's an |
195 | * invalid field) or blank it (if it's an invalid value), but if it's |
196 | * a mandatory value, we have no choice but to reject the whole row. |
197 | */ |
198 | public static function blankOrRejectBadData( $cdb, $title, $tableName, &$tableFieldValues, $tableSchema ) { |
199 | foreach ( $tableFieldValues as $fieldName => $fieldValue ) { |
200 | if ( !array_key_exists( $fieldName, $tableSchema->mFieldDescriptions ) ) { |
201 | unset( $tableFieldValues[$fieldName] ); |
202 | } |
203 | } |
204 | |
205 | foreach ( $tableSchema->mFieldDescriptions as $fieldName => $fieldDescription ) { |
206 | if ( !array_key_exists( $fieldName, $tableFieldValues ) ) { |
207 | continue; |
208 | } |
209 | $fieldValue = $tableFieldValues[$fieldName]; |
210 | if ( $fieldDescription->mIsMandatory && ( $fieldValue === '' || $fieldValue === null ) ) { |
211 | return "Mandatory field, \"$fieldName\", cannot have a blank value."; |
212 | } |
213 | if ( $fieldDescription->mIsUnique && $fieldValue != '' ) { |
214 | $res = $cdb->select( $tableName, 'COUNT(*)', [ $fieldName => $fieldValue ] ); |
215 | $row = $res->fetchRow(); |
216 | $numExistingValues = $row['COUNT(*)']; |
217 | if ( $numExistingValues == 1 ) { |
218 | $rowAlreadyExists = self::doesRowAlreadyExist( $cdb, $title, $tableName, $tableFieldValues, $tableSchema ); |
219 | if ( $rowAlreadyExists ) { |
220 | $numExistingValues = 0; |
221 | } |
222 | } |
223 | if ( $numExistingValues > 0 ) { |
224 | if ( $fieldDescription->mIsMandatory ) { |
225 | return "Cannot store mandatory field \"$fieldName\" as it contains a duplicate value."; |
226 | } |
227 | $tableFieldValues[$fieldName] = null; |
228 | } |
229 | } |
230 | if ( $fieldDescription->mRegex != null && !preg_match( '/^' . $fieldDescription->mRegex . '$/', $fieldValue ) ) { |
231 | if ( $fieldDescription->mIsMandatory ) { |
232 | return "Cannot store mandatory field \"$fieldName\" as the value does not match the field's regex constraint."; |
233 | } |
234 | $tableFieldValues[$fieldName] = null; |
235 | } |
236 | } |
237 | } |
238 | |
239 | public static function getDateValueAndPrecision( $dateStr, $fieldType ) { |
240 | $precision = null; |
241 | |
242 | // Special handling if it's just a year. If it's a number and |
243 | // less than 8 digits, assume it's a year (hey, it could be a |
244 | // very large BC year). If it's 8 digits, it's probably a full |
245 | // date in the form YYYYMMDD. |
246 | if ( ctype_digit( $dateStr ) && strlen( $dateStr ) < 8 ) { |
247 | // Add a fake date - it will get ignored later. |
248 | return [ "$dateStr-01-01", self::YEAR_ONLY ]; |
249 | } |
250 | |
251 | // Determine if there's a month but no day. There's no ideal |
252 | // way to do this, so: we'll just look for the total number of |
253 | // spaces, slashes and dashes, and if there's exactly one |
254 | // altogether, we'll guess that it's a month only. |
255 | $numSpecialChars = substr_count( $dateStr, ' ' ) + |
256 | substr_count( $dateStr, '/' ) + substr_count( $dateStr, '-' ); |
257 | if ( $numSpecialChars == 1 ) { |
258 | // No need to add anything - PHP will set it to the |
259 | // first of the month. |
260 | $precision = self::MONTH_ONLY; |
261 | } else { |
262 | // We have at least a full date. |
263 | if ( $fieldType == 'Date' ) { |
264 | $precision = self::DATE_ONLY; |
265 | } |
266 | } |
267 | |
268 | $seconds = strtotime( $dateStr ); |
269 | if ( $seconds === false ) { |
270 | return [ null, null ]; |
271 | } |
272 | // If the precision has already been set, then we know it |
273 | // doesn't include a time value - we can set the value already. |
274 | if ( $precision != null ) { |
275 | // Put into YYYY-MM-DD format. |
276 | return [ date( 'Y-m-d', $seconds ), $precision ]; |
277 | } |
278 | |
279 | // It's a Datetime field, which may or may not have a time - |
280 | // check for that now. |
281 | $datePortion = date( 'Y-m-d', $seconds ); |
282 | $timePortion = date( 'G:i:s', $seconds ); |
283 | // If it's not right at midnight, there's definitely a time |
284 | // there. |
285 | $precision = self::DATE_AND_TIME; |
286 | if ( $timePortion !== '0:00:00' ) { |
287 | return [ $datePortion . ' ' . $timePortion, $precision ]; |
288 | } |
289 | |
290 | // It's midnight, so chances are good that there was no time |
291 | // specified, but how do we know for sure? |
292 | // Slight @HACK - look for either "00" or "AM" (or "am") in the |
293 | // original date string. If neither one is there, there's |
294 | // probably no time. |
295 | if ( strpos( $dateStr, '00' ) === false && |
296 | strpos( $dateStr, 'AM' ) === false && |
297 | strpos( $dateStr, 'am' ) === false ) { |
298 | $precision = self::DATE_ONLY; |
299 | } |
300 | // Either way, we just need the date portion. |
301 | return [ $datePortion, $precision ]; |
302 | } |
303 | |
304 | public static function storeAllData( $title, $tableName, $tableFieldValues, $tableSchema ) { |
305 | $pageID = $title->getArticleID(); |
306 | $pageName = $title->getPrefixedText(); |
307 | $pageTitle = $title->getText(); |
308 | $pageNamespace = $title->getNamespace(); |
309 | |
310 | foreach ( $tableSchema->mFieldDescriptions as $fieldName => $fieldDescription ) { |
311 | // If it's null or not set, skip this value. |
312 | if ( !array_key_exists( $fieldName, $tableFieldValues ) ) { |
313 | continue; |
314 | } |
315 | $curValue = $tableFieldValues[$fieldName]; |
316 | if ( $curValue === null ) { |
317 | continue; |
318 | } |
319 | |
320 | $valueArray = $fieldDescription->prepareAndValidateValue( $curValue ); |
321 | $tableFieldValues[$fieldName] = $valueArray['value']; |
322 | if ( array_key_exists( 'precision', $valueArray ) ) { |
323 | $tableFieldValues[$fieldName . '__precision'] = $valueArray['precision']; |
324 | } |
325 | } |
326 | |
327 | // Add the "metadata" field values. |
328 | $tableFieldValues['_pageName'] = $pageName; |
329 | $tableFieldValues['_pageTitle'] = $pageTitle; |
330 | $tableFieldValues['_pageNamespace'] = $pageNamespace; |
331 | $tableFieldValues['_pageID'] = $pageID; |
332 | |
333 | // Allow other hooks to modify the values. |
334 | MediaWikiServices::getInstance()->getHookContainer()->run( 'CargoBeforeStoreData', [ $title, $tableName, &$tableSchema, &$tableFieldValues ] ); |
335 | |
336 | $cdb = CargoUtils::getDB(); |
337 | |
338 | // Somewhat of a @HACK - recreating a Cargo table from the web |
339 | // interface can lead to duplicate rows, due to the use of jobs. |
340 | // So before we store this data, check if a row with this |
341 | // exact set of data is already in the database. If it is, just |
342 | // ignore this #cargo_store call. |
343 | // This is not ideal, because there can be valid duplicate |
344 | // data - a page can have multiple calls to the same template, |
345 | // with identical data, for various reasons. However, that's |
346 | // a very rare case, while unwanted code duplication is |
347 | // unfortunately a common case. So until there's a real |
348 | // solution, this workaround will be helpful. |
349 | $rowAlreadyExists = self::doesRowAlreadyExist( $cdb, $title, $tableName, $tableFieldValues, $tableSchema ); |
350 | if ( $rowAlreadyExists ) { |
351 | return; |
352 | } |
353 | |
354 | // The _position field was only added to list tables in Cargo |
355 | // 2.1, which means that any list table last created or |
356 | // re-created before then will not have that field. How to know |
357 | // whether to populate that field? We go to the first list |
358 | // table for this main table (there may be more than one), query |
359 | // that field, and see whether it throws an exception. (We'll |
360 | // assume that either all the list tables for this main table |
361 | // have a _position field, or none do.) |
362 | $hasPositionField = true; |
363 | foreach ( $tableSchema->mFieldDescriptions as $fieldName => $fieldDescription ) { |
364 | if ( $fieldDescription->mIsList ) { |
365 | $listFieldTableName = $tableName . '__' . $fieldName; |
366 | try { |
367 | $cdb->select( $listFieldTableName, 'COUNT(' . |
368 | $cdb->addIdentifierQuotes( '_position' ) . ')' ); |
369 | } catch ( Exception $e ) { |
370 | $hasPositionField = false; |
371 | } |
372 | break; |
373 | } |
374 | } |
375 | |
376 | // We put the retrieval of the row ID, and the saving of the new row, into a |
377 | // single DB transaction, to avoid "collisions". |
378 | $cdb->begin(); |
379 | |
380 | $maxID = $cdb->selectField( $tableName, |
381 | 'MAX(' . $cdb->addIdentifierQuotes( '_ID' ) . ')' ); |
382 | $curRowID = $maxID + 1; |
383 | $tableFieldValues['_ID'] = $curRowID; |
384 | $fieldTableFieldValues = []; |
385 | |
386 | // For each field that holds a list of values, also add its |
387 | // values to its own table; and rename the actual field. |
388 | foreach ( $tableSchema->mFieldDescriptions as $fieldName => $fieldDescription ) { |
389 | if ( !array_key_exists( $fieldName, $tableFieldValues ) ) { |
390 | continue; |
391 | } |
392 | $fieldType = $fieldDescription->mType; |
393 | if ( $fieldDescription->mIsList ) { |
394 | $fieldTableName = $tableName . '__' . $fieldName; |
395 | $delimiter = $fieldDescription->getDelimiter(); |
396 | $individualValues = explode( $delimiter, $tableFieldValues[$fieldName] ?? '' ); |
397 | $valueNum = 1; |
398 | foreach ( $individualValues as $individualValue ) { |
399 | $individualValue = trim( $individualValue ); |
400 | // Ignore blank values. |
401 | if ( $individualValue == '' ) { |
402 | continue; |
403 | } |
404 | $fieldValues = [ |
405 | '_rowID' => $curRowID, |
406 | '_value' => $individualValue |
407 | ]; |
408 | if ( $hasPositionField ) { |
409 | $fieldValues['_position'] = $valueNum++; |
410 | } |
411 | if ( $fieldDescription->isDateOrDatetime() ) { |
412 | [ $dateValue, $precision ] = self::getDateValueAndPrecision( $individualValue, $fieldType ); |
413 | $fieldValues['_value'] = $dateValue; |
414 | $fieldValues['_value__precision'] = $precision; |
415 | } |
416 | // For coordinates, there are two more |
417 | // fields, for latitude and longitude. |
418 | if ( $fieldType == 'Coordinates' ) { |
419 | try { |
420 | [ $latitude, $longitude ] = CargoUtils::parseCoordinatesString( $individualValue ); |
421 | } catch ( MWException $e ) { |
422 | continue; |
423 | } |
424 | $fieldValues['_lat'] = $latitude; |
425 | $fieldValues['_lon'] = $longitude; |
426 | } |
427 | // We could store these values in the DB |
428 | // now, but we'll do it later, to keep |
429 | // the transaction as short as possible. |
430 | $fieldTableFieldValues[] = [ $fieldTableName, $fieldValues ]; |
431 | } |
432 | |
433 | // Now rename the field. |
434 | $tableFieldValues[$fieldName . '__full'] = $tableFieldValues[$fieldName]; |
435 | unset( $tableFieldValues[$fieldName] ); |
436 | } elseif ( $fieldType == 'Coordinates' ) { |
437 | try { |
438 | [ $latitude, $longitude ] = CargoUtils::parseCoordinatesString( $tableFieldValues[$fieldName] ); |
439 | } catch ( MWException $e ) { |
440 | unset( $tableFieldValues[$fieldName] ); |
441 | continue; |
442 | } |
443 | // Rename the field. |
444 | $tableFieldValues[$fieldName . '__full'] = $tableFieldValues[$fieldName]; |
445 | unset( $tableFieldValues[$fieldName] ); |
446 | $tableFieldValues[$fieldName . '__lat'] = $latitude; |
447 | $tableFieldValues[$fieldName . '__lon'] = $longitude; |
448 | } |
449 | } |
450 | |
451 | // Insert the current data into the main table. |
452 | CargoUtils::escapedInsert( $cdb, $tableName, $tableFieldValues ); |
453 | |
454 | // End transaction and apply DB changes. |
455 | $cdb->commit(); |
456 | |
457 | // Now, store the data for all the "field tables". |
458 | foreach ( $fieldTableFieldValues as $tableNameAndValues ) { |
459 | [ $fieldTableName, $fieldValues ] = $tableNameAndValues; |
460 | CargoUtils::escapedInsert( $cdb, $fieldTableName, $fieldValues ); |
461 | } |
462 | |
463 | // Also insert the names of any "attached" files into the |
464 | // "files" helper table. |
465 | $fileTableName = $tableName . '___files'; |
466 | foreach ( $tableSchema->mFieldDescriptions as $fieldName => $fieldDescription ) { |
467 | $fieldType = $fieldDescription->mType; |
468 | // Only handle this field if it's of type File, and if it exists in the table records. |
469 | if ( $fieldType != 'File' || !array_key_exists( $fieldName, $tableFieldValues ) ) { |
470 | continue; |
471 | } |
472 | if ( $fieldDescription->mIsList ) { |
473 | $delimiter = $fieldDescription->getDelimiter(); |
474 | $individualValues = explode( $delimiter, $tableFieldValues[$fieldName . '__full'] ); |
475 | foreach ( $individualValues as $individualValue ) { |
476 | $individualValue = trim( $individualValue ); |
477 | // Ignore blank values. |
478 | if ( $individualValue == '' ) { |
479 | continue; |
480 | } |
481 | $fileName = CargoUtils::removeNamespaceFromFileName( $individualValue ); |
482 | $fieldValues = [ |
483 | '_pageName' => $pageName, |
484 | '_pageID' => $pageID, |
485 | '_fieldName' => $fieldName, |
486 | '_fileName' => $fileName |
487 | ]; |
488 | CargoUtils::escapedInsert( $cdb, $fileTableName, $fieldValues ); |
489 | } |
490 | } else { |
491 | $fullFileName = $tableFieldValues[$fieldName]; |
492 | if ( $fullFileName == '' ) { |
493 | continue; |
494 | } |
495 | $fileName = CargoUtils::removeNamespaceFromFileName( $fullFileName ); |
496 | $fieldValues = [ |
497 | '_pageName' => $pageName, |
498 | '_pageID' => $pageID, |
499 | '_fieldName' => $fieldName, |
500 | '_fileName' => $fileName |
501 | ]; |
502 | CargoUtils::escapedInsert( $cdb, $fileTableName, $fieldValues ); |
503 | } |
504 | } |
505 | } |
506 | |
507 | /** |
508 | * Determines whether a row with the specified set of values already |
509 | * exists in the specified Cargo table. |
510 | */ |
511 | public static function doesRowAlreadyExist( $cdb, $title, $tableName, $tableFieldValues, $tableSchema ) { |
512 | $pageID = $title->getArticleID(); |
513 | $tableFieldValuesForCheck = [ $cdb->addIdentifierQuotes( '_pageID' ) => $pageID ]; |
514 | foreach ( $tableSchema->mFieldDescriptions as $fieldName => $fieldDescription ) { |
515 | if ( !array_key_exists( $fieldName, $tableFieldValues ) ) { |
516 | continue; |
517 | } |
518 | if ( $fieldDescription->mIsList || $fieldDescription->mType == 'Coordinates' ) { |
519 | $quotedFieldName = $cdb->addIdentifierQuotes( $fieldName . '__full' ); |
520 | } else { |
521 | $quotedFieldName = $cdb->addIdentifierQuotes( $fieldName ); |
522 | } |
523 | $fieldValue = $tableFieldValues[$fieldName]; |
524 | |
525 | if ( in_array( $fieldDescription->mType, [ 'Text', 'Wikitext', 'Searchtext' ] ) ) { |
526 | // @HACK - for some reason, there are times |
527 | // when, for long values, the check only works |
528 | // if there's some kind of limit in place. |
529 | // Rather than delve into that, we'll just |
530 | // make sure to only check a (relatively large) |
531 | // substring - which should be good enough. |
532 | $fieldSize = 1000; |
533 | } else { |
534 | $fieldSize = $fieldDescription->getFieldSize(); |
535 | } |
536 | |
537 | if ( $fieldValue === null ) { |
538 | // Do nothing. |
539 | } elseif ( $fieldValue === '' ) { |
540 | // Needed for correct SQL handling of blank values, for some reason. |
541 | $fieldValue = null; |
542 | } elseif ( $fieldSize != null && strlen( $fieldValue ) > $fieldSize ) { |
543 | // In theory, this SUBSTR() call is not needed, |
544 | // since the value stored in the DB won't be |
545 | // greater than this size. But that's not |
546 | // always true - there's the hack mentioned |
547 | // above, plus some other cases. |
548 | $quotedFieldName = "SUBSTR($quotedFieldName, 1, $fieldSize)"; |
549 | $fieldValue = mb_substr( $fieldValue, 0, $fieldSize ); |
550 | } |
551 | |
552 | $tableFieldValuesForCheck[$quotedFieldName] = $fieldValue; |
553 | } |
554 | $count = $cdb->selectRowCount( $tableName, '*', $tableFieldValuesForCheck, __METHOD__ ); |
555 | return ( $count > 0 ); |
556 | } |
557 | } |