<?php
/**
* @file classes/components/form/FormComponent.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 FormComponent
*
* @ingroup classes_controllers_form
*
* @brief A base class for building forms to be passed to the Form component
* in the UI Library.
*/
namespace PKP\components\forms;
use Exception;
use PKP\facades\Locale;
use PKP\plugins\Hook;
define('FIELD_POSITION_BEFORE', 'before');
define('FIELD_POSITION_AFTER', 'after');
class FormComponent
{
/**
* @var string An $action value that will emit an event
* when the form is submitted, instead of sending a
* HTTP request
*/
public const ACTION_EMIT = 'emit';
/** @var string A unique ID for this form */
public $id = '';
/** @var string Form method: POST or PUT */
public $method = '';
/** @var string Where the form should be submitted. */
public $action = '';
/** @var array Key/value list of languages this form should support. Key = locale code. Value = locale name */
public $locales = [];
/** @var array List of fields in this form. */
public $fields = [];
/** @var array List of groups in this form. */
public $groups = [];
/** @var array List of hidden fields in this form. */
public $hiddenFields = [];
/** @var array List of pages in this form. */
public $pages = [];
/** @var array List of error messages */
public $errors = [];
/**
* Initialize the form with config parameters
*
* @param string $id
* @param string $method
* @param string $action
* @param array $locales
*/
public function __construct($id, $method, $action, $locales)
{
$this->id = $id;
$this->action = $action;
$this->method = $method;
$this->locales = $locales;
}
/**
* Add a form field
*
* @param Field $field
* @param array $position [
*
* @option string One of FIELD_POSITION_BEFORE or FIELD_POSITION_AFTER
* @option string The field to position it before or after
* ]
*/
public function addField($field, $position = []): static
{
if (empty($position)) {
$this->fields[] = $field;
} else {
$this->fields = $this->addToPosition($position[1], $this->fields, $field, $position[0]);
}
return $this;
}
/**
* Remove a form field
*
* @param string $fieldName
*/
public function removeField($fieldName): static
{
$this->fields = array_values(array_filter($this->fields, function ($field) use ($fieldName) {
return $field->name !== $fieldName;
}));
return $this;
}
/**
* Get a form field
*
* @param string $fieldName
*
* @return ?Field
*/
public function getField($fieldName)
{
foreach ($this->fields as $field) {
if ($field->name === $fieldName) {
return $field;
}
}
return null;
}
/**
* Add a form group
*
* @param array $args [
*
* @option id string Required A unique ID for this form group
* @option label string A label to identify this group of fields. Will become the fieldset's <legend>
* @option description string A description of this group of fields.
* ]
*
* @param array $position [
*
* @option string One of FIELD_POSITION_BEFORE or FIELD_POSITION_AFTER
* @option string The group to position it before or after
* ]
*/
public function addGroup($args, $position = []): static
{
if (empty($args['id'])) {
throw new Exception('Tried to add a form group without an id.');
}
if (empty($position)) {
$this->groups[] = $args;
} else {
$this->groups = $this->addToPosition($position[1], $this->groups, $args, $position[0]);
}
return $this;
}
/**
* Remove a form group
*
* @param string $groupId
*/
public function removeGroup($groupId): static
{
$this->groups = array_filter($this->groups, function ($group) use ($groupId) {
return $group['id'] !== $groupId;
});
$this->fields = array_filter($this->fields, function ($field) use ($groupId) {
return $field['groupId'] !== $groupId;
});
return $this;
}
/**
* Add a form page
*
* @param array $args [
*
* @option id string Required A unique ID for this form page
* @option label string The name of the page to identify it in the page list
* @option submitButton array Required Assoc array defining submission/next button params. Supports any param of the Button component in the UI Library.
* @option previousButton array Assoc array defining button params to go back to the previous page. Supports any param of the Button component in the UI Library.
* ]
*
* @param array $position [
*
* @option string One of FIELD_POSITION_BEFORE or FIELD_POSITION_AFTER
* @option string The page to position it before or after
* ]
*/
public function addPage($args, $position = []): static
{
if (empty($args['id'])) {
fatalError('Tried to add a form page without an id.');
}
if (empty($position)) {
$this->pages[] = $args;
} else {
$this->pages = $this->addToPosition($position[1], $this->pages, $args, $position[0]);
}
return $this;
}
/**
* Remove a form page
*
* @param string $pageId
*/
public function removePage($pageId): static
{
$this->pages = array_filter($this->pages, function ($page) use ($pageId) {
return $page['id'] !== $pageId;
});
foreach ($this->groups as $group) {
if ($group['pageId'] === $pageId) {
$this->removeGroup($group['id']);
}
}
return $this;
}
/**
* Add an field, group or page to a specific position in its array
*
* @param string $id The id of the item to position before or after
* @param array $list The list of fields, groups or pages
* @param mixed $item The item to insert
* @param string $position FIELD_POSITION_BEFORE or FIELD_POSITION_AFTER
*
* @return array
*/
public function addToPosition($id, $list, $item, $position)
{
$index = count($list);
foreach ($list as $key => $val) {
if (($val instanceof \PKP\components\forms\Field && $id === $val->name) || (!$val instanceof \PKP\components\forms\Field && $id === $val['id'])) {
$index = $key;
break;
}
}
if (!$index && $position === FIELD_POSITION_BEFORE) {
array_unshift($list, $item);
return $list;
}
$slice = $position === FIELD_POSITION_BEFORE ? $index : $index + 1;
return array_merge(
array_slice($list, 0, $slice),
[$item],
array_slice($list, $slice)
);
}
/**
* Add a hidden field to this form
*/
public function addHiddenField(string $name, $value)
{
$this->hiddenFields[$name] = $value;
}
/**
* Retrieve the configuration data to be used when initializing this
* handler on the frontend
*
* @return array Configuration data
*/
public function getConfig()
{
if (empty($this->id) || empty($this->action) || ($this->action !== self::ACTION_EMIT && empty($this->method))) {
throw new Exception('FormComponent::getConfig() was called but one or more required property is missing: id, method, action.');
}
Hook::run('Form::config::before', [$this]);
// Add a default page/group if none exist
if (!$this->groups) {
$this->addGroup(['id' => 'default']);
$this->fields = array_map(function ($field) {
$field->groupId = 'default';
return $field;
}, $this->fields);
}
if (!$this->pages) {
$this->addPage(['id' => 'default', 'submitButton' => ['label' => __('common.save')]]);
$this->groups = array_map(function ($group) {
$group['pageId'] = 'default';
return $group;
}, $this->groups);
}
$fieldsConfig = array_map([$this, 'getFieldConfig'], $this->fields);
$visibleLocales = [Locale::getLocale()];
if (Locale::getLocale() !== Locale::getPrimaryLocale()) {
array_unshift($visibleLocales, Locale::getPrimaryLocale());
}
$config = [
'id' => $this->id,
'method' => $this->method,
'action' => $this->action,
'fields' => $fieldsConfig,
'groups' => $this->groups,
'hiddenFields' => (object) $this->hiddenFields,
'pages' => $this->pages,
'primaryLocale' => Locale::getPrimaryLocale(),
'visibleLocales' => $visibleLocales,
'supportedFormLocales' => array_values($this->locales), // See #5690
'errors' => (object) [],
];
Hook::call('Form::config::after', [&$config, $this]);
return $config;
}
/**
* Compile a configuration array for a single field
*
* @param Field $field
*
* @return array
*/
public function getFieldConfig($field)
{
$config = $field->getConfig();
// Add a value property if the field does not include one
if (!array_key_exists('value', $config)) {
$config['value'] = $field->isMultilingual ? [] : $field->getEmptyValue();
}
if ($field->isMultilingual) {
if (!is_array($config['value'])) {
$config['value'] = [];
}
foreach ($this->locales as $locale) {
if (!array_key_exists($locale['key'], $config['value'])) {
$config['value'][$locale['key']] = $field->getEmptyValue();
}
}
}
return $config;
}
}
|