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