| Server IP : 127.0.1.1 / Your IP : 216.73.216.60 Web Server : Apache/2.4.58 (Ubuntu) System : Linux nepub 6.8.0-88-generic #89-Ubuntu SMP PREEMPT_DYNAMIC Sat Oct 11 01:02:46 UTC 2025 x86_64 User : root ( 0) PHP Version : 8.2.30 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /var/www/html/public_html/lib/pkp/classes/metadata/ |
Upload File : |
<?php
/**
* @file classes/metadata/MetadataProperty.inc.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.
*/
// literal values (plain)
define('METADATA_PROPERTY_TYPE_STRING', 0x01);
// literal values (typed)
define('METADATA_PROPERTY_TYPE_DATE', 0x02); // This is W3CDTF encoding without time (YYYY[-MM[-DD]])!
define('METADATA_PROPERTY_TYPE_INTEGER', 0x03);
// non-literal value string from a controlled vocabulary
define('METADATA_PROPERTY_TYPE_VOCABULARY', 0x04);
// non-literal value URI
define('METADATA_PROPERTY_TYPE_URI', 0x05);
// non-literal value pointing to a separate description set instance (=another MetadataRecord object)
define('METADATA_PROPERTY_TYPE_COMPOSITE', 0x06);
// allowed cardinality of statements for a given property type in a meta-data schema
define('METADATA_PROPERTY_CARDINALITY_ONE', 0x01);
define('METADATA_PROPERTY_CARDINALITY_MANY', 0x02);
class MetadataProperty {
/** @var string property name */
var $_name;
/** @var string a translation id */
var $_displayName;
/** @var int the resource types that can be described with this property */
var $_assocTypes;
/** @var array allowed property types */
var $_allowedTypes;
/** @var boolean flag that defines whether the property can be translated */
var $_translated;
/** @var integer property cardinality */
var $_cardinality;
/** @var string validation message */
var $_validationMessage;
/** @var boolean */
var $_mandatory;
/**
* Constructor
* @param $name string the unique name of the property within a meta-data schema (can be a property URI)
* @param $assocTypes array an array of integers that define the application entities that can
* be described with this property.
* @param $allowedTypes mixed must be a scalar or an array with the supported types, default: METADATA_PROPERTY_TYPE_STRING
* @param $translated boolean whether the property may have various language versions, default: false
* @param $cardinality integer must be on of the supported cardinalities, default: METADATA_PROPERTY_CARDINALITY_ONE
* @param $displayName string
* @param $validationMessage string A string that can be displayed in case a user tries to set an invalid value for this property.
* @param $mandatory boolean Is this a mandatory property within the schema?
*/
function __construct($name, $assocTypes = array(), $allowedTypes = METADATA_PROPERTY_TYPE_STRING,
$translated = false, $cardinality = 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 = array($allowedTypes);
}
// Validate types
$canonicalizedTypes = array();
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 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 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 = (boolean)$translated;
$this->_cardinality = (integer)$cardinality;
$this->_displayName = (string)$displayName;
$this->_validationMessage = (string)$validationMessage;
$this->_mandatory = (boolean)$mandatory;
}
/**
* Get the name
* @return string
*/
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
*/
function getId() {
// Replace special characters in XPath-like names
// as 'person-group[@person-group-type="author"]'.
$from = array(
'[', ']', '@', '"', '='
);
$to = array(
'-', '', '', '', '-'
);
$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
*/
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.
*/
function &getAssocTypes() {
return $this->_assocTypes;
}
/**
* Get the allowed type
* @return integer
*/
function getAllowedTypes() {
return $this->_allowedTypes;
}
/**
* Is this property translated?
* @return boolean
*/
function getTranslated() {
return $this->_translated;
}
/**
* Get the cardinality
* @return integer
*/
function getCardinality() {
return $this->_cardinality;
}
/**
* Get the validation message
* @return string
*/
function getValidationMessage() {
return $this->_validationMessage;
}
/**
* Is this property mandatory?
* @return boolean
*/
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 $value mixed the input to be validated
* @param $locale string 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.
*/
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 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 is_a($value, '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
list($assocType, $assocId) = $valueParts;
if (!(is_numeric($assocType) && is_numeric($assocId))) break 2; // break the outer switch
$assocType = (integer)$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 array(METADATA_PROPERTY_TYPE_COMPOSITE => $assocType);
break;
case 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
list($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 array(METADATA_PROPERTY_TYPE_VOCABULARY => $allowedTypeParam);
}
}
if (is_integer($value)) {
// Validate with controlled vocabulary validator
import('lib.pkp.classes.validation.ValidatorControlledVocab');
$validator = new ValidatorControlledVocab($symbolic, $assocType, $assocId);
if ($validator->isValid($value)) {
return array(METADATA_PROPERTY_TYPE_VOCABULARY => $allowedTypeParam);
}
}
break;
case METADATA_PROPERTY_TYPE_URI:
import('lib.pkp.classes.validation.ValidatorFactory');
$validator = ValidatorFactory::make(
array('uri' => $value),
array('uri' => 'url')
);
if (!$validator->fails()) {
return array(METADATA_PROPERTY_TYPE_URI => null);
}
break;
case 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
list($year, $month, $day) = $dateParts;
// Validate the date (only leap days will pass unnoticed ;-) )
// Who invented this argument order?
if (checkdate($month, $day, $year)) return array(METADATA_PROPERTY_TYPE_DATE => null);
break;
case METADATA_PROPERTY_TYPE_INTEGER:
if (is_integer($value)) return array(METADATA_PROPERTY_TYPE_INTEGER => null);
break;
case METADATA_PROPERTY_TYPE_STRING:
if (is_string($value)) return array(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
*/
static function getSupportedTypes() {
static $_supportedTypes = array(
METADATA_PROPERTY_TYPE_COMPOSITE,
METADATA_PROPERTY_TYPE_VOCABULARY,
METADATA_PROPERTY_TYPE_URI,
METADATA_PROPERTY_TYPE_DATE,
METADATA_PROPERTY_TYPE_INTEGER,
METADATA_PROPERTY_TYPE_STRING
);
return $_supportedTypes;
}
/**
* Return supported cardinalities
* @return array supported cardinalities
*/
static function getSupportedCardinalities() {
static $_supportedCardinalities = array(
METADATA_PROPERTY_CARDINALITY_ONE,
METADATA_PROPERTY_CARDINALITY_MANY
);
return $_supportedCardinalities;
}
}