Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
0.00% |
0 / 1 |
|
70.00% |
7 / 10 |
CRAP | |
84.72% |
183 / 216 |
CargoFieldDescription | |
0.00% |
0 / 1 |
|
70.00% |
7 / 10 |
117.25 | |
84.72% |
183 / 216 |
newFromString | |
0.00% |
0 / 1 |
20.67 | |
88.14% |
52 / 59 |
|||
newFromDBArray | |
100.00% |
1 / 1 |
14 | |
100.00% |
28 / 28 |
|||
getDelimiter | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
setDelimiter | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
isDateOrDatetime | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
getFieldSize | |
100.00% |
1 / 1 |
4 | |
100.00% |
7 / 7 |
|||
toDBArray | |
100.00% |
1 / 1 |
12 | |
100.00% |
26 / 26 |
|||
prepareAndValidateValue | |
100.00% |
1 / 1 |
27 | |
100.00% |
66 / 66 |
|||
prettyPrintType | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 6 |
|||
prettyPrintTypeAndAttributes | |
0.00% |
0 / 1 |
56 | |
0.00% |
0 / 20 |
<?php | |
/** | |
* CargoFieldDescription - holds the attributes of a single field as defined | |
* in the #cargo_declare parser function. | |
* | |
* @author Yaron Koren | |
* @ingroup Cargo | |
*/ | |
class CargoFieldDescription { | |
public $mType; | |
public $mSize; | |
public $mDependentOn = []; | |
public $mIsList = false; | |
private $mDelimiter; | |
public $mAllowedValues = null; | |
public $mIsMandatory = false; | |
public $mIsUnique = false; | |
public $mRegex = null; | |
public $mIsHidden = false; | |
public $mIsHierarchy = false; | |
public $mHierarchyStructure = null; | |
public $mOtherParams = []; | |
/** | |
* Initializes from a string within the #cargo_declare function. | |
* | |
* @param string $fieldDescriptionStr | |
* @return \CargoFieldDescription|null | |
*/ | |
public static function newFromString( $fieldDescriptionStr ) { | |
$fieldDescription = new CargoFieldDescription(); | |
if ( strpos( strtolower( $fieldDescriptionStr ), 'list' ) === 0 ) { | |
$matches = []; | |
$foundMatch = preg_match( '/[Ll][Ii][Ss][Tt] \((.*)\) [Oo][Ff] (.*)/is', $fieldDescriptionStr, $matches ); | |
if ( !$foundMatch ) { | |
// Return a true error message here? | |
return null; | |
} | |
$fieldDescription->mIsList = true; | |
$fieldDescription->mDelimiter = $matches[1]; | |
$fieldDescriptionStr = $matches[2]; | |
} | |
CargoUtils::validateFieldDescriptionString( $fieldDescriptionStr ); | |
// There may be additional parameters, in/ parentheses. | |
$matches = []; | |
$foundMatch2 = preg_match( '/([^(]*)\s*\((.*)\)/s', $fieldDescriptionStr, $matches ); | |
$allowedValuesParam = ""; | |
if ( $foundMatch2 ) { | |
$fieldDescriptionStr = trim( $matches[1] ); | |
$extraParamsString = $matches[2]; | |
$extraParams = explode( ';', $extraParamsString ); | |
foreach ( $extraParams as $extraParam ) { | |
$extraParamParts = explode( '=', $extraParam, 2 ); | |
if ( count( $extraParamParts ) == 1 ) { | |
$paramKey = strtolower( trim( $extraParamParts[0] ) ); | |
if ( $paramKey == 'hierarchy' ) { | |
$fieldDescription->mIsHierarchy = true; | |
} | |
$fieldDescription->mOtherParams[$paramKey] = true; | |
} else { | |
$paramKey = strtolower( trim( $extraParamParts[0] ) ); | |
$paramValue = trim( $extraParamParts[1] ); | |
if ( $paramKey == 'allowed values' ) { | |
// we do not assign allowed values to fieldDescription here, | |
// because we don't know yet if it's a hierarchy or an enumeration | |
$allowedValuesParam = $paramValue; | |
} elseif ( $paramKey == 'size' ) { | |
$fieldDescription->mSize = $paramValue; | |
} elseif ( $paramKey == 'dependent on' ) { | |
$fieldDescription->mDependentOn = array_map( 'trim', explode( ',', $paramValue ) ); | |
} else { | |
$fieldDescription->mOtherParams[$paramKey] = $paramValue; | |
} | |
} | |
} | |
if ( $allowedValuesParam !== "" ) { | |
$allowedValuesArray = []; | |
if ( $fieldDescription->mIsHierarchy == true ) { | |
// $paramValue contains "*" hierarchy structure | |
CargoUtils::validateHierarchyStructure( trim( $allowedValuesParam ) ); | |
$fieldDescription->mHierarchyStructure = trim( $allowedValuesParam ); | |
// now make the allowed values param similar to the syntax | |
// used by other fields | |
$hierarchyNodesArray = explode( "\n", $allowedValuesParam ); | |
foreach ( $hierarchyNodesArray as $node ) { | |
// Remove prefix of multiple "*" | |
$allowedValuesArray[] = trim( preg_replace( '/^[*]*/', '', $node ) ); | |
} | |
} else { | |
// Replace the comma/delimiter | |
// substitution with a character | |
// that has no chance of being | |
// included in the values list - | |
// namely, the ASCII beep. | |
// The delimiter can't be a | |
// semicolon, because that's | |
// already used to separate | |
// "extra parameters", so just | |
// hardcode it to a semicolon. | |
$delimiter = ','; | |
$allowedValuesStr = str_replace( "\\$delimiter", "\a", $allowedValuesParam ); | |
$allowedValuesTempArray = explode( $delimiter, $allowedValuesStr ); | |
foreach ( $allowedValuesTempArray as $i => $value ) { | |
if ( $value == '' ) { | |
continue; | |
} | |
// Replace beep back with delimiter, trim. | |
$value = str_replace( "\a", $delimiter, trim( $value ) ); | |
$allowedValuesArray[] = $value; | |
} | |
} | |
$fieldDescription->mAllowedValues = $allowedValuesArray; | |
} | |
} | |
// What's left will be the type, hopefully. | |
// Allow any capitalization of the type. | |
$type = ucfirst( strtolower( $fieldDescriptionStr ) ); | |
// The 'URL' type has special capitalization. | |
if ( $type == 'Url' ) { | |
$type = 'URL'; | |
} | |
$fieldDescription->mType = $type; | |
// Validation. | |
if ( $fieldDescription->mType == 'Text' && array_key_exists( 'unique', $fieldDescription->mOtherParams ) ) { | |
throw new MWException( "'unique' is not allowed for fields of type 'Text'." ); | |
} | |
if ( $fieldDescription->mType == 'Boolean' && $fieldDescription->mIsList == true ) { | |
throw new MWException( "Error: 'list' is not allowed for fields of type 'Boolean'." ); | |
} | |
return $fieldDescription; | |
} | |
/** | |
* @param array $descriptionData | |
* @return \CargoFieldDescription | |
*/ | |
public static function newFromDBArray( $descriptionData ) { | |
$fieldDescription = new CargoFieldDescription(); | |
foreach ( $descriptionData as $param => $value ) { | |
if ( $param == 'type' ) { | |
$fieldDescription->mType = $value; | |
} elseif ( $param == 'size' ) { | |
$fieldDescription->mSize = $value; | |
} elseif ( $param == 'dependent on' ) { | |
$fieldDescription->mDependentOn = $value; | |
} elseif ( $param == 'isList' ) { | |
$fieldDescription->mIsList = true; | |
} elseif ( $param == 'delimiter' ) { | |
$fieldDescription->mDelimiter = $value; | |
} elseif ( $param == 'allowedValues' ) { | |
$fieldDescription->mAllowedValues = $value; | |
} elseif ( $param == 'mandatory' ) { | |
$fieldDescription->mIsMandatory = true; | |
} elseif ( $param == 'unique' ) { | |
$fieldDescription->mIsUnique = true; | |
} elseif ( $param == 'regex' ) { | |
$fieldDescription->mRegex = $value; | |
} elseif ( $param == 'hidden' ) { | |
$fieldDescription->mIsHidden = true; | |
} elseif ( $param == 'hierarchy' ) { | |
$fieldDescription->mIsHierarchy = true; | |
} elseif ( $param == 'hierarchyStructure' ) { | |
$fieldDescription->mHierarchyStructure = $value; | |
} else { | |
$fieldDescription->mOtherParams[$param] = $value; | |
} | |
} | |
return $fieldDescription; | |
} | |
public function getDelimiter() { | |
// Make "\n" represent a newline. | |
return str_replace( '\n', "\n", $this->mDelimiter ); | |
} | |
public function setDelimiter( $delimiter ) { | |
$this->mDelimiter = $delimiter; | |
} | |
public function isDateOrDatetime() { | |
return in_array( $this->mType, [ 'Date', 'Start date', 'End date', 'Datetime', 'Start datetime', 'End datetime' ] ); | |
} | |
public function getFieldSize() { | |
if ( $this->isDateOrDatetime() ) { | |
return null; | |
} elseif ( in_array( $this->mType, [ 'Integer', 'Float', 'Rating', 'Boolean', 'Text', 'Wikitext', 'Searchtext' ] ) ) { | |
return null; | |
// This leaves String, Page, etc. - see CargoUtils::fieldTypeToSQLType(). | |
} elseif ( $this->mSize != null ) { | |
return $this->mSize; | |
} else { | |
global $wgCargoDefaultStringBytes; | |
return $wgCargoDefaultStringBytes; | |
} | |
} | |
/** | |
* @return array | |
*/ | |
public function toDBArray() { | |
$descriptionData = []; | |
$descriptionData['type'] = $this->mType; | |
if ( $this->mSize != null ) { | |
$descriptionData['size'] = $this->mSize; | |
} | |
if ( $this->mDependentOn != null ) { | |
$descriptionData['dependent on'] = $this->mDependentOn; | |
} | |
if ( $this->mIsList ) { | |
$descriptionData['isList'] = true; | |
} | |
if ( $this->mDelimiter != null ) { | |
$descriptionData['delimiter'] = $this->mDelimiter; | |
} | |
if ( $this->mAllowedValues != null ) { | |
$descriptionData['allowedValues'] = $this->mAllowedValues; | |
} | |
if ( $this->mIsMandatory ) { | |
$descriptionData['mandatory'] = true; | |
} | |
if ( $this->mIsUnique ) { | |
$descriptionData['unique'] = true; | |
} | |
if ( $this->mRegex != null ) { | |
$descriptionData['regex'] = $this->mRegex; | |
} | |
if ( $this->mIsHidden ) { | |
$descriptionData['hidden'] = true; | |
} | |
if ( $this->mIsHierarchy ) { | |
$descriptionData['hierarchy'] = true; | |
$descriptionData['hierarchyStructure'] = $this->mHierarchyStructure; | |
} | |
foreach ( $this->mOtherParams as $otherParam => $value ) { | |
$descriptionData[$otherParam] = $value; | |
} | |
return $descriptionData; | |
} | |
public function prepareAndValidateValue( $fieldValue ) { | |
// @TODO - also set, and return, an error message and/or code | |
// if the returned value is different from the incoming value. | |
// @TODO - it might make sense to create a new class around | |
// this function, like "CargoFieldValue" - | |
// CargoStore::getDateValueAndPrecision() could move there too. | |
$fieldValue = trim( $fieldValue ); | |
if ( $fieldValue == '' ) { | |
if ( $this->isDateOrDatetime() ) { | |
// If it's a date field, it has to be null, | |
// not blank, for DB storage to work correctly. | |
// Possibly this is true for other types as well. | |
return [ 'value' => null ]; | |
} | |
return [ 'value' => $fieldValue ]; | |
} | |
$newValue = $precision = null; | |
$fieldType = $this->mType; | |
if ( $this->mAllowedValues != null ) { | |
$allowedValues = $this->mAllowedValues; | |
if ( $this->mIsList ) { | |
$delimiter = $this->getDelimiter(); | |
$individualValues = explode( $delimiter, $fieldValue ); | |
$valuesToBeKept = []; | |
foreach ( $individualValues as $individualValue ) { | |
$realIndividualVal = trim( $individualValue ); | |
if ( in_array( $realIndividualVal, $allowedValues ) ) { | |
$valuesToBeKept[] = $realIndividualVal; | |
} | |
} | |
$newValue = implode( $delimiter, $valuesToBeKept ); | |
} else { | |
if ( in_array( $fieldValue, $allowedValues ) ) { | |
$newValue = $fieldValue; | |
} | |
} | |
} | |
if ( $this->isDateOrDatetime() ) { | |
if ( $this->mIsList ) { | |
$delimiter = $this->getDelimiter(); | |
$individualValues = explode( $delimiter, $fieldValue ); | |
// There's unfortunately only one precision | |
// value per field, even if it holds more than | |
// one date - store the most "precise" of the | |
// precision values. | |
$maxPrecision = CargoStore::YEAR_ONLY; | |
$dateValues = []; | |
foreach ( $individualValues as $individualValue ) { | |
$realIndividualVal = trim( $individualValue ); | |
if ( $realIndividualVal == '' ) { | |
continue; | |
} | |
list( $dateValue, $curPrecision ) = CargoStore::getDateValueAndPrecision( $realIndividualVal, $fieldType ); | |
$dateValues[] = $dateValue; | |
if ( $curPrecision < $maxPrecision ) { | |
$maxPrecision = $curPrecision; | |
} | |
} | |
$newValue = implode( $delimiter, $dateValues ); | |
$precision = $maxPrecision; | |
} else { | |
list( $newValue, $precision ) = CargoStore::getDateValueAndPrecision( $fieldValue, $fieldType ); | |
} | |
} elseif ( $fieldType == 'Integer' ) { | |
// Remove digit-grouping character. | |
global $wgCargoDigitGroupingCharacter; | |
if ( $this->mIsList ) { | |
$delimiter = $this->getDelimiter(); | |
if ( $delimiter != $wgCargoDigitGroupingCharacter ) { | |
$fieldValue = str_replace( $wgCargoDigitGroupingCharacter, '', $fieldValue ); | |
} | |
$individualValues = explode( $delimiter, $fieldValue ); | |
foreach ( $individualValues as &$individualValue ) { | |
if ( !is_int( $individualValue ) ) { | |
$individualValue = round( $individualValue ); | |
} | |
} | |
$newValue = implode( $delimiter, $individualValues ); | |
} else { | |
$newValue = str_replace( $wgCargoDigitGroupingCharacter, '', $fieldValue ); | |
if ( !is_int( $newValue ) ) { | |
$newValue = round( $newValue ); | |
} | |
} | |
} elseif ( $fieldType == 'Float' || $fieldType == 'Rating' ) { | |
// Remove digit-grouping character, and change | |
// decimal mark to '.' if it's anything else. | |
global $wgCargoDigitGroupingCharacter; | |
global $wgCargoDecimalMark; | |
$newValue = str_replace( $wgCargoDigitGroupingCharacter, '', $fieldValue ); | |
$newValue = str_replace( $wgCargoDecimalMark, '.', $newValue ); | |
} elseif ( $fieldType == 'Boolean' ) { | |
// True = 1, "yes" | |
// False = 0, "no" | |
$msgForNo = wfMessage( 'htmlform-no' )->text(); | |
if ( $fieldValue === 0 | |
|| $fieldValue === '0' | |
|| strtolower( $fieldValue ) === 'no' | |
|| strtolower( $fieldValue ) == strtolower( $msgForNo ) ) { | |
$newValue = '0'; | |
} else { | |
$newValue = '1'; | |
} | |
} else { | |
$newValue = $fieldValue; | |
} | |
$valueArray = [ 'value' => $newValue ]; | |
if ( $precision !== null ) { | |
$valueArray['precision'] = $precision; | |
} | |
return $valueArray; | |
} | |
public function prettyPrintType() { | |
$typeDesc = '<tt>' . $this->mType . '</tt>'; | |
if ( $this->mIsList ) { | |
$delimiter = '<tt>' . $this->mDelimiter . '</tt>'; | |
$typeDesc = wfMessage( 'cargo-cargotables-listof', | |
$typeDesc, $delimiter )->parse(); | |
} | |
return $typeDesc; | |
} | |
public function prettyPrintTypeAndAttributes() { | |
$text = $this->prettyPrintType(); | |
$attributesStrings = []; | |
if ( $this->mIsMandatory ) { | |
$attributesStrings[] = [ wfMessage( 'cargo-cargotables-mandatory' )->text() ]; | |
} | |
if ( $this->mIsUnique ) { | |
$attributesStrings[] = [ wfMessage( 'cargo-cargotables-unique' )->text() ]; | |
} | |
if ( $this->mAllowedValues !== null ) { | |
$allowedValuesStr = implode( ' · ', $this->mAllowedValues ); | |
$attributesStrings[] = [ wfMessage( 'cargo-cargotables-allowedvalues' )->text(), | |
$allowedValuesStr ]; | |
} | |
if ( count( $attributesStrings ) == 0 ) { | |
return $text; | |
} | |
$attributeDisplayStrings = []; | |
foreach ( $attributesStrings as $attributesStrs ) { | |
$displayString = '<span class="cargoFieldName">' . | |
$attributesStrs[0] . '</span>'; | |
if ( count( $attributesStrs ) > 1 ) { | |
$displayString .= ' ' . $attributesStrs[1]; | |
} | |
$attributeDisplayStrings[] = $displayString; | |
} | |
$text .= ' (' . implode( '; ', $attributeDisplayStrings ) . ')'; | |
return $text; | |
} | |
} |