first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-06-08 17:09:23 -04:00
commit df3a033196
17887 changed files with 8637778 additions and 0 deletions
@@ -0,0 +1,42 @@
<?php
/**
* @file classes/metadata/CrosswalkFilter.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 CrosswalkFilter
*
* @ingroup metadata
*
* @see MetadataDescription
*
* @brief Class that provides methods to convert one type of
* meta-data description into another. This is an abstract
* class that must be sub-classed by specific cross-walk
* implementations.
*/
namespace PKP\metadata;
use PKP\filter\Filter;
class CrosswalkFilter extends Filter
{
/**
* Constructor
*
* @param string $fromSchema fully qualified class name of supported input meta-data schema
* @param string $toSchema fully qualified class name of supported output meta-data schema
*/
public function __construct($fromSchema, $toSchema)
{
parent::__construct('metadata::' . $fromSchema . '(*)', 'metadata::' . $toSchema . '(*)');
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\metadata\CrosswalkFilter', '\CrosswalkFilter');
}
@@ -0,0 +1,492 @@
<?php
/**
* @file classes/metadata/MetadataDataObjectAdapter.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 MetadataDataObjectAdapter
*
* @ingroup metadata
*
* @see DataObject
* @see MetadataSchema
* @see MetadataDescription
*
* @brief Class that injects/extracts a meta-data description
* into/from an application entity object (DataObject).
*
* These adapters have to be persistable as they'll be provided
* by plug-ins via the filter registry.
*/
namespace PKP\metadata;
use PKP\core\DataObject;
use PKP\filter\ClassTypeDescription;
use PKP\filter\FilterGroup;
use PKP\filter\PersistableFilter;
class MetadataDataObjectAdapter extends PersistableFilter
{
public const METADATA_DOA_INJECTION_MODE = 1;
public const METADATA_DOA_EXTRACTION_MODE = 2;
/** @var int */
public $_mode;
/** @var MetadataSchema */
public $_metadataSchema;
/** @var string */
public $_dataObjectClass;
/** @var array */
public $_metadataFieldNames;
/** @var string */
public $_metadataSchemaName;
/** @var int */
public $_assocType;
/** @var string */
public $_dataObjectName;
/** @var DataObject */
public $_targetDataObject;
/**
* Constructor
*
* @param FilterGroup $filterGroup
* @param null|mixed $mode
*/
public function __construct($filterGroup, $mode = null)
{
// Initialize the adapter.
parent::__construct($filterGroup);
// Extract information from the input/output types.
// Find out whether this filter is injecting or
// extracting meta-data.
$metadataTypeDescription = null; /** @var MetadataTypeDescription $metadataTypeDescription */
$dataObjectTypeDescription = null; /** @var ClassTypeDescription $dataObjectTypeDescription */
$inputType = & $this->getInputType();
$outputType = & $this->getOutputType();
if (is_null($mode)) {
if ($inputType instanceof \PKP\metadata\MetadataTypeDescription) {
$mode = self::METADATA_DOA_INJECTION_MODE;
} else {
$mode = self::METADATA_DOA_EXTRACTION_MODE;
}
}
$this->_mode = $mode;
if ($mode == self::METADATA_DOA_INJECTION_MODE) {
// We are in meta-data injection mode (or both input and output are meta-data descriptions).
$metadataTypeDescription = & $inputType; /** @var MetadataTypeDescription $metadataTypeDescription */
assert($outputType instanceof ClassTypeDescription);
$dataObjectTypeDescription = & $outputType; /** @var ClassTypeDescription $dataObjectTypeDescription */
} else {
// We are in meta-data extraction mode.
assert($outputType instanceof \PKP\metadata\MetadataTypeDescription);
$metadataTypeDescription = & $outputType;
assert($inputType instanceof ClassTypeDescription);
$dataObjectTypeDescription = & $inputType;
}
// Extract information from the input/output types.
$this->_metadataSchemaName = $metadataTypeDescription->getMetadataSchemaClass();
$this->_assocType = $metadataTypeDescription->getAssocType();
$this->_dataObjectName = $dataObjectTypeDescription->getTypeName();
// Set the display name.
if ($mode == self::METADATA_DOA_INJECTION_MODE) {
$this->setDisplayName('Inject metadata into a(n) ' . $this->getDataObjectClass());
} else {
$this->setDisplayName('Extract metadata from a(n) ' . $this->getDataObjectClass());
}
}
//
// Getters and setters
//
/**
* One of the METADATA_DOA_*_MODE constants.
*
* @return int
*/
public function getMode()
{
return $this->_mode;
}
/**
* Get the fully qualified class name of
* the supported meta-data schema.
*
* @return string
*/
public function getMetadataSchemaName()
{
return $this->_metadataSchemaName;
}
/**
* Get the supported meta-data schema (lazy load)
*
* @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();
assert(!is_null($metadataSchemaName));
if (preg_match('/^[a-zA-Z0-9_.]+$/', $metadataSchemaName)) {
// DEPRECATED as of 3.4.0: Schema class names should be fully qualified pkp/pkp-lib#8186
$this->_metadataSchema = & instantiate($metadataSchemaName, (string) \PKP\metadata\MetadataSchema::class);
} elseif (class_exists($metadataSchemaName)) {
$this->_metadataSchema = new $metadataSchemaName();
}
if (!$this->_metadataSchema instanceof \PKP\metadata\MetadataSchema) {
throw new \Exception('Metadata schema is unexpected class!');
}
assert(is_object($this->_metadataSchema));
}
return $this->_metadataSchema;
}
/**
* Convenience method that returns the
* meta-data name space.
*
* @return string
*/
public function getMetadataNamespace()
{
$metadataSchema = & $this->getMetadataSchema();
return $metadataSchema->getNamespace();
}
/**
* Get the supported application entity (class) name
*
* @return string
*/
public function getDataObjectName()
{
return $this->_dataObjectName;
}
/**
* Return the data object class name
* (without the package prefix)
*
* @return string
*/
public function getDataObjectClass()
{
if (is_null($this->_dataObjectClass)) {
$dataObjectName = $this->getDataObjectName();
assert(!is_null($dataObjectName));
$dataObjectNameParts = explode('.', $dataObjectName);
$this->_dataObjectClass = array_pop($dataObjectNameParts);
}
return $this->_dataObjectClass;
}
/**
* Get the association type corresponding to the data
* object type.
*
* @return int
*/
public function getAssocType()
{
return $this->_assocType;
}
/**
* Set the target data object for meta-data injection.
*
* @param DataObject $targetDataObject
*/
public function setTargetDataObject(&$targetDataObject)
{
$this->_targetDataObject = & $targetDataObject;
}
/**
* Get the target data object for meta-data injection.
*/
public function &getTargetDataObject()
{
return $this->_targetDataObject;
}
//
// Abstract template methods
//
/**
* Inject a MetadataDescription into the target DataObject
*
* @param MetadataDescription $metadataDescription
* @param DataObject $targetDataObject
*
* @return DataObject
*/
public function &injectMetadataIntoDataObject(&$metadataDescription, &$targetDataObject)
{
// Must be implemented by sub-classes
assert(false);
}
/**
* Extract a MetadataDescription from a source DataObject.
*
* @param DataObject $sourceDataObject
*
* @return MetadataDescription
*/
public function extractMetadataFromDataObject(&$sourceDataObject)
{
// Must be implemented by sub-classes
assert(false);
}
/**
* Return the additional field names introduced by the
* meta-data schema that need to be persisted in the
* ..._settings table corresponding to the DataObject
* which is supported by this adapter.
* NB: The field names must be prefixed with the meta-data
* schema namespace identifier.
*
* @param bool $translated if true, return localized field
* names, otherwise return additional field names.
*
* @return array an array of field names to be persisted.
*/
public function getDataObjectMetadataFieldNames($translated = true)
{
// By default return all field names
return $this->getMetadataFieldNames($translated);
}
//
// Implement template methods from Filter
//
/**
* Convert a MetadataDescription to an application
* object or vice versa.
*
* @see Filter::process()
*
* @param mixed $input either a MetadataDescription or an application object
*
* @return mixed either a MetadataDescription or an application object
*/
public function &process(&$input)
{
// Do we inject or extract metadata?
switch ($this->getMode()) {
case self::METADATA_DOA_INJECTION_MODE:
$targetDataObject = & $this->getTargetDataObject();
// Instantiate a new data object if none was given.
if (is_null($targetDataObject)) {
$targetDataObject = & $this->instantiateDataObject();
assert(is_a($targetDataObject, $this->getDataObjectName()));
}
// Inject meta-data into the data object.
$output = & $this->injectMetadataIntoDataObject($input, $targetDataObject);
break;
case self::METADATA_DOA_EXTRACTION_MODE:
$output = $this->extractMetadataFromDataObject($input);
break;
default:
// Input should be validated by now.
assert(false);
}
return $output;
}
//
// Protected helper methods
//
/**
* Instantiate a new data object of the
* correct type.
*
* NB: This can be overridden by sub-classes for more complex
* data objects. The standard implementation assumes there are
* no constructor args to be set or configurations to be made.
*
* @return DataObject
*/
public function &instantiateDataObject()
{
$dataObjectName = $this->getDataObjectName();
assert(!is_null($dataObjectName));
$dataObject = & instantiate($dataObjectName, $this->getDataObjectClass());
return $dataObject;
}
/**
* Instantiate a meta-data description that conforms to the
* settings of this adapter.
*
* @return MetadataDescription
*/
public function &instantiateMetadataDescription()
{
$metadataDescription = new MetadataDescription($this->getMetadataSchemaName(), $this->getAssocType());
return $metadataDescription;
}
/**
* Return all field names introduced by the
* meta-data schema that might have to be persisted.
*
* @param bool $translated if true, return localized field
* names, otherwise return additional field names.
*
* @return array an array of field names to be persisted.
*/
public function getMetadataFieldNames($translated = true)
{
// Do we need to build the field name cache first?
if (is_null($this->_metadataFieldNames)) {
// Initialize the cache array
$this->_metadataFieldNames = [];
// Retrieve all properties and add
// their names to the cache
$metadataSchema = & $this->getMetadataSchema();
$metadataSchemaNamespace = $metadataSchema->getNamespace();
$properties = & $metadataSchema->getProperties();
foreach ($properties as $property) {
$propertyAssocTypes = $property->getAssocTypes();
if (in_array($this->_assocType, $propertyAssocTypes)) {
// Separate translated and non-translated property names
// and add the name space so that field names are unique
// across various meta-data schemas.
$this->_metadataFieldNames[$property->getTranslated()][] = $metadataSchemaNamespace . ':' . $property->getName();
}
}
}
// Return the field names
return $this->_metadataFieldNames[$translated];
}
/**
* Set several localized statements in a meta-data schema.
*
* @param MetadataDescription $metadataDescription
* @param string $propertyName
* @param array $localizedValues (keys: locale, values: localized values)
*/
public function addLocalizedStatements(&$metadataDescription, $propertyName, $localizedValues)
{
if (is_array($localizedValues)) {
foreach ($localizedValues as $locale => $values) {
// Handle cardinality "many" and "one" in the same way.
if (is_scalar($values)) {
$values = [$values];
}
foreach ($values as $value) {
$metadataDescription->addStatement($propertyName, $value, $locale);
unset($value);
}
}
}
}
/**
* Directly inject all fields that are not mapped to the
* data object into the data object's data array for
* automatic persistence by the meta-data framework.
*
* @param MetadataDescription $metadataDescription
* @param DataObject $dataObject
*/
public function injectUnmappedDataObjectMetadataFields(&$metadataDescription, &$dataObject)
{
// Handle translated and non-translated statements separately.
foreach ([true, false] as $translated) {
// Retrieve the unmapped fields.
foreach ($this->getDataObjectMetadataFieldNames($translated) as $unmappedProperty) {
// Identify the corresponding property name.
[$namespace, $propertyName] = explode(':', $unmappedProperty);
// Find out whether we have a statement for this unmapped property.
if ($metadataDescription->hasStatement($propertyName)) {
// Add the unmapped statement directly to the
// data object.
if ($translated) {
$dataObject->setData($unmappedProperty, $metadataDescription->getStatementTranslations($propertyName));
} else {
$dataObject->setData($unmappedProperty, $metadataDescription->getStatement($propertyName));
}
}
}
}
}
/**
* Directly extract all fields that are not mapped to the
* data object from the data object's data array.
*
* @param DataObject $dataObject
* @param MetadataDescription $metadataDescription
*/
public function extractUnmappedDataObjectMetadataFields(&$dataObject, &$metadataDescription)
{
$metadataSchema = & $this->getMetadataSchema();
$handledNamespace = $metadataSchema->getNamespace();
// Handle translated and non-translated statements separately.
foreach ([true, false] as $translated) {
// Retrieve the unmapped fields.
foreach ($this->getDataObjectMetadataFieldNames($translated) as $unmappedProperty) {
// Find out whether we have a statement for this unmapped property.
if ($dataObject->hasData($unmappedProperty)) {
// Identify the corresponding property name and namespace.
[$namespace, $propertyName] = explode(':', $unmappedProperty);
// Only extract data if the namespace of the property
// is the same as the one handled by this adapter and the
// property is within the current description.
if ($namespace == $handledNamespace && $metadataSchema->hasProperty($propertyName)) {
// Add the unmapped statement to the metadata description.
if ($translated) {
$this->addLocalizedStatements($metadataDescription, $propertyName, $dataObject->getData($unmappedProperty));
} else {
$metadataDescription->addStatement($propertyName, $dataObject->getData($unmappedProperty));
}
}
}
}
}
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\metadata\MetadataDataObjectAdapter', '\MetadataDataObjectAdapter');
define('METADATA_DOA_INJECTION_MODE', MetadataDataObjectAdapter::METADATA_DOA_INJECTION_MODE);
define('METADATA_DOA_EXTRACTION_MODE', MetadataDataObjectAdapter::METADATA_DOA_EXTRACTION_MODE);
}
@@ -0,0 +1,663 @@
<?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));
}
}
@@ -0,0 +1,556 @@
<?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));
}
}
+141
View File
@@ -0,0 +1,141 @@
<?php
/**
* @file classes/metadata/MetadataRecord.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 MetadataRecord
*
* @ingroup metadata
*
* @see MetadataProperty
* @see MetadataDescription
*
* @brief Class modeling a meta-data record (DCMI abstract model: an instance
* of a description set, RDF: a graph of several subject nodes with associated
* object nodes).
*/
namespace PKP\metadata;
class MetadataRecord
{
/** @var array the MetadataDescriptions in this record */
public $_descriptions = [];
//
// Get/set methods
//
/**
* Add a meta-data description.
*
* @param MetadataDescription $metadataDescription
* @param bool $replace whether to replace a description if a description for
* the same application entity instance already exists.
*
* @return bool true if a valid description was added, otherwise false
*/
public function addDescription($metadataDescription, $replace = true)
{
assert($metadataDescription instanceof \PKP\metadata\MetadataDescription);
// Check that the description complies with the meta-data schema
$descriptionMetadataSchema = $metadataDescription->getMetadataSchema();
$recordMetadataSchema = $this->getMetadataSchema();
if ($descriptionMetadataSchema->getName() != $recordMetadataSchema->getName()) {
return false;
}
// Check whether we already have a description for the same
// application entity instance.
$applicationEntityId = $this->getApplicationEntityIdFromMetadataDescription($metadataDescription);
if (isset($this->_descriptions[$applicationEntityId]) && !$replace) {
return false;
}
// Add the description
$this->_descriptions[$applicationEntityId] = & $metadataDescription;
}
/**
* Remove description.
*
* @param string $applicationEntityId consisting of 'assocType:assocId'
*
* @return bool true if the description was found and removed, otherwise false
*
* @see MetadataRecord::getApplicationEntityIdFromMetadataDescription()
*/
public function removeDescription($applicationEntityId)
{
// Remove the description if it exists
if (isset($applicationEntityId) && isset($this->_descriptions[$applicationEntityId])) {
unset($this->_descriptions[$applicationEntityId]);
return true;
}
return false;
}
/**
* Get all descriptions
*
* @return array statements
*/
public function &getDescriptions()
{
return $this->_descriptions;
}
/**
* Get a specific description
*
* @param string $applicationEntityId consisting of 'assocType:assocId'
*
* @return ?bool true if the description was found and removed, otherwise false
*
* @see MetadataRecord::getApplicationEntityIdFromMetadataDescription()
*/
public function &getDescription($applicationEntityId)
{
assert(isset($applicationEntityId));
// Retrieve the description
if (isset($this->_descriptions[$applicationEntityId])) {
return $this->_descriptions[$applicationEntityId];
} else {
$nullValue = null;
return $nullValue;
}
}
/**
* Replace all descriptions at once. If one of the descriptions
* is invalid then the meta-data record will be empty after this
* operation.
*
* @param array $descriptions descriptions
*
* @return bool true if all descriptions could be added, false otherwise
*/
public function setDescriptions(&$descriptions)
{
// Delete existing statements
$this->_descriptions = [];
// Add descriptions one by one to validate them.
foreach ($descriptions as $description) {
if (!($this->addDescription($description, false))) {
$this->_descriptions = [];
}
}
return true;
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\metadata\MetadataRecord', '\MetadataRecord');
}
+300
View File
@@ -0,0 +1,300 @@
<?php
/**
* @defgroup metadata Metadata
* Implements the metadata framework, which allows for the flexible description
* of objects in many schemas, and conversion of metadata from one schema to
* another.
*/
/**
* @file classes/metadata/MetadataSchema.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 MetadataSchema
*
* @ingroup metadata
*
* @see MetadataProperty
* @see MetadataRecord
*
* @brief Class that represents a meta-data schema (e.g. NLM element-citation,
* OpenURL, dc(terms), MODS) or a subset of it.
*
* We only implement such subsets of meta-data schemas that contain elements which
* can be mapped to PKP application objects. Meta-data schemas are not meant to
* represent any meta-data in the given schema just PKP application meta-data. The
* constructor argument uniquely identifies the application objects this meta-data
* schema can be mapped to. There should never be two MetadataSchemas with the same
* namespace that map to the same application object type. This also means that we
* implement composite elements if and only if the composite complies with our
* internal class composition schema and not only because the schema allows a composite
* in a certain position. See MetadataDescription and MetadataProperty for further
* information about composite meta-data properties.
*
* Example: We implement a composite to represent authors that correspond to the
* \PKP\author\Author class. We do not implement composites for title meta-data
* even if the chosen schema allows this (e.g. abbreviated title, alternative title)
* as this data is implemented as fields of the Submission object. This doesn't mean
* that such data cannot be mapped to composites in external bindings, e.g. in an
* XML binding of the meta-data schema. We can always map a flat list of key/value
* pairs to a hierarchical representation in an external binding.
*
* This coupling allows us to flexibly configure meta-data entry for application
* objects. We can identify appropriate meta-data fields for application objects
* when displaying or entering object meta-data in application forms. Thereby users
* can dynamically add meta-data fields for input/output if they require these for
* their local meta-data partners (e.g. libraries, repositories, harvesters, indexers).
*
* We assume that all properties defined within a schema can potentially be assigned
* to the objects represented by the given association types. Users should, however,
* be able to enable / disable properties on a per-assoc-type basis so that only a
* sub-set of properties will actually be available in the user interface as well as
* exported or imported for these objects.
*
* New schemas can be dynamically added to the mix at any time if they provide fields
* not provided by already existing schemas.
*
* NB: We currently provide meta-data schemas as classes for better performance
* and code readability. It might, however, be necessary to maintain meta-data
* schemas in the database for higher flexibility and easier run-time configuration/
* installation of new schemas.
*/
namespace PKP\metadata;
class MetadataSchema
{
/** @var array */
public $_assocTypes;
/** @var string */
public $_name;
/** @var string */
public $_namespace;
/** @var string */
public $_classname;
/**
* @var array meta-data properties (predicates)
* supported for this meta-data schema.
*/
public $_properties = [];
/**
* Constructor
*
* @param string $name the meta-data schema name
* @param string $namespace a globally unique namespace for
* the schema. Property names must be unique within this
* namespace.
* @param string $classname the fully qualified class name of
* this schema
* @param array|int $assocTypes the association types of
* PKP application objects that can be described using
* this schema. A single association type can be given as
* a scalar.
*/
public function __construct($name, $namespace, $classname, $assocTypes)
{
assert(is_string($name) && is_string($namespace) && is_string($classname));
assert(is_array($assocTypes) || is_integer($assocTypes));
// Set name and namespace.
$this->_name = $name;
$this->_namespace = $namespace;
$this->_classname = $classname;
// Normalize and set the association types.
if (!is_array($assocTypes)) {
$assocTypes = [$assocTypes];
}
$this->_assocTypes = $assocTypes;
}
//
// Getters and Setters
//
/**
* Get the name of the schema
*
* @return string
*/
public function getName()
{
return $this->_name;
}
/**
* Get the internal namespace qualifier of the schema
*
* @return string
*/
public function getNamespace()
{
return $this->_namespace;
}
/**
* Get the fully qualified class name of this schema.
*
* @return string
*/
public function getClassName()
{
return $this->_classname;
}
/**
* Get the association types for PKP application objects
* that can be described with this schema.
*
* @return array
*/
public function getAssocTypes()
{
return $this->_assocTypes;
}
/**
* Get the properties of the meta-data schema.
*
* @return array an array of MetadataProperties
*/
public function &getProperties()
{
return $this->_properties;
}
/**
* Get a property. Returns null if the property
* doesn't exist.
*
* @return MetadataProperty
*/
public function &getProperty($propertyName)
{
assert(is_string($propertyName));
if ($this->hasProperty($propertyName)) {
$property = & $this->_properties[$propertyName];
} else {
$property = null;
}
return $property;
}
/**
* Returns the property id with prefixed name space
* for use in an external context (e.g. Forms, Templates).
*
* @param string $propertyName
*
* @return string
*/
public function getNamespacedPropertyId($propertyName)
{
$property = & $this->getProperty($propertyName);
assert($property instanceof \PKP\metadata\MetadataProperty);
return $this->getNamespace() . ucfirst($property->getId());
}
/**
* (Re-)set all properties of this meta-data schema.
*
* @param array $properties an array of MetadataProperties
*/
public function setProperties(&$properties)
{
// Remove the existing properties
$this->_properties = [];
// Insert the new properties
foreach ($properties as $property) {
$this->addProperty($property);
}
}
/**
* Add a property to this meta-data schema.
*
* @param string $name the unique name of the property within a meta-data schema (can be a property URI)
* @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 addProperty(
$name,
$allowedTypes = MetadataProperty::METADATA_PROPERTY_TYPE_STRING,
$translated = false,
$cardinality = MetadataProperty::METADATA_PROPERTY_CARDINALITY_ONE,
$displayName = null,
$validationMessage = null,
$mandatory = false
) {
// Make sure that this property has not been added before
assert(!is_null($name) && !isset($this->_properties[$name]));
// Instantiate the property.
$property = new MetadataProperty($name, $this->_assocTypes, $allowedTypes, $translated, $cardinality, $displayName, $validationMessage, $mandatory);
// Add the property
$this->_properties[$name] = & $property;
}
/**
* Get the property names defined for this meta-data schema
*
* @return array an array of string values representing valid property names
*/
public function getPropertyNames()
{
return array_keys($this->_properties);
}
/**
* Get the names of properties with a given data type.
*
* @param mixed $propertyType a valid property type description
*
* @return array an array of string values representing valid property names
*/
public function getPropertyNamesByType($propertyType)
{
assert(in_array($propertyType, MetadataProperty::getSupportedTypes()));
$propertyNames = [];
foreach ($this->_properties as $property) {
$allowedPropertyTypes = $property->getAllowedTypes();
if (isset($allowedPropertyTypes[$propertyType])) {
$propertyNames[] = $property->getName();
}
}
return $propertyNames;
}
/**
* Checks whether a property exists in the meta-data schema
*
* @param string $propertyName
*
* @return bool
*/
public function hasProperty($propertyName)
{
return isset($this->_properties[$propertyName]);
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\metadata\MetadataSchema', '\MetadataSchema');
}
@@ -0,0 +1,147 @@
<?php
/**
* @file classes/metadata/MetadataTypeDescription.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 MetadataTypeDescription
*
* @ingroup metadata
*
* @brief Type validator for metadata input/output.
*
* This type description accepts descriptors of the following form:
* metadata::fully.qualified.MetadataSchema(ASSOC)
*
* The assoc form must be the final part of a Application::ASSOC_TYPE_* definition.
* It can be '*' to designate any assoc type.
*/
namespace PKP\metadata;
use PKP\filter\ClassTypeDescription;
class MetadataTypeDescription extends ClassTypeDescription
{
public const ASSOC_TYPE_ANY = -1;
/** @var string the expected meta-data schema package */
public $_metadataSchemaPackageName;
/** @var string the expected meta-data schema class */
public $_metadataSchemaClassName;
/** @var int the expected assoc type of the meta-data description */
public $_assocType;
//
// Setters and Getters
//
/**
* @see TypeDescription::getNamespace()
*/
public function getNamespace()
{
return \PKP\filter\TypeDescriptionFactory::TYPE_DESCRIPTION_NAMESPACE_METADATA;
}
/**
* @return string the fully qualified class name of the meta-data schema.
*/
public function getMetadataSchemaClass()
{
// If using PSR-based FQCNs, the package name will be empty.
// If using deprecated period.separated.package.names, the package name will be provided.
$prefix = (string) $this->_metadataSchemaPackageName;
if ($prefix) {
$prefix .= '.';
}
return $prefix . $this->_metadataSchemaClassName;
}
/**
* @return int
*/
public function getAssocType()
{
return $this->_assocType;
}
//
// Implement abstract template methods from TypeDescription
//
/**
* @see TypeDescription::parseTypeName()
*/
public function parseTypeName($typeName)
{
// Configure the parent class type description
// with the expected meta-data class.
parent::parseTypeName('lib.pkp.classes.metadata.MetadataDescription');
// Split the type name into class name and assoc type.
$typeNameParts = explode('(', $typeName);
if (!count($typeNameParts) == 2) {
return false;
}
// The meta-data schema class must be
// a fully qualified class name.
$splitMetadataSchemaClass = $this->splitClassName($typeNameParts[0]);
if ($splitMetadataSchemaClass === false) {
return false;
}
[$this->_metadataSchemaPackageName, $this->_metadataSchemaClassName] = $splitMetadataSchemaClass;
// Identify the assoc type.
$assocTypeString = trim($typeNameParts[1], ')');
if ($assocTypeString == '*') {
$this->_assocType = self::ASSOC_TYPE_ANY;
} else {
// Make sure that the given assoc type exists.
$assocTypeString = 'ASSOC_TYPE_' . $assocTypeString;
if (!defined($assocTypeString)) {
return false;
}
$this->_assocType = constant($assocTypeString);
}
return true;
}
/**
* @see TypeDescription::checkType()
*/
public function checkType($object)
{
// First of all check whether this is a
// meta-data description at all.
if (!parent::checkType($object)) {
return false;
}
// Check the meta-data schema.
$metadataSchema = & $object->getMetadataSchema();
if (!$metadataSchema instanceof $this->_metadataSchemaClassName) {
return false;
}
// Check the assoc type
if ($this->_assocType != self::ASSOC_TYPE_ANY) {
if ($object->getAssocType() != $this->_assocType) {
return false;
}
}
return true;
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\metadata\MetadataTypeDescription', '\MetadataTypeDescription');
define('ASSOC_TYPE_ANY', MetadataTypeDescription::ASSOC_TYPE_ANY);
}