[ Mini Kiebo ]
Server: Windows NT DESKTOP-5B8S0D4 6.2 build 9200 (Windows 8 Professional Edition) i586
Path:
D:
/
Backup
/
05122024
/
htdocs
/
jurnal-kesmas
/
lib
/
pkp
/
classes
/
metadata
/
[
Home
]
File: MetadataProperty.php
<?php /** * @file classes/metadata/MetadataProperty.php * * Copyright (c) 2014-2021 Simon Fraser University * Copyright (c) 2000-2021 John Willinsky * Distributed under the GNU GPL v3. For full terms see the file docs/COPYING. * * @class MetadataProperty * * @ingroup metadata * * @see MetadataSchema * @see MetadataRecord * * @brief Class representing metadata properties. It specifies type and cardinality * of a meta-data property (=term, field, ...) and whether the property can * be internationalized. It also provides a validator to test whether input * conforms to the property specification. * * In the DCMI abstract model, this class specifies a property together with its * allowed range and cardinality. * * We also define the resource types (application entities, association types) * that can be described with the property. This allows us to check that only * valid resource associations are made. It also allows us to prepare property * entry forms or displays for a given resource type and integrate these in the * work-flow of the resource. By dynamically adding or removing assoc types, * end users will be able to configure the meta-data fields that they wish to * make available, persist or enter in their application. */ namespace PKP\metadata; use InvalidArgumentException; use PKP\core\PKPString; use PKP\db\DAORegistry; use PKP\validation\ValidatorControlledVocab; use PKP\validation\ValidatorFactory; class MetadataProperty { // literal values (plain) public const METADATA_PROPERTY_TYPE_STRING = 1; // literal values (typed) public const METADATA_PROPERTY_TYPE_DATE = 2; // This is W3CDTF encoding without time (YYYY[-MM[-DD]])! public const METADATA_PROPERTY_TYPE_INTEGER = 3; // non-literal value string from a controlled vocabulary public const METADATA_PROPERTY_TYPE_VOCABULARY = 4; // non-literal value URI public const METADATA_PROPERTY_TYPE_URI = 5; // non-literal value pointing to a separate description set instance (=another MetadataRecord object) public const METADATA_PROPERTY_TYPE_COMPOSITE = 6; // allowed cardinality of statements for a given property type in a meta-data schema public const METADATA_PROPERTY_CARDINALITY_ONE = 1; public const METADATA_PROPERTY_CARDINALITY_MANY = 2; /** @var string property name */ public $_name; /** @var string a translation id */ public $_displayName; /** @var int the resource types that can be described with this property */ public $_assocTypes; /** @var array allowed property types */ public $_allowedTypes; /** @var bool flag that defines whether the property can be translated */ public $_translated; /** @var int property cardinality */ public $_cardinality; /** @var string validation message */ public $_validationMessage; /** @var bool */ public $_mandatory; /** * Constructor * * @param string $name the unique name of the property within a meta-data schema (can be a property URI) * @param array $assocTypes an array of integers that define the application entities that can * be described with this property. * @param mixed $allowedTypes must be a scalar or an array with the supported types, default: METADATA_PROPERTY_TYPE_STRING * @param bool $translated whether the property may have various language versions, default: false * @param int $cardinality must be on of the supported cardinalities, default: METADATA_PROPERTY_CARDINALITY_ONE * @param string $displayName * @param string $validationMessage A string that can be displayed in case a user tries to set an invalid value for this property. * @param bool $mandatory Is this a mandatory property within the schema? */ public function __construct( $name, $assocTypes = [], $allowedTypes = self::METADATA_PROPERTY_TYPE_STRING, $translated = false, $cardinality = self::METADATA_PROPERTY_CARDINALITY_ONE, $displayName = null, $validationMessage = null, $mandatory = false ) { // Validate name and assoc type array if (!is_string($name)) { throw new InvalidArgumentException('$name should be a string.'); } if (!is_array($assocTypes)) { throw new InvalidArgumentException('$assocTypes should be an array.'); } // A single type will be transformed to an // array of types so that we can handle them // uniformly. if (is_scalar($allowedTypes) || count($allowedTypes) == 1) { $allowedTypes = [$allowedTypes]; } // Validate types $canonicalizedTypes = []; foreach ($allowedTypes as $allowedType) { if (is_array($allowedType)) { // We expect an array with a single entry // of the form "type => additional parameter". assert(count($allowedType) == 1); // Reset the array, just in case... reset($allowedType); // Extract the type and the additional parameter $allowedTypeId = key($allowedType); $allowedTypeParam = current($allowedType); } else { // No additional parameter has been set. $allowedTypeId = $allowedType; $allowedTypeParam = null; } // Validate type if (!in_array($allowedTypeId, MetadataProperty::getSupportedTypes())) { throw new InvalidArgumentException('Allowed types must be supported types!'); } // Transform the type array in a // structure that is easy to handle // in for loops. $canonicalizedTypes[$allowedTypeId][] = $allowedTypeParam; // Validate additional type parameter. switch ($allowedTypeId) { case self::METADATA_PROPERTY_TYPE_COMPOSITE: // Validate the assoc id of the composite. if (!is_integer($allowedTypeParam)) { throw new InvalidArgumentException('Allowed type parameter should be an integer.'); } // Properties that allow composite types cannot be translated. if ($translated) { throw new InvalidArgumentException('Properties that allow composite types cannot be translated.'); } break; case self::METADATA_PROPERTY_TYPE_VOCABULARY: // Validate the symbolic name of the vocabulary. if (!is_string($allowedTypeParam)) { throw new InvalidArgumentException('Allowed type parameter should be a string.'); } break; default: // No other types support an additional parameter if (!is_null($allowedTypeParam)) { throw new InvalidArgumentException('An additional parameter was supplied for an unsupported metadata property type.'); } } } // Validate translation and cardinality if (!is_bool($translated)) { throw new InvalidArgumentException('$translated must be a boolean'); } if (!in_array($cardinality, MetadataProperty::getSupportedCardinalities())) { throw new InvalidArgumentException('$cardinality must be a supported cardinality.'); } // Default display name if (is_null($displayName)) { $displayName = 'metadata.property.displayName.' . $name; } if (!is_string($displayName)) { throw new InvalidArgumentException('$displayName must be a string.'); } // Default validation message if (is_null($validationMessage)) { $validationMessage = 'metadata.property.validationMessage.' . $name; } if (!is_string($validationMessage)) { throw new InvalidArgumentException('$validationMessage must be a string.'); } // Initialize the class $this->_name = (string)$name; $this->_assocTypes = & $assocTypes; $this->_allowedTypes = & $canonicalizedTypes; $this->_translated = (bool)$translated; $this->_cardinality = (int)$cardinality; $this->_displayName = (string)$displayName; $this->_validationMessage = (string)$validationMessage; $this->_mandatory = (bool)$mandatory; } /** * Get the name * * @return string */ public function getName() { return $this->_name; } /** * Returns a canonical form of the property * name ready to be used as a property id in an * external context (e.g. Forms or Templates). * * @return string */ public function getId() { // Replace special characters in XPath-like names // as 'person-group[@person-group-type="author"]'. $from = [ '[', ']', '@', '"', '=' ]; $to = [ '-', '', '', '', '-' ]; $propertyId = trim(str_replace($from, $to, $this->getName()), '-'); $propertyId = PKPString::camelize($propertyId); return $propertyId; } /** * Get the translation id representing * the display name of the property. * * @return string */ public function getDisplayName() { return $this->_displayName; } /** * Get the allowed association types * (resources that can be described * with this property) * * @return array a list of integers representing * association types. */ public function &getAssocTypes() { return $this->_assocTypes; } /** * Get the allowed type * * @return int */ public function getAllowedTypes() { return $this->_allowedTypes; } /** * Is this property translated? * * @return bool */ public function getTranslated() { return $this->_translated; } /** * Get the cardinality * * @return int */ public function getCardinality() { return $this->_cardinality; } /** * Get the validation message * * @return string */ public function getValidationMessage() { return $this->_validationMessage; } /** * Is this property mandatory? * * @return bool */ public function getMandatory() { return $this->_mandatory; } // // Public methods // /** * Validate a given input against the property specification * * The given value must validate against at least one of the * allowed types. The first allowed type id will be returned as * validation result. If the given value fits none of the allowed * types, then we'll return 'false'. * * @param mixed $value the input to be validated * @param string $locale the locale to be used for validation * * @return array|boolean an array with a single entry of the format * "type => additional type parameter" against which the value * validated or boolean false if not validated at all. */ public function isValid($value, $locale = null) { // We never accept null values or arrays. if (is_null($value) || is_array($value)) { return false; } // Translate the locale. if (is_null($locale)) { $locale = ''; } // MetadataProperty::getSupportedTypes() returns an ordered // list of possible meta-data types with the most specific // type coming first so that we always correctly identify // specializations (e.g. a date is a specialized string). $allowedTypes = $this->getAllowedTypes(); foreach (MetadataProperty::getSupportedTypes() as $testedType) { if (isset($allowedTypes[$testedType])) { foreach ($allowedTypes[$testedType] as $allowedTypeParam) { // Type specific validation switch ($testedType) { case self::METADATA_PROPERTY_TYPE_COMPOSITE: // Composites can either be represented by a meta-data description // or by a string of the form AssocType:AssocId if the composite // has already been persisted in the database. switch (true) { // Test for MetadataDescription format case $value instanceof \PKP\metadata\MetadataDescription: $assocType = $value->getAssocType(); break; // Test for AssocType:AssocId format case is_string($value): $valueParts = explode(':', $value); if (count($valueParts) != 2) { break 2; } // break the outer switch [$assocType, $assocId] = $valueParts; if (!(is_numeric($assocType) && is_numeric($assocId))) { break 2; } // break the outer switch $assocType = (int)$assocType; break; default: // None of the allowed types break; } // Check that the association type matches // with the allowed association type (which // is configured as an additional type parameter). if (isset($assocType) && $assocType === $allowedTypeParam) { return [self::METADATA_PROPERTY_TYPE_COMPOSITE => $assocType]; } break; case self::METADATA_PROPERTY_TYPE_VOCABULARY: // Interpret the type parameter of this type like this: // symbolic[:assoc-type:assoc-id]. If no assoc type/id are // given then we assume :0:0 to represent site-wide vocabs. $vocabNameParts = explode(':', $allowedTypeParam); $vocabNamePartsCount = count($vocabNameParts); switch ($vocabNamePartsCount) { case 1: // assume a site-wide vocabulary $symbolic = $allowedTypeParam; $assocType = $assocId = 0; break; case 3: // assume a context-specific vocabulary [$symbolic, $assocType, $assocId] = $vocabNameParts; break; default: // Invalid configuration assert(false); } if (is_string($value)) { // Try to translate the string value into a controlled vocab entry $controlledVocabEntryDao = DAORegistry::getDAO('ControlledVocabEntryDAO'); /** @var ControlledVocabEntryDAO $controlledVocabEntryDao */ if (!is_null($controlledVocabEntryDao->getBySetting($value, $symbolic, $assocType, $assocId, 'name', $locale))) { // The string was successfully translated so mark it as "valid". return [self::METADATA_PROPERTY_TYPE_VOCABULARY => $allowedTypeParam]; } } if (is_integer($value)) { // Validate with controlled vocabulary validator $validator = new ValidatorControlledVocab($symbolic, $assocType, $assocId); if ($validator->isValid($value)) { return [self::METADATA_PROPERTY_TYPE_VOCABULARY => $allowedTypeParam]; } } break; case self::METADATA_PROPERTY_TYPE_URI: $validator = ValidatorFactory::make( ['uri' => $value], ['uri' => 'url'] ); if (!$validator->fails()) { return [self::METADATA_PROPERTY_TYPE_URI => null]; } break; case self::METADATA_PROPERTY_TYPE_DATE: // We allow the following patterns: // YYYY-MM-DD, YYYY-MM and YYYY $datePattern = '/^[0-9]{4}(-[0-9]{2}(-[0-9]{2})?)?$/'; if (!preg_match($datePattern, $value)) { break; } // Check whether the given string is really a valid date $dateParts = explode('-', $value); // Set the day and/or month to 1 if not set $dateParts = array_pad($dateParts, 3, 1); // Extract the date parts [$year, $month, $day] = $dateParts; // Validate the date (only leap days will pass unnoticed ;-) ) // Who invented this argument order? if (checkdate($month, $day, $year)) { return [self::METADATA_PROPERTY_TYPE_DATE => null]; } break; case self::METADATA_PROPERTY_TYPE_INTEGER: if (is_integer($value)) { return [self::METADATA_PROPERTY_TYPE_INTEGER => null]; } break; case self::METADATA_PROPERTY_TYPE_STRING: if (is_string($value)) { return [self::METADATA_PROPERTY_TYPE_STRING => null]; } break; default: // Unknown type. As we validate type in the setter, this // should be unreachable code. assert(false); } } } } // Return false if the value didn't validate against any // of the allowed types. return false; } // // Public static methods // /** * Return supported meta-data property types * * NB: These types are sorted from most specific to * most general and will be validated in this order * so that we'll always identify more specific types * as such (see MetadataProperty::isValid() for more * details). * * @return array supported meta-data property types */ public static function getSupportedTypes() { static $_supportedTypes = [ self::METADATA_PROPERTY_TYPE_COMPOSITE, self::METADATA_PROPERTY_TYPE_VOCABULARY, self::METADATA_PROPERTY_TYPE_URI, self::METADATA_PROPERTY_TYPE_DATE, self::METADATA_PROPERTY_TYPE_INTEGER, self::METADATA_PROPERTY_TYPE_STRING ]; return $_supportedTypes; } /** * Return supported cardinalities * * @return array supported cardinalities */ public static function getSupportedCardinalities() { static $_supportedCardinalities = [ self::METADATA_PROPERTY_CARDINALITY_ONE, self::METADATA_PROPERTY_CARDINALITY_MANY ]; return $_supportedCardinalities; } } if (!PKP_STRICT_MODE) { class_alias('\PKP\metadata\MetadataProperty', '\MetadataProperty'); foreach ([ 'METADATA_PROPERTY_TYPE_STRING', 'METADATA_PROPERTY_TYPE_DATE', 'METADATA_PROPERTY_TYPE_INTEGER', 'METADATA_PROPERTY_TYPE_VOCABULARY', 'METADATA_PROPERTY_TYPE_URI', 'METADATA_PROPERTY_TYPE_COMPOSITE', 'METADATA_PROPERTY_CARDINALITY_ONE', 'METADATA_PROPERTY_CARDINALITY_MANY', ] as $constantName) { define($constantName, constant('\MetadataProperty::' . $constantName)); } }