[ 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: MetadataDescription.php
<?php /** * @file classes/metadata/MetadataDescription.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 MetadataDescription * * @ingroup metadata * * @see MetadataProperty * @see MetadataRecord * @see MetadataSchema * * @brief Class modeling a description (DCMI abstract model) or subject- * predicate-object graph (RDF). This class and its children provide * meta-data (DCMI abstract model: statements of property-value pairs, * RDF: assertions of predicate-object pairs) about a given PKP application * entity instance (DCMI abstract model: described resource, RDF: subject). * * This class has primarily been designed to describe journals, journal * issues, articles, conferences, conference proceedings (conference papers), * monographs (books), monograph components (book chapters) or citations. * * It is, however, flexible enough to be extended to describe any * application entity in the future. Descriptions can be retrieved from * any application object that implements the MetadataProvider interface. * * Special attention has been paid to the compatibility of the class * implementation with the implementation of several meta-data standards * that we consider especially relevant to our use cases. * * We distinguish two main use cases for meta-data: discovery and delivery * of described resources. We have chosen the element-citation tag from the * NLM standard <http://dtd.nlm.nih.gov/publishing/tag-library/3.0/n-8xa0.html> * as our primary representation of delivery meta-data and dcterms * <http://dublincore.org/documents/dcmi-terms/> as our primary * representation of discovery meta-data. * * Our specific use of meta-data has important implications and determines * our design goals: * * Neither NLM-citation nor dcterms have been designed with an object * oriented encoding in mind. NLM-citation is usually XML encoded * while typical dcterms encodings are HTML meta-tags, RDF or XML. * * We believe that trying to implement a super-set of meta-data * standards ("least common denominator" or super-schema approach) * is fundamentally flawed as meta-data standards are always * developed with specific use-cases in mind that require potentially * incompatible data properties or encodings. * * Although we think that NLM-citation and dcterms are sensible default * meta-data schemes our design should remain flexible enough for * users to implement and use other schemes as an internal meta-data * standard. * * We have to make sure that we can easily extract/inject meta-data * from/to PKP application objects. * * We have to avoid code duplication to keep maintenance cost under * control. * * We have to minimize the "impedance mismatch" between our own * object oriented encoding and fully standard compliant external * encodings (i.e. XML, RDF, HTML meta-tags, ...) to allow for easy * conversion between encodings. * * We have to make sure that we can switch between internal and * external encodings without any data loss. * * We have to make sure that crosswalks to and from other important * meta-data standards (e.g. OpenURL variants, MODS, MARC) can be * performed in a well-defined and easy way while minimizing data * loss. * * We have to make sure that we can support qualified fields (e.g. * qualified DC). * * We have to make sure that we can support RDF triples. * * We took the following design decisions to achieve these goals: * * We only implement properties that are justified by strong real-world * use-cases. We recognize that the limiting factor is not the data that * we could represent but the data we actually have. This is not determined * by the chosen standard but by the PKP application objects we want to * represent. Additional meta-data properties/predicates can be added as * required. * * We do adapt data structures as long as we can make sure that a * fully standard compliant encoding can always be re-constructed. This * is especially true for NLM-citation which is designed with * XML in mind and therefore uses hierarchical constructs that are * difficult to represent in an OO class model. * This means that our meta-data framework only supports (nested) key/ * value-based schemas which can however be converted to hierarchical * representations. * * We borrow class and property names from the DCMI abstract model as * the terms used there provide better readability for developers less * acquainted with formal model theory. We'll, however, make sure that * data can easily be RDF encoded within our data model. * * Data validation must ensure that meta-data always complies with a * specific meta-data standard. As we are speaking about an object * oriented encoding that is not defined in the original standard, we * define compliance as "roundtripability". This means we must be able * to convert our object oriented data encoding to a fully standard * compliant encoding and back without any data loss. */ namespace PKP\metadata; use APP\core\Application; use PKP\facades\Locale; class MetadataDescription extends \PKP\core\DataObject { public const METADATA_DESCRIPTION_REPLACE_ALL = 1; public const METADATA_DESCRIPTION_REPLACE_PROPERTIES = 2; public const METADATA_DESCRIPTION_REPLACE_NOTHING = 3; public const METADATA_DESCRIPTION_UNKNOWN_LOCALE = 'unknown'; /** @var string fully qualified class name of the meta-data schema this description complies to */ public $_metadataSchemaName; /** @var MetadataSchema the schema this description complies to */ public $_metadataSchema; /** @var int association type (the type of the described resource) */ public $_assocType; /** @var int association id (the identifier of the described resource) */ public $_assocId; /** * @var string an (optional) display name that describes the contents * of this meta-data description to the end user. */ public $_displayName; /** * @var int sequence id used when saving several descriptions * of the same subject. */ public $_seq; /** * Constructor */ public function __construct($metadataSchemaName, $assocType) { assert(is_string($metadataSchemaName) && is_integer($assocType)); parent::__construct(); $this->_metadataSchemaName = $metadataSchemaName; $this->_assocType = $assocType; } // // Get/set methods // /** * Get the fully qualified class name of * the supported meta-data schema. */ public function getMetadataSchemaName() { return $this->_metadataSchemaName; } /** * Get the metadata schema * * @return MetadataSchema */ public function &getMetadataSchema() { // Lazy-load the meta-data schema if this has // not been done before. if (is_null($this->_metadataSchema)) { $metadataSchemaName = $this->getMetadataSchemaName(); if (preg_match('/^[a-zA-Z0-9_.]+$/', $metadataSchemaName)) { // DEPRECATED as of 3.4.0: non-PSR classloading pkp/pkp-lib#8186 $this->_metadataSchema = & instantiate($metadataSchemaName, \PKP\metadata\MetadataSchema::class); } elseif (class_exists($metadataSchemaName)) { $this->_metadataSchema = new $metadataSchemaName(); } if (! $this->_metadataSchema instanceof \PKP\metadata\MetadataSchema) { throw new \Exception('Unexpected metadata schema class!'); } } return $this->_metadataSchema; } /** * Get the association type (described resource type) * * @return int */ public function getAssocType() { return $this->_assocType; } /** * Get the association id (described resource identifier) * * @return int */ public function getAssocId() { return $this->_assocId; } /** * Set the association id (described resource identifier) * * @param int $assocId */ public function setAssocId($assocId) { $this->_assocId = $assocId; } /** * Construct a meta-data application entity id * (described resource id / subject id) for * this meta-data description object. * * @return string */ public function getAssoc() { $assocType = $this->getAssocType(); $assocId = $this->getAssocId(); assert(isset($assocType) && isset($assocId)); return $assocType . ':' . $assocId; } /** * Set the (optional) display name * * @param string $displayName */ public function setDisplayName($displayName) { $this->_displayName = $displayName; } /** * Get the (optional) display name * * @return string */ public function getDisplayName() { return $this->_displayName; } /** * Set the sequence id * * @param int $seq */ public function setSequence($seq) { $this->_seq = $seq; } /** * Get the sequence id * * @return int */ public function getSequence() { return $this->_seq; } /** * Add a meta-data statement. Statements can only be added * for properties that are part of the meta-data schema. This * method will also check the validity of the value for the * given property before adding the statement. * * @param string $propertyName The name of the property * @param mixed $value The value to be assigned to the property * @param string $locale * @param bool $replace whether to replace an existing statement * * @return bool true if a valid statement was added, otherwise false */ public function addStatement($propertyName, $value, $locale = null, $replace = false) { // Check the property $property = & $this->getProperty($propertyName); if (is_null($property)) { return false; } assert($property instanceof \PKP\metadata\MetadataProperty); // Check that the property is allowed for the described resource if (!in_array($this->_assocType, $property->getAssocTypes())) { return false; } // Handle translation $translated = $property->getTranslated(); if (isset($locale) && !$translated) { return false; } if (!isset($locale) && $translated) { // Retrieve the current locale $locale = Locale::getLocale(); } // Check that the value is compliant with the property specification if ($property->isValid($value, $locale) === false) { return false; } // Handle cardinality $existingValue = & $this->getStatement($propertyName, $locale); switch ($property->getCardinality()) { case MetadataProperty::METADATA_PROPERTY_CARDINALITY_ONE: if (isset($existingValue) && !$replace) { return false; } $newValue = $value; break; case MetadataProperty::METADATA_PROPERTY_CARDINALITY_MANY: if (isset($existingValue) && !$replace) { assert(is_array($existingValue)); $newValue = $existingValue; array_push($newValue, $value); } else { $newValue = [$value]; } break; default: assert(false); } // Add the value $this->setData($propertyName, $newValue, $locale); return true; } /** * Remove statement. If the property has cardinality 'many' * then all statements for the property will be removed at once. * If the property is translated and the locale is null then * the statements for all locales will be removed. * * @param string $propertyName * @param string $locale * * @return bool true if the statement was found and removed, otherwise false */ public function removeStatement($propertyName, $locale = null) { // Remove the statement if it exists if (isset($propertyName) && $this->hasData($propertyName, $locale)) { $this->setData($propertyName, null, $locale); return true; } return false; } /** * Get all statements * * @return array statements */ public function &getStatements() { // Do not retrieve the data by-ref // otherwise the following unset() // will change internal state. $allData = $this->getAllData(); // Unset data variables that are not statements unset($allData['id']); return $allData; } /** * Get a specific statement * * @param string $propertyName * @param string $locale * * @return mixed a scalar property value or an array of property values * if the cardinality of the property is 'many'. */ public function &getStatement($propertyName, $locale = null) { // Check the property $property = & $this->getProperty($propertyName); assert(isset($property) && $property instanceof \PKP\metadata\MetadataProperty); // Handle translation $translated = $property->getTranslated(); if (!$translated) { assert(is_null($locale)); } if ($translated && !isset($locale)) { // Retrieve the current locale $locale = Locale::getLocale(); } // Retrieve the value return $this->getData($propertyName, $locale); } /** * Returns all translations of a translated property * * @param string $propertyName * * @return array all translations of a given property; if the * property has cardinality "many" then this returns a two-dimensional * array whereby the first key represents the locale and the second * the translated values. */ public function &getStatementTranslations($propertyName) { assert($this->isTranslatedProperty($propertyName)); return $this->getData($propertyName); } /** * Add several statements at once. If one of the statements * is invalid then the meta-data description will remain in its * initial state. * * Properties with a cardinality of 'many' must be passed in as * sub-arrays. * * Translated properties with a cardinality of 'one' must be * passed in as sub-arrays with the locale as a key. * * Translated properties with a cardinality of 'many' must be * passed in as sub-sub-arrays with the locale as the first key. * * @param array $statements statements * @param int $replace one of the allowed replace levels. * * @return bool true if all statements could be added, false otherwise */ public function setStatements(&$statements, $replace = self::METADATA_DESCRIPTION_REPLACE_PROPERTIES) { assert(in_array($replace, $this->_allowedReplaceLevels())); // Make a backup copy of all existing statements. $statementsBackup = $this->getAllData(); if ($replace == self::METADATA_DESCRIPTION_REPLACE_ALL) { // Delete existing statements $emptyArray = []; $this->setAllData($emptyArray); } // Add statements one by one to detect invalid values. foreach ($statements as $propertyName => $content) { assert(!empty($content)); // Transform scalars or translated fields to arrays so that // we can handle properties with different cardinalities in // the same way. if (is_scalar($content) || is_string(key($content))) { $values = [&$content]; } else { $values = & $content; } if ($replace == self::METADATA_DESCRIPTION_REPLACE_PROPERTIES) { $replaceProperty = true; } else { $replaceProperty = false; } $valueIndex = 0; foreach ($values as $value) { $firstValue = ($valueIndex == 0) ? true : false; // Is this a translated property? if (is_array($value)) { foreach ($value as $locale => $translation) { // Handle cardinality many and one in the same way if (is_scalar($translation)) { $translationValues = [&$translation]; } else { $translationValues = & $translation; } $translationIndex = 0; foreach ($translationValues as $translationValue) { $firstTranslation = ($translationIndex == 0) ? true : false; // Add a statement (replace existing statement if any) if (!($this->addStatement($propertyName, $translationValue, $locale, $firstTranslation && $replaceProperty))) { $this->setAllData($statementsBackup); return false; } unset($translationValue); $translationIndex++; } unset($translationValues); } unset($translation); } else { // Add a statement (replace existing statement if any) if (!($this->addStatement($propertyName, $value, null, $firstValue && $replaceProperty))) { $this->setAllData($statementsBackup); return false; } } unset($value); $valueIndex++; } unset($values); } return true; } /** * Convenience method that returns the properties of * the underlying meta-data schema. * * @return array an array of MetadataProperties */ public function &getProperties() { $metadataSchema = & $this->getMetadataSchema(); return $metadataSchema->getProperties(); } /** * Convenience method that returns a property from * the underlying meta-data schema. * * @param string $propertyName * * @return MetadataProperty */ public function &getProperty($propertyName) { $metadataSchema = & $this->getMetadataSchema(); return $metadataSchema->getProperty($propertyName); } /** * Convenience method that returns a property id * the underlying meta-data schema. * * @param string $propertyName * * @return string */ public function getNamespacedPropertyId($propertyName) { $metadataSchema = & $this->getMetadataSchema(); return $metadataSchema->getNamespacedPropertyId($propertyName); } /** * Convenience method that returns the valid * property names of the underlying meta-data schema. * * @return array an array of string values representing valid property names */ public function getPropertyNames() { $metadataSchema = & $this->getMetadataSchema(); return $metadataSchema->getPropertyNames(); } /** * Convenience method that returns the names of properties with a * given data type of the underlying meta-data schema. * * @param string $propertyType * * @return array an array of string values representing valid property names */ public function getPropertyNamesByType($propertyType) { $metadataSchema = & $this->getMetadataSchema(); return $metadataSchema->getPropertyNamesByType($propertyType); } /** * Returns an array of property names for * which statements exist. * * @return array an array of string values representing valid property names */ public function getSetPropertyNames() { return array_keys($this->getStatements()); } /** * Convenience method that checks the existence * of a property in the underlying meta-data schema. * * @param string $propertyName * * @return bool */ public function hasProperty($propertyName) { $metadataSchema = & $this->getMetadataSchema(); return $metadataSchema->hasProperty($propertyName); } /** * Check the existence of a statement for the given property. * * @param string $propertyName * * @return bool */ public function hasStatement($propertyName) { $statements = & $this->getStatements(); return (isset($statements[$propertyName])); } /** * Convenience method that checks whether a given property * is translated. * * @param string $propertyName * * @return bool */ public function isTranslatedProperty($propertyName) { $property = $this->getProperty($propertyName); assert($property instanceof \PKP\metadata\MetadataProperty); return $property->getTranslated(); } // // Private helper methods // /** * The allowed replace levels for the * setStatements() method. */ public static function _allowedReplaceLevels() { static $allowedReplaceLevels = [ self::METADATA_DESCRIPTION_REPLACE_ALL, self::METADATA_DESCRIPTION_REPLACE_PROPERTIES, self::METADATA_DESCRIPTION_REPLACE_NOTHING ]; return $allowedReplaceLevels; } } if (!PKP_STRICT_MODE) { class_alias('\PKP\metadata\MetadataDescription', '\MetadataDescription'); foreach ([ 'METADATA_DESCRIPTION_REPLACE_ALL', 'METADATA_DESCRIPTION_REPLACE_PROPERTIES', 'METADATA_DESCRIPTION_REPLACE_NOTHING', 'METADATA_DESCRIPTION_UNKNOWN_LOCALE' ] as $constantName) { define($constantName, constant('\MetadataDescription::' . $constantName)); } }