One Hat Cyber Team
Your IP :
216.73.216.182
Server IP :
203.175.9.166
Server :
Linux tanggamus.iixcp.rumahweb.net 5.14.0-427.28.1.el9_4.x86_64 #1 SMP PREEMPT_DYNAMIC Fri Aug 2 03:44:10 EDT 2024 x86_64
Server Software :
LiteSpeed
PHP Version :
7.4.33
Buat File
|
Buat Folder
Eksekusi
Dir :
~
/
home
/
cite5577
/
www
/
lib
/
pkp
/
classes
/
services
/
Edit File:
PKPSchemaService.inc.php
<?php /** * @file classes/services/PKPSchemaService.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 PKPSchemaService * @ingroup services * * @brief Helper class for loading schemas, using them to sanitize and * validate objects, and installing default data. */ namespace PKP\Services; define('SCHEMA_ANNOUNCEMENT', 'announcement'); define('SCHEMA_AUTHOR', 'author'); define('SCHEMA_CONTEXT', 'context'); define('SCHEMA_EMAIL_TEMPLATE', 'emailTemplate'); define('SCHEMA_GALLEY', 'galley'); define('SCHEMA_ISSUE', 'issue'); define('SCHEMA_PUBLICATION', 'publication'); define('SCHEMA_REVIEW_ASSIGNMENT', 'reviewAssignment'); define('SCHEMA_REVIEW_ROUND', 'reviewRound'); define('SCHEMA_SECTION', 'section'); define('SCHEMA_SITE', 'site'); define('SCHEMA_SUBMISSION', 'submission'); define('SCHEMA_SUBMISSION_FILE', 'submissionFile'); define('SCHEMA_USER', 'user'); define('SCHEMA_USER_GROUP', 'userGroup'); class PKPSchemaService { /** @var array cache of schemas that have been loaded */ private $_schemas = []; /** * Get a schema * * - Loads the schema file and transforms it into an object * - Passes schema through hook * - Returns pre-loaded schemas on request * * @param $schemaName string One of the SCHEMA_... constants * @param $forceReload boolean Optional. Compile the schema again from the * source files, bypassing any cached version. * @return object */ public function get($schemaName, $forceReload = false) { if (!$forceReload && array_key_exists($schemaName, $this->_schemas)) { return $this->_schemas[$schemaName]; } $schemaFile = sprintf('%s/lib/pkp/schemas/%s.json', BASE_SYS_DIR, $schemaName); if (file_exists($schemaFile)) { $schema = json_decode(file_get_contents($schemaFile)); if (!$schema) { fatalError('Schema failed to decode. This usually means it is invalid JSON. Requested: ' . $schemaFile . '. Last JSON error: ' . json_last_error()); } } else { // allow plugins to create a custom schema and load it via hook $schema = new \stdClass(); } // Merge an app-specific schema file if it exists $appSchemaFile = sprintf('%s/schemas/%s.json', BASE_SYS_DIR, $schemaName); if (file_exists($appSchemaFile)) { $appSchema = json_decode(file_get_contents($appSchemaFile)); if (!$appSchema) { fatalError('Schema failed to decode. This usually means it is invalid JSON. Requested: ' . $appSchemaFile . '. Last JSON error: ' . json_last_error()); } $schema = $this->merge($schema, $appSchema); } \HookRegistry::call('Schema::get::' . $schemaName, array(&$schema)); $this->_schemas[$schemaName] = $schema; return $schema; } /** * Merge two schemas * * Merges the properties of two schemas, updating the title, description, * and properties definitions. * * If both schemas contain definitions for the same property, the property * definition in the additional schema will override the base schema. * * @param $baseSchema object The base schema * @param $additionalSchema object The additional schema properties to apply * to $baseSchema. * @return object */ public function merge($baseSchema, $additionalSchema) { $newSchema = clone $baseSchema; if (!empty($additionalSchema->title)) { $newSchema->title = $additionalSchema->title; } if (!empty($additionalSchema->description)) { $newSchema->description = $additionalSchema->description; } if (!empty($additionalSchema->properties)) { if (empty($newSchema->properties)) { $newSchema->properties = new \stdClass(); } foreach ($additionalSchema->properties as $propName => $propSchema) { $newSchema->properties->{$propName} = $propSchema; } } return $newSchema; } /** * Get the summary properties of a schema * * Gets the properties of a schema which are considered part of the summary * view presented in an API. * * @param $schemaName string One of the SCHEMA_... constants * @return array List of property names */ public function getSummaryProps($schemaName) { $schema = $this->get($schemaName); $props = []; foreach ($schema->properties as $propName => $propSchema) { if (property_exists($propSchema, 'apiSummary') && $propSchema->apiSummary) { $props[] = $propName; } } return $props; } /** * Get all properties of a schema * * Gets the complete list of properties of a schema which are considered part * of the full view presented in an API. * * @param $schemaName string One of the SCHEMA_... constants * @return array List of property names */ public function getFullProps($schemaName) { $schema = $this->get($schemaName); $propNames = []; foreach ($schema->properties as $propName => $propSchema) { if (empty($propSchema->writeOnly)) { $propNames[] = $propName; } } return $propNames; } /** * Get required properties of a schema * * @param $schemaName string One of the SCHEMA_... constants * @return array List of property names */ public function getRequiredProps($schemaName) { $schema = $this->get($schemaName); if (!empty($schema->required)) { return $schema->required; } return []; } /** * Get multilingual properties of a schema * * @param $schemaName string One of the SCHEMA_... constants * @return array List of property names */ public function getMultilingualProps($schemaName) { $schema = $this->get($schemaName); $multilingualProps = []; foreach ($schema->properties as $propName => $propSchema) { if (!empty($propSchema->multilingual)) { $multilingualProps[] = $propName; } } return $multilingualProps; } /** * Sanitize properties according to a schema * * This method coerces properties to their appropriate type, and strips out * properties that are not specified in the schema. * * @param $schemaName string One of the SCHEMA_... constants * @param $props array Properties to be sanitized * @return array The sanitized props */ public function sanitize($schemaName, $props) { $schema = $this->get($schemaName); $cleanProps = []; foreach ($props as $propName => $propValue) { if (empty($schema->properties->{$propName}) || empty($schema->properties->{$propName}->type) || !empty($schema->properties->{$propName}->readOnly)) { continue; } $propSchema = $schema->properties->{$propName}; if (!empty($propSchema->multilingual)) { $values = []; foreach ((array) $propValue as $localeKey => $localeValue) { $values[$localeKey] = $this->coerce($localeValue, $propSchema->type, $propSchema); } if (!empty($values)) { $cleanProps[$propName] = $values; } } else { $cleanProps[$propName] = $this->coerce($propValue, $propSchema->type, $propSchema); } } return $cleanProps; } /** * Coerce a value to a variable type * * It will leave null values alone. * * @param $value mixed * @param $type string boolean, integer, number, string, array, object * @param $schema object A schema defining this property * @return mixed The value coerced to type */ public function coerce($value, $type, $schema) { if (is_null($value)) { return $value; } switch ($type) { case 'boolean': return (bool) $value; case 'integer': return (int) $value; case 'number': return (float) $value; case 'string': if (is_object($value) || is_array($value)) { $value = serialize($value); } return (string) $value; case 'array': $newArray = []; if (is_array($schema->items)) { foreach ($schema->items as $i => $itemSchema) { $newArray[$i] = $this->coerce($value[$i], $itemSchema->type, $itemSchema); } } elseif (is_array($value)) { foreach ($value as $i => $v) { $newArray[$i] = $this->coerce($v, $schema->items->type, $schema->items); } } else { $newArray[] = serialize($value); } return $newArray; case 'object': $newObject = []; // we handle JSON objects as assoc arrays in PHP foreach ($schema->properties as $propName => $propSchema) { if (!isset($value[$propName]) || !empty($propSchema->readOnly)) { continue; } $newObject[$propName] = $this->coerce($value[$propName], $propSchema->type, $propSchema); } return $newObject; } fatalError('Requested variable coercion for a type that was not recognized: ' . $type); } /** * Get the validation rules for the properties of a schema * * These validation rules are returned in a format that is ready to be passed * into ValidatorFactory::make(). * * @param $schemaName string One of the SCHEMA_... constants * @param $allowedLocales array List of allowed locale keys. * @return array List of validation rules for each property */ public function getValidationRules($schemaName, $allowedLocales) { $schema = $this->get($schemaName); $rules = []; foreach ($schema->properties as $propName => $propSchema) { if (!empty($propSchema->multilingual)) { foreach ($allowedLocales as $localeKey) { $rules = $this->addPropValidationRules($rules, $propName . '.' . $localeKey, $propSchema); } } else { $rules = $this->addPropValidationRules($rules, $propName, $propSchema); } } return $rules; } /** * Compile the validation rules for a single property's schema * * @param $propSchema object The property schema * @return array List of Laravel-formatted validation rules */ public function addPropValidationRules($rules, $ruleKey, $propSchema) { if (!empty($propSchema->readOnly)) { return $rules; } switch ($propSchema->type) { case 'boolean': case 'integer': case 'numeric': case 'string': $rules[$ruleKey] = [$propSchema->type]; if (!empty($propSchema->validation)) { $rules[$ruleKey] = array_merge($rules[$ruleKey], $propSchema->validation); } break; case 'array': if ($propSchema->items->type === 'object') { $rules = $this->addPropValidationRules($rules, $ruleKey . '.*', $propSchema->items); } else { $rules[$ruleKey] = ['array']; if (!empty($propSchema->validation)) { $rules[$ruleKey] = array_merge($rules[$ruleKey], $propSchema->validation); } $rules[$ruleKey . '.*'] = [$propSchema->items->type]; if (!empty($propSchema->items->validation)) { $rules[$ruleKey . '.*'] = array_merge($rules[$ruleKey . '.*'], $propSchema->items->validation); } } break; case 'object': foreach ($propSchema->properties as $subPropName => $subPropSchema) { $rules = $this->addPropValidationRules($rules, $ruleKey . '.' . $subPropName, $subPropSchema); } break; } return $rules; } /** * Format validation errors * * This method receives a (Laravel) MessageBag object and formats an error * array to match the entity's schema. It converts Laravel's dot notation for * objects and arrays: * * [ * foo.en_US: ['Error message'], * foo.fr_CA: ['Error message'], * bar.0.baz: ['Error message'], * ] * * Into an assoc array, collapsing subproperty errors into their parent prop: * * [ * foo: [ * en_US: ['Error message'], * fr_CA: ['Error message'], * ], * bar: ['Error message'], * ] * * @param $errorBag \Illuminate\Support\MessageBag * @param $schema object The entity schema * @return array */ public function formatValidationErrors($errorBag, $schema, $locales) { $errors = $errorBag->getMessages(); $formatted = []; foreach ($errors as $ruleKey => $messages) { $ruleKeyParts = explode('.', $ruleKey); $propName = $ruleKeyParts[0]; if (!isset($formatted[$propName])) { $formatted[$propName] = []; } if (!empty($schema->properties->{$propName}) && !empty($schema->properties->{$propName}->multilingual)) { $localeKey = $ruleKeyParts[1]; if (!isset($formatted[$propName][$localeKey])) { $formatted[$propName][$localeKey] = []; } $formatted[$propName][$localeKey] = array_merge($formatted[$propName][$localeKey], $messages); } else { $formatted[$propName] = array_merge($formatted[$propName], $messages); } } return $formatted; } /** * Set default values for an object * * Get default values from an object's schema and set them for the passed * object. * * localeParams are used to populate translation strings where default values * rely on them. For example, a locale string like the following: * * "This email was sent on behalf of {$contextName}." * * Will expect a $localeParams value like this: * * ['contextName' => 'Journal of Public Knowledge'] * * @param $schemaName string One of the SCHEMA_... constants * @param $object DataObject The object to be modified * @param $supportedLocales array List of locale keys that shoud receive * default content. Example: ['en_US', 'fr_CA'] * @param $primaryLocale string Example: `en_US` * @param $localeParams array Key/value params for the translation strings * @return DataObject */ public function setDefaults($schemaName, $object, $supportedLocales, $primaryLocale, $localeParams = array()) { $schema = $this->get($schemaName); // Ensure the locale files are loaded for all required locales foreach ($supportedLocales as $localeKey) { \AppLocale::requireComponents( LOCALE_COMPONENT_PKP_DEFAULT, LOCALE_COMPONENT_APP_DEFAULT, LOCALE_COMPONENT_PKP_COMMON, LOCALE_COMPONENT_APP_COMMON, $localeKey ); } foreach ($schema->properties as $propName => $propSchema) { // Don't override existing values if (!is_null($object->getData($propName))) { continue; } if (!property_exists($propSchema, 'default') && !property_exists($propSchema, 'defaultLocaleKey')) { continue; } if (!empty($propSchema->multilingual)) { $value = []; foreach ($supportedLocales as $localeKey) { $value[$localeKey] = $this->getDefault($propSchema, $localeParams, $localeKey); } } else { $value = $this->getDefault($propSchema, $localeParams, $primaryLocale); } $object->setData($propName, $value); } return $object; } /** * Get the default values for a specific locale * * @param $schemaName string One of the SCHEMA_... constants * @param $locale string The locale key to get values for. Example: `en_US` * @param $localeParams array Key/value params for the translation strings * @return array Key/value of property defaults for the specified locale */ public function getLocaleDefaults($schemaName, $locale, $localeParams) { $schema = $this->get($schemaName); \AppLocale::requireComponents(LOCALE_COMPONENT_PKP_DEFAULT, LOCALE_COMPONENT_APP_DEFAULT, $locale); $defaults = []; foreach ($schema->properties as $propName => $propSchema) { if (empty($propSchema->multilingual) || empty($propSchema->defaultLocaleKey)) { continue; } $defaults[$propName] = $this->getDefault($propSchema, $localeParams, $locale); } return $defaults; } /** * Get a default value for a property based on the schema * * @param $propSchema object The schema definition for this property * @param $localeParams array\null Optional. Key/value params for the translation strings * @param $localeKey string|null Optional. The locale to translate into * @return mixed Will return null if no default value is available */ public function getDefault($propSchema, $localeParams = null, $localeKey = null) { switch ($propSchema->type) { case 'boolean': case 'integer': case 'number': case 'string': if (property_exists($propSchema, 'default')) { return $propSchema->default; } elseif (property_exists($propSchema, 'defaultLocaleKey')) { return __($propSchema->defaultLocaleKey, $localeParams, $localeKey); } break; case 'array': $value = []; foreach ($propSchema->default as $default) { $itemSchema = $propSchema->items; $itemSchema->default = $default; $value[] = $this->getDefault($itemSchema, $localeParams, $localeKey); } return $value; case 'object': $value = []; foreach ($propSchema->properties as $subPropName => $subPropSchema) { if (!property_exists($propSchema->default, $subPropName)) { continue; } $defaultSubProp = $propSchema->default->{$subPropName}; // If a prop is expected to be a string but the default value is an // object with a `defaultLocaleKey` property, then we render that // translation. Othewrise, we assign the values as-is and do not // recursively check for nested objects/arrays inside of objects. if ($subPropSchema->type === 'string' && is_object($defaultSubProp) && property_exists($defaultSubProp, 'defaultLocaleKey')) { $value[$subPropName] = __($defaultSubProp->defaultLocaleKey, $localeParams, $localeKey); } else { $value[$subPropName] = $defaultSubProp; } } return $value; } return null; } /** * Add multilingual props for missing values * * This method will take a set of property values and add empty entries for * any locales that are missing. Given the following: * * $values = [ * 'title' => [ * 'en_US' => 'The Journal of Public Knowledge', * ] * ] * * If the locales en_US and fr_CA are requested, it will return the following: * * $values = [ * 'title' => [ * 'en_US' => 'The Journal of Public Knowledge', * 'fr_CA' => '', * ] * ] * * This is primarily used to ensure API responses present a consistent data * structure regardless of which properties have values. * * @param $schemaName string One of the SCHEMA_... constants * @param $values array Key/value list of entity properties * @param $$localeKeys array List of locale keys expected in the result * @return array */ public function addMissingMultilingualValues($schemaName, $values, $localeKeys) { $schema = $this->get($schemaName); $multilingualProps = $this->getMultilingualProps($schemaName); foreach ($values as $key => $value) { if (!in_array($key, $multilingualProps)) { continue; } foreach ($localeKeys as $localeKey) { if (is_array($value) && array_key_exists($localeKey, $value)) { continue; } switch ($schema->properties->{$key}->type) { case 'string': $values[$key][$localeKey] = ''; break; case 'array': $values[$key][$localeKey] = []; break; default: $values[$key][$localeKey] = null; } } } return $values; } }
Simpan