| 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/MetadataDescription.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 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.
*/
import('lib.pkp.classes.core.DataObject');
define('METADATA_DESCRIPTION_REPLACE_ALL', 0x01);
define('METADATA_DESCRIPTION_REPLACE_PROPERTIES', 0x02);
define('METADATA_DESCRIPTION_REPLACE_NOTHING', 0x03);
define('METADATA_DESCRIPTION_UNKNOWN_LOCALE', 'unknown');
class MetadataDescription extends DataObject {
/** @var string fully qualified class name of the meta-data schema this description complies to */
var $_metadataSchemaName;
/** @var MetadataSchema the schema this description complies to */
var $_metadataSchema;
/** @var int association type (the type of the described resource) */
var $_assocType;
/** @var int association id (the identifier of the described resource) */
var $_assocId;
/**
* @var string an (optional) display name that describes the contents
* of this meta-data description to the end user.
*/
var $_displayName;
/**
* @var integer sequence id used when saving several descriptions
* of the same subject.
*/
var $_seq;
/**
* Constructor
*/
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.
*/
function getMetadataSchemaName() {
return $this->_metadataSchemaName;
}
/**
* Get the metadata schema
* @return MetadataSchema
*/
function &getMetadataSchema() {
// Lazy-load the meta-data schema if this has
// not been done before.
if (is_null($this->_metadataSchema)) {
$this->_metadataSchema =& instantiate($this->getMetadataSchemaName(), 'MetadataSchema');
assert(is_object($this->_metadataSchema));
}
return $this->_metadataSchema;
}
/**
* Get the association type (described resource type)
* @return int
*/
function getAssocType() {
return $this->_assocType;
}
/**
* Get the association id (described resource identifier)
* @return int
*/
function getAssocId() {
return $this->_assocId;
}
/**
* Set the association id (described resource identifier)
* @param $assocId int
*/
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
*/
function getAssoc() {
$assocType = $this->getAssocType();
$assocId = $this->getAssocId();
assert(isset($assocType) && isset($assocId));
return $assocType.':'.$assocId;
}
/**
* Set the (optional) display name
* @param $displayName string
*/
function setDisplayName($displayName) {
$this->_displayName = $displayName;
}
/**
* Get the (optional) display name
* @return string
*/
function getDisplayName() {
return $this->_displayName;
}
/**
* Set the sequence id
* @param $seq integer
*/
function setSequence($seq) {
$this->_seq = $seq;
}
/**
* Get the sequence id
* @return integer
*/
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 $propertyName string The name of the property
* @param $value mixed The value to be assigned to the property
* @param $locale string
* @param $replace boolean whether to replace an existing statement
* @return boolean true if a valid statement was added, otherwise false
*/
function addStatement($propertyName, $value, $locale = null, $replace = false) {
// Check the property
$property =& $this->getProperty($propertyName);
if (is_null($property)) return false;
assert(is_a($property, '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 = AppLocale::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 METADATA_PROPERTY_CARDINALITY_ONE:
if (isset($existingValue) && !$replace) return false;
$newValue = $value;
break;
case METADATA_PROPERTY_CARDINALITY_MANY:
if (isset($existingValue) && !$replace) {
assert(is_array($existingValue));
$newValue = $existingValue;
array_push($newValue, $value);
} else {
$newValue = array($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 $propertyName string
* @param $locale string
* @return boolean true if the statement was found and removed, otherwise false
*/
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
*/
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 $propertyName string
* @param $locale string
* @return mixed a scalar property value or an array of property values
* if the cardinality of the property is 'many'.
*/
function &getStatement($propertyName, $locale = null) {
// Check the property
$property =& $this->getProperty($propertyName);
assert(isset($property) && is_a($property, 'MetadataProperty'));
// Handle translation
$translated = $property->getTranslated();
if (!$translated) assert(is_null($locale));
if ($translated && !isset($locale)) {
// Retrieve the current locale
$locale = AppLocale::getLocale();
}
// Retrieve the value
return $this->getData($propertyName, $locale);
}
/**
* Returns all translations of a translated property
* @param $propertyName string
* @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.
*/
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 $statements array statements
* @param $replace integer one of the allowed replace levels.
* @return boolean true if all statements could be added, false otherwise
*/
function setStatements(&$statements, $replace = METADATA_DESCRIPTION_REPLACE_PROPERTIES) {
assert(in_array($replace, $this->_allowedReplaceLevels()));
// Make a backup copy of all existing statements.
$statementsBackup = $this->getAllData();
if ($replace == METADATA_DESCRIPTION_REPLACE_ALL) {
// Delete existing statements
$emptyArray = array();
$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 = array(&$content);
} else {
$values =& $content;
}
if ($replace == 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 = array(&$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
*/
function &getProperties() {
$metadataSchema =& $this->getMetadataSchema();
return $metadataSchema->getProperties();
}
/**
* Convenience method that returns a property from
* the underlying meta-data schema.
* @param $propertyName string
* @return MetadataProperty
*/
function &getProperty($propertyName) {
$metadataSchema =& $this->getMetadataSchema();
return $metadataSchema->getProperty($propertyName);
}
/**
* Convenience method that returns a property id
* the underlying meta-data schema.
* @param $propertyName string
* @return string
*/
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
*/
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 $propertyType string
* @return array an array of string values representing valid property names
*/
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
*/
function getSetPropertyNames() {
return array_keys($this->getStatements());
}
/**
* Convenience method that checks the existence
* of a property in the underlying meta-data schema.
* @param $propertyName string
* @return boolean
*/
function hasProperty($propertyName) {
$metadataSchema =& $this->getMetadataSchema();
return $metadataSchema->hasProperty($propertyName);
}
/**
* Check the existence of a statement for the given property.
* @param $propertyName string
* @return boolean
*/
function hasStatement($propertyName) {
$statements =& $this->getStatements();
return (isset($statements[$propertyName]));
}
/**
* Convenience method that checks whether a given property
* is translated.
* @param $propertyName string
* @return boolean
*/
function isTranslatedProperty($propertyName) {
$property = $this->getProperty($propertyName);
assert(is_a($property, 'MetadataProperty'));
return $property->getTranslated();
}
//
// Private helper methods
//
/**
* The allowed replace levels for the
* setStatements() method.
*/
static function _allowedReplaceLevels() {
static $allowedReplaceLevels = array(
METADATA_DESCRIPTION_REPLACE_ALL,
METADATA_DESCRIPTION_REPLACE_PROPERTIES,
METADATA_DESCRIPTION_REPLACE_NOTHING
);
return $allowedReplaceLevels;
}
}