531 lines
16 KiB
PHP
531 lines
16 KiB
PHP
<?php
|
|
|
|
/**
|
|
* @file classes/core/DataObject.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 DataObject
|
|
*
|
|
* @ingroup core
|
|
*
|
|
* @see Core
|
|
*
|
|
* @brief Any class with an associated DAO should extend this class.
|
|
*/
|
|
|
|
namespace PKP\core;
|
|
|
|
use APP\core\Application;
|
|
use PKP\db\DAO;
|
|
use PKP\db\DAORegistry;
|
|
use \PKP\filter\FilterDAO;
|
|
use PKP\facades\Locale;
|
|
|
|
/**
|
|
* @template T of EntityDAO|DAO
|
|
*/
|
|
class DataObject
|
|
{
|
|
/** @var array Array of object data */
|
|
public $_data = [];
|
|
|
|
/** @var bool whether this objects loads meta-data adapters from the database */
|
|
public $_hasLoadableAdapters = false;
|
|
|
|
/** @var array an array of meta-data extraction adapters (one per supported schema) */
|
|
public $_metadataExtractionAdapters = [];
|
|
|
|
/** @var bool whether extraction adapters have already been loaded from the database */
|
|
public $_extractionAdaptersLoaded = false;
|
|
|
|
/** @var array an array of meta-data injection adapters (one per supported schema) */
|
|
public $_metadataInjectionAdapters = [];
|
|
|
|
/** @var bool whether injection adapters have already been loaded from the database */
|
|
public $_injectionAdaptersLoaded = false;
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
public function __construct()
|
|
{
|
|
}
|
|
|
|
|
|
//
|
|
// Getters and Setters
|
|
//
|
|
/**
|
|
* Get a piece of data for this object, localized to the current
|
|
* locale if possible.
|
|
*/
|
|
public function getLocalizedData(string $key, string $preferredLocale = null, string &$selectedLocale = null): mixed
|
|
{
|
|
foreach ($this->getLocalePrecedence($preferredLocale) as $locale) {
|
|
$value = & $this->getData($key, $locale);
|
|
if (!empty($value)) {
|
|
$selectedLocale = $locale;
|
|
return $value;
|
|
}
|
|
unset($value);
|
|
}
|
|
|
|
// Fallback: Get the first available piece of data.
|
|
$data = $this->getData($key, null);
|
|
foreach ((array) $data as $locale => $dataValue) {
|
|
if (!empty($dataValue)) {
|
|
$selectedLocale = $locale;
|
|
return $dataValue;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Get the locale precedence order for object in the following order
|
|
*
|
|
* 1. Preferred Locale if provided
|
|
* 2. User's current local
|
|
* 3. Object's default locale if set
|
|
* 4. Context's primary locale if context available
|
|
* 5. Site's primary locale
|
|
*/
|
|
public function getLocalePrecedence(string $preferredLocale = null): array
|
|
{
|
|
$request = Application::get()->getRequest();
|
|
|
|
return array_unique(
|
|
array_filter([
|
|
$preferredLocale ?? Locale::getLocale(),
|
|
$this->getDefaultLocale(),
|
|
$request->getContext()?->getPrimaryLocale(),
|
|
$request->getSite()->getPrimaryLocale(),
|
|
])
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get the default locale for object
|
|
*/
|
|
public function getDefaultLocale(): ?string
|
|
{
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Get the value of a data variable.
|
|
*
|
|
* @param string $key
|
|
* @param string $locale (optional)
|
|
*
|
|
* @return mixed
|
|
*/
|
|
public function &getData($key, $locale = null)
|
|
{
|
|
if (is_null($locale)) {
|
|
if (array_key_exists($key, $this->_data)) {
|
|
return $this->_data[$key];
|
|
}
|
|
} elseif (array_key_exists($locale, (array) ($this->_data[$key] ?? []))) {
|
|
return $this->_data[$key][$locale];
|
|
}
|
|
$nullVar = null;
|
|
return $nullVar;
|
|
}
|
|
|
|
/**
|
|
* Set the value of a new or existing data variable.
|
|
*
|
|
* @param string $key
|
|
* @param mixed $value can be either a single value or
|
|
* an array of of localized values in the form:
|
|
* array(
|
|
* 'fr_FR' => 'en français',
|
|
* 'en' => 'in English',
|
|
* ...
|
|
* )
|
|
* @param string $locale (optional) non-null for a single
|
|
* localized value. Null for a non-localized value or
|
|
* when setting all locales at once (see comment for
|
|
* $value parameter)
|
|
*/
|
|
public function setData($key, $value, $locale = null)
|
|
{
|
|
if (is_null($locale)) {
|
|
// This is either a non-localized value or we're passing in all locales at once.
|
|
$this->_data[$key] = $value;
|
|
return;
|
|
}
|
|
// Set a single localized value.
|
|
if (!is_null($value)) {
|
|
if (isset($this->_data[$key]) && !is_array($this->_data[$key])) {
|
|
$this->_data[$key] = [];
|
|
}
|
|
$this->_data[$key][$locale] = $value;
|
|
return;
|
|
}
|
|
// If the value is null, remove the entry.
|
|
if (array_key_exists($key, $this->_data)) {
|
|
if (array_key_exists($locale, (array) $this->_data[$key])) {
|
|
unset($this->_data[$key][$locale]);
|
|
}
|
|
// Was this the last entry for the data variable?
|
|
if (empty($this->_data[$key])) {
|
|
unset($this->_data[$key]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Unset an element of the data object.
|
|
*
|
|
* @param string $key
|
|
* @param string $locale (optional) non-null for a single
|
|
* localized value. Null for a non-localized value or
|
|
* when unsetting all locales at once.
|
|
*/
|
|
public function unsetData($key, $locale = null)
|
|
{
|
|
if (is_null($locale)) {
|
|
unset($this->_data[$key]);
|
|
} else {
|
|
unset($this->_data[$key][$locale]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check whether a value exists for a given data variable.
|
|
*
|
|
* @param string $key
|
|
* @param string $locale (optional)
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function hasData($key, $locale = null)
|
|
{
|
|
return is_null($locale) ? array_key_exists($key, $this->_data) : array_key_exists($locale, (array) ($this->_data[$key] ?? []));
|
|
}
|
|
|
|
/**
|
|
* Return an array with all data variables.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function &getAllData()
|
|
{
|
|
return $this->_data;
|
|
}
|
|
|
|
/**
|
|
* Set all data variables at once.
|
|
*
|
|
* @param array $data
|
|
*/
|
|
public function setAllData($data)
|
|
{
|
|
$this->_data = $data;
|
|
}
|
|
|
|
/**
|
|
* Get ID of object.
|
|
*
|
|
* @return int
|
|
*/
|
|
public function getId()
|
|
{
|
|
return $this->getData('id');
|
|
}
|
|
|
|
/**
|
|
* Set ID of object.
|
|
*
|
|
* @param int $id
|
|
*/
|
|
public function setId($id)
|
|
{
|
|
$this->setData('id', $id);
|
|
}
|
|
|
|
|
|
//
|
|
// MetadataProvider interface implementation
|
|
//
|
|
/**
|
|
* Set whether the object has loadable meta-data adapters
|
|
*
|
|
* @param bool $hasLoadableAdapters
|
|
*/
|
|
public function setHasLoadableAdapters($hasLoadableAdapters)
|
|
{
|
|
$this->_hasLoadableAdapters = $hasLoadableAdapters;
|
|
}
|
|
|
|
/**
|
|
* Get whether the object has loadable meta-data adapters
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function getHasLoadableAdapters()
|
|
{
|
|
return $this->_hasLoadableAdapters;
|
|
}
|
|
|
|
/**
|
|
* Add a meta-data adapter that will be supported
|
|
* by this application entity. Only one adapter per schema
|
|
* can be added.
|
|
*
|
|
* @param \PKP\metadata\MetadataDataObjectAdapter $metadataAdapter
|
|
*/
|
|
public function addSupportedMetadataAdapter($metadataAdapter)
|
|
{
|
|
$metadataSchemaName = $metadataAdapter->getMetadataSchemaName();
|
|
assert(!empty($metadataSchemaName));
|
|
|
|
// NB: Some adapters are injectors and extractors at the same time,
|
|
// notably the meta-data description dummy adapter that converts
|
|
// from/to a meta-data description. That's why we have to check
|
|
// input and output type separately.
|
|
|
|
// Is this a meta-data extractor?
|
|
$inputType = $metadataAdapter->getInputType();
|
|
if ($inputType->checkType($this)) {
|
|
if (!isset($this->_metadataExtractionAdapters[$metadataSchemaName])) {
|
|
$this->_metadataExtractionAdapters[$metadataSchemaName] = $metadataAdapter;
|
|
}
|
|
}
|
|
|
|
// Is this a meta-data injector?
|
|
$outputType = $metadataAdapter->getOutputType();
|
|
if ($outputType->checkType($this)) {
|
|
if (!isset($this->_metadataInjectionAdapters[$metadataSchemaName])) {
|
|
$this->_metadataInjectionAdapters[$metadataSchemaName] = $metadataAdapter;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove all adapters for the given meta-data schema
|
|
* (if it exists).
|
|
*
|
|
* @param string $metadataSchemaName fully qualified class name
|
|
*
|
|
* @return bool true if an adapter was removed, otherwise false.
|
|
*/
|
|
public function removeSupportedMetadataAdapter($metadataSchemaName)
|
|
{
|
|
$result = false;
|
|
if (isset($this->_metadataExtractionAdapters[$metadataSchemaName])) {
|
|
unset($this->_metadataExtractionAdapters[$metadataSchemaName]);
|
|
$result = true;
|
|
}
|
|
if (isset($this->_metadataInjectionAdapters[$metadataSchemaName])) {
|
|
unset($this->_metadataInjectionAdapters[$metadataSchemaName]);
|
|
$result = true;
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Get all meta-data extraction adapters that
|
|
* support this data object. This includes adapters
|
|
* loaded from the database.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getSupportedExtractionAdapters()
|
|
{
|
|
// Load meta-data adapters from the database.
|
|
if ($this->getHasLoadableAdapters() && !$this->_extractionAdaptersLoaded) {
|
|
$filterDao = DAORegistry::getDAO('FilterDAO'); /** @var FilterDAO $filterDao */
|
|
$loadedAdapters = $filterDao->getObjectsByTypeDescription('class::%', 'metadata::%', $this);
|
|
foreach ($loadedAdapters as $loadedAdapter) {
|
|
$this->addSupportedMetadataAdapter($loadedAdapter);
|
|
}
|
|
$this->_extractionAdaptersLoaded = true;
|
|
}
|
|
|
|
return $this->_metadataExtractionAdapters;
|
|
}
|
|
|
|
/**
|
|
* Get all meta-data injection adapters that
|
|
* support this data object. This includes adapters
|
|
* loaded from the database.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getSupportedInjectionAdapters()
|
|
{
|
|
// Load meta-data adapters from the database.
|
|
if ($this->getHasLoadableAdapters() && !$this->_injectionAdaptersLoaded) {
|
|
$filterDao = DAORegistry::getDAO('FilterDAO'); /** @var FilterDAO $filterDao */
|
|
$loadedAdapters = $filterDao->getObjectsByTypeDescription('metadata::%', 'class::%', $this, false);
|
|
foreach ($loadedAdapters as $loadedAdapter) {
|
|
$this->addSupportedMetadataAdapter($loadedAdapter);
|
|
}
|
|
$this->_injectionAdaptersLoaded = true;
|
|
}
|
|
|
|
return $this->_metadataInjectionAdapters;
|
|
}
|
|
|
|
/**
|
|
* Returns all supported meta-data schemas
|
|
* which are supported by extractor adapters.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getSupportedMetadataSchemas()
|
|
{
|
|
$supportedMetadataSchemas = [];
|
|
$extractionAdapters = $this->getSupportedExtractionAdapters();
|
|
foreach ($extractionAdapters as $metadataAdapter) {
|
|
$supportedMetadataSchemas[] = $metadataAdapter->getMetadataSchema();
|
|
}
|
|
return $supportedMetadataSchemas;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the names of meta-data
|
|
* properties of this data object.
|
|
*
|
|
* @param bool $translated if true, return localized field
|
|
* names, otherwise return additional field names.
|
|
*/
|
|
public function getMetadataFieldNames($translated = true)
|
|
{
|
|
// Create a list of all possible meta-data field names
|
|
$metadataFieldNames = [];
|
|
$extractionAdapters = $this->getSupportedExtractionAdapters();
|
|
foreach ($extractionAdapters as $metadataAdapter) {
|
|
// Add the field names from the current adapter
|
|
$metadataFieldNames = array_merge(
|
|
$metadataFieldNames,
|
|
$metadataAdapter->getDataObjectMetadataFieldNames($translated)
|
|
);
|
|
}
|
|
return array_unique($metadataFieldNames);
|
|
}
|
|
|
|
/**
|
|
* Retrieve the names of meta-data
|
|
* properties that need to be persisted
|
|
* (i.e. that have data).
|
|
*
|
|
* @param bool $translated if true, return localized field
|
|
* names, otherwise return additional field names.
|
|
*
|
|
* @return array an array of field names
|
|
*/
|
|
public function getSetMetadataFieldNames($translated = true)
|
|
{
|
|
// Retrieve a list of all possible meta-data field names
|
|
$metadataFieldNameCandidates = $this->getMetadataFieldNames($translated);
|
|
|
|
// Only retain those fields that have data
|
|
$metadataFieldNames = [];
|
|
foreach ($metadataFieldNameCandidates as $metadataFieldNameCandidate) {
|
|
if ($this->hasData($metadataFieldNameCandidate)) {
|
|
$metadataFieldNames[] = $metadataFieldNameCandidate;
|
|
}
|
|
}
|
|
return $metadataFieldNames;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the names of translated meta-data
|
|
* properties that need to be persisted.
|
|
*
|
|
* @return array an array of field names
|
|
*/
|
|
public function getLocaleMetadataFieldNames()
|
|
{
|
|
return $this->getMetadataFieldNames(true);
|
|
}
|
|
|
|
/**
|
|
* Retrieve the names of additional meta-data
|
|
* properties that need to be persisted.
|
|
*
|
|
* @return array an array of field names
|
|
*/
|
|
public function getAdditionalMetadataFieldNames()
|
|
{
|
|
return $this->getMetadataFieldNames(false);
|
|
}
|
|
|
|
/**
|
|
* Inject a meta-data description into this
|
|
* data object.
|
|
*
|
|
* @param \PKP\metadata\MetadataDescription $metadataDescription
|
|
*
|
|
* @return bool true on success, otherwise false
|
|
*/
|
|
public function injectMetadata($metadataDescription)
|
|
{
|
|
$dataObject = null;
|
|
$metadataSchemaName = $metadataDescription->getMetadataSchemaName();
|
|
$injectionAdapters = $this->getSupportedInjectionAdapters();
|
|
if (isset($injectionAdapters[$metadataSchemaName])) {
|
|
// Get the meta-data adapter that supports the
|
|
// given meta-data description's schema.
|
|
$metadataAdapter = $injectionAdapters[$metadataSchemaName]; /** @var \PKP\metadata\MetadataDataObjectAdapter $metadataAdapter */
|
|
|
|
// Pass in a reference to the data object which
|
|
// the filter will use to update the current instance
|
|
// of the data object.
|
|
$metadataAdapter->setTargetDataObject($this);
|
|
|
|
// Use adapter filter to convert from a meta-data
|
|
// description to a data object.
|
|
$dataObject = $metadataAdapter->execute($metadataDescription);
|
|
}
|
|
return $dataObject;
|
|
}
|
|
|
|
/**
|
|
* Extract a meta-data description from this
|
|
* data object.
|
|
*
|
|
* @param \PKP\metadata\MetadataSchema $metadataSchema
|
|
*
|
|
* @return $metadataDescription MetadataDescription
|
|
*/
|
|
public function extractMetadata($metadataSchema)
|
|
{
|
|
$metadataDescription = null;
|
|
$metadataSchemaName = $metadataSchema->getClassName();
|
|
$extractionAdapters = $this->getSupportedExtractionAdapters();
|
|
if (isset($extractionAdapters[$metadataSchemaName])) {
|
|
// Get the meta-data adapter that supports the
|
|
// given meta-data description's schema.
|
|
$metadataAdapter = $extractionAdapters[$metadataSchemaName];
|
|
|
|
// Use adapter filter to convert from a data object
|
|
// to a meta-data description.
|
|
$metadataDescription = $metadataAdapter->execute($this);
|
|
}
|
|
return $metadataDescription;
|
|
}
|
|
|
|
/**
|
|
* Get DAO class for this object.
|
|
*
|
|
* @return T
|
|
*/
|
|
public function getDAO()
|
|
{
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
if (!PKP_STRICT_MODE) {
|
|
class_alias('\PKP\core\DataObject', '\DataObject');
|
|
}
|