493 lines
16 KiB
PHP
493 lines
16 KiB
PHP
<?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);
|
|
}
|