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
+528
View File
@@ -0,0 +1,528 @@
<?php
/**
* @defgroup form Form
* Implements a toolkit for the server-side implementation of forms, including
* initializing forms with presets, reading submitted content, validating
* content, and saving the results.
*/
/**
* @file classes/form/Form.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 Form
*
* @ingroup core
*
* @brief Class defining basic operations for handling HTML forms.
*/
namespace PKP\form;
use APP\core\Application;
use APP\notification\NotificationManager;
use APP\template\TemplateManager;
use PKP\core\PKPRequest;
use PKP\facades\Locale;
use PKP\form\validation\FormValidator;
use PKP\notification\PKPNotification;
use PKP\plugins\Hook;
use PKP\session\SessionManager;
class Form
{
/** @var string The template file containing the HTML form */
public $_template;
/** @var array Associative array containing form data */
public $_data;
/** @var array Validation checks for this form */
public $_checks;
/** @var array Errors occurring in form validation */
public $_errors;
/** @var array Array of field names where an error occurred and the associated error message */
public $errorsArray;
/** @var array Array of field names where an error occurred */
public $errorFields;
/** @var array Array of errors for the form section currently being processed */
public $formSectionErrors;
/** @var array Client-side validation rules */
public $cssValidation;
/** @var string Symbolic name of required locale */
public $requiredLocale;
/** @var array Set of supported locales */
public $supportedLocales;
/** @var string Default form locale */
public $defaultLocale;
/**
* Constructor.
*
* @param string $template the path to the form template file
* @param null|mixed $requiredLocale
* @param null|mixed $supportedLocales
*/
public function __construct($template = null, $callHooks = true, $requiredLocale = null, $supportedLocales = null)
{
if ($requiredLocale === null) {
$requiredLocale = Locale::getPrimaryLocale();
}
$this->requiredLocale = $requiredLocale;
if ($supportedLocales === null) {
$supportedLocales = Locale::getSupportedFormLocales();
}
$this->supportedLocales = $supportedLocales;
$this->defaultLocale = Locale::getLocale();
$this->_template = $template;
$this->_data = [];
$this->_checks = [];
$this->_errors = [];
$this->errorsArray = [];
$this->errorFields = [];
$this->formSectionErrors = [];
if ($callHooks === true) {
// Call hooks based on the calling entity, assuming
// this method is only called by a subclass. Results
// in hook calls named e.g. "papergalleyform::Constructor"
// Note that class names are always lower case.
$classNameParts = explode('\\', get_class($this)); // Separate namespace info from class name
Hook::call(strtolower_codesafe(end($classNameParts)) . '::Constructor', [$this, &$template]);
}
}
//
// Setters and Getters
//
/**
* Set the template
*
* @param string $template
*/
public function setTemplate($template)
{
$this->_template = $template;
}
/**
* Get the template
*
* @return string
*/
public function getTemplate()
{
return $this->_template;
}
/**
* Get the required locale for this form (i.e. the locale for which
* required fields must be set, all others being optional)
*
* @return string
*/
public function getRequiredLocale()
{
return $this->requiredLocale;
}
//
// Public Methods
//
/**
* Display the form.
*
* @param PKPRequest $request
* @param string $template the template to be rendered, mandatory
* if no template has been specified on class instantiation.
*/
public function display($request = null, $template = null)
{
$this->fetch($request, $template, true);
}
/**
* Returns a string of the rendered form
*
* @param PKPRequest $request
* @param string $template the template to be rendered, mandatory
* if no template has been specified on class instantiation.
* @param bool $display
*
* @return string the rendered form
*/
public function fetch($request, $template = null, $display = false)
{
// Set custom template.
if (!is_null($template)) {
$this->_template = $template;
}
// Call hooks based on the calling entity, assuming
// this method is only called by a subclass. Results
// in hook calls named e.g. "papergalleyform::display"
// Note that class names are always lower case.
$returner = null;
$classNameParts = explode('\\', get_class($this)); // Separate namespace info from class name
if (Hook::call(strtolower_codesafe(end($classNameParts)) . '::display', [$this, &$returner])) {
return $returner;
}
$templateMgr = TemplateManager::getManager($request);
$templateMgr->setCacheability(TemplateManager::CACHEABILITY_NO_STORE);
$context = $request->getContext();
// Attach this form object to the Form Builder Vocabulary for validation to work
$fbv = $templateMgr->getFBV();
$fbv->setForm($this);
$templateMgr->assign(array_merge(
$this->_data,
[
'isError' => !$this->isValid(),
'errors' => $this->getErrorsArray(),
'formLocales' => $this->supportedLocales,
'formLocale' => $this->getDefaultFormLocale(),
]
));
if (!$templateMgr->getTemplateVars('primaryLocale')) {
$templateMgr->assign([
'primaryLocale' => $context
? $context->getPrimaryLocale()
: (Application::isInstalled() ? $request->getSite()->getPrimaryLocale() : null),
]);
}
if ($display) {
$templateMgr->display($this->_template);
$returner = null;
} else {
$returner = $templateMgr->fetch($this->_template);
}
// Reset the FBV's form in case template manager fetches another template not within a form.
$fbv->setForm(null);
return $returner;
}
/**
* Get the value of a form field.
*
* @param string $key
*
* @return mixed
*/
public function getData($key)
{
return $this->_data[$key] ?? null;
}
/**
* Set the value of one or several form fields.
*
* @param string|array $key If a string, then set a single field. If an associative array, then set many.
* @param null|mixed $value
*/
public function setData($key, $value = null)
{
if (is_array($key)) {
foreach ($key as $aKey => $aValue) {
$this->setData($aKey, $aValue);
}
} else {
$this->_data[$key] = $value;
}
}
/**
* Initialize form data for a new form.
*/
public function initData()
{
// Call hooks based on the calling entity, assuming
// this method is only called by a subclass. Results
// in hook calls named e.g. "papergalleyform::initData"
// Note that class and function names are always lower
// case.
$classNameParts = explode('\\', get_class($this)); // Separate namespace info from class name
Hook::call(strtolower_codesafe(end($classNameParts) . '::initData'), [$this]);
}
/**
* Assign form data to user-submitted data.
* Can be overridden from subclasses.
*/
public function readInputData()
{
// Default implementation does nothing.
}
/**
* Validate form data.
*
* @param bool $callHooks True (default) iff hooks are to be called.
*/
public function validate($callHooks = true)
{
if (!isset($this->errorsArray)) {
$this->getErrorsArray();
}
foreach ($this->_checks as $check) {
if (!isset($this->errorsArray[$check->getField()]) && !$check->isValid()) {
if (method_exists($check, 'getErrorFields') && method_exists($check, 'isArray') && call_user_func([&$check, 'isArray'])) {
$errorFields = call_user_func([&$check, 'getErrorFields']);
for ($i = 0, $count = count($errorFields); $i < $count; $i++) {
$this->addError($errorFields[$i], $check->getMessage());
$this->errorFields[$errorFields[$i]] = 1;
}
} else {
$this->addError($check->getField(), $check->getMessage());
$this->errorFields[$check->getField()] = 1;
}
}
}
if ($callHooks === true) {
// Call hooks based on the calling entity, assuming
// this method is only called by a subclass. Results
// in hook calls named e.g. "papergalleyform::validate"
// Note that class and function names are always lower
// case.
$value = null;
$classNameParts = explode('\\', get_class($this)); // Separate namespace info from class name
if (Hook::call(strtolower_codesafe(end($classNameParts) . '::validate'), [$this, &$value])) {
return $value;
}
}
if (!SessionManager::isDisabled()) {
$request = Application::get()->getRequest();
$user = $request->getUser();
if (!$this->isValid() && $user) {
// Create a form error notification.
$notificationManager = new NotificationManager();
$notificationManager->createTrivialNotification(
$user->getId(),
PKPNotification::NOTIFICATION_TYPE_FORM_ERROR,
['contents' => $this->getErrorsArray()]
);
}
}
return $this->isValid();
}
/**
* Execute the form's action.
* (Note that it is assumed that the form has already been validated.)
*
* @return mixed Result from the consumer to be passed to the caller. Send a true-ish result if you want the caller to do something with the return value.
*/
public function execute(...$functionArgs)
{
// Call hooks based on the calling entity, assuming
// this method is only called by a subclass. Results
// in hook calls named e.g. "papergalleyform::execute"
// Note that class and function names are always lower
// case.
$returner = null;
$classNameParts = explode('\\', get_class($this)); // Separate namespace info from class name
Hook::call(strtolower_codesafe(end($classNameParts) . '::execute'), array_merge([$this], $functionArgs, [&$returner]));
return $returner;
}
/**
* Get the list of field names that need to support multiple locales
*
* @return array
*/
public function getLocaleFieldNames()
{
// Call hooks based on the calling entity, assuming
// this method is only called by a subclass. Results
// in hook calls named e.g. "papergalleyform::getLocaleFieldNames"
// Note that class and function names are always lower
// case.
$returner = [];
$classNameParts = explode('\\', get_class($this)); // Separate namespace info from class name
Hook::call(strtolower_codesafe(end($classNameParts) . '::getLocaleFieldNames'), [$this, &$returner]);
return $returner;
}
/**
* Get the default form locale.
*
* @return string
*/
public function getDefaultFormLocale()
{
$formLocale = $this->defaultLocale;
if (!isset($this->supportedLocales[$formLocale])) {
$formLocale = $this->requiredLocale;
}
return $formLocale;
}
/**
* Set the default form locale.
*
* @param string $defaultLocale
*/
public function setDefaultFormLocale($defaultLocale)
{
$this->defaultLocale = $defaultLocale;
}
/**
* Add a supported locale.
*
* @param string $supportedLocale
*/
public function addSupportedFormLocale($supportedLocale)
{
if (!in_array($supportedLocale, $this->supportedLocales)) {
$site = Application::get()->getRequest()->getSite();
$siteSupportedLocales = $site->getSupportedLocaleNames();
if (array_key_exists($supportedLocale, $siteSupportedLocales)) {
$this->supportedLocales[$supportedLocale] = $siteSupportedLocales[$supportedLocale];
}
}
}
/**
* Adds specified user variables to input data.
*
* @param array $vars the names of the variables to read
*/
public function readUserVars($vars)
{
// Call hooks based on the calling entity, assuming
// this method is only called by a subclass. Results
// in hook calls named e.g. "papergalleyform::readUserVars"
// Note that class and function names are always lower
// case.
$classNameParts = explode('\\', get_class($this)); // Separate namespace info from class name
Hook::call(strtolower_codesafe(end($classNameParts) . '::readUserVars'), [$this, &$vars]);
$request = Application::get()->getRequest();
foreach ($vars as $k) {
$this->setData($k, $request->getUserVar($k));
}
}
/**
* Add a validation check to the form.
*
* @param FormValidator $formValidator
*/
public function addCheck($formValidator)
{
$this->_checks[] = & $formValidator;
}
/**
* Add an error to the form.
* Errors are typically assigned as the form is validated.
*
* @param string $field the name of the field where the error occurred
*/
public function addError($field, $message)
{
$this->_errors[] = new FormError($field, $message);
}
/**
* Add an error field for highlighting on form
*
* @param string $field the name of the field where the error occurred
*/
public function addErrorField($field)
{
$this->errorFields[$field] = 1;
}
/**
* Check if form passes all validation checks.
*
* @return bool
*/
public function isValid()
{
return empty($this->_errors);
}
/**
* Return set of errors that occurred in form validation.
* If multiple errors occurred processing a single field, only the first error is included.
*
* @return array erroneous fields and associated error messages
*/
public function getErrorsArray()
{
$this->errorsArray = [];
foreach ($this->_errors as $error) {
if (!isset($this->errorsArray[$error->getField()])) {
$this->errorsArray[$error->getField()] = $error->getMessage();
}
}
return $this->errorsArray;
}
//
// Private helper methods
//
/**
* Convert PHP variable (literals or arrays) into HTML containing
* hidden input fields.
*
* @param string $name Name of variable
* @param mixed $value Value of variable
* @param array $stack Names of array keys (for recursive calling)
*
* @return string HTML hidden form elements describing the parameters.
*/
public function _decomposeArray($name, $value, $stack)
{
$returner = '';
if (is_array($value)) {
foreach ($value as $key => $subValue) {
$newStack = $stack;
$newStack[] = $key;
$returner .= $this->_decomposeArray($name, $subValue, $newStack);
}
} else {
$name = htmlentities($name, ENT_COMPAT);
$value = htmlentities($value, ENT_COMPAT);
$returner .= '<input type="hidden" name="' . $name;
while (($item = array_shift($stack)) !== null) {
$item = htmlentities($item, ENT_COMPAT);
$returner .= '[' . $item . ']';
}
$returner .= '" value="' . $value . "\" />\n";
}
return $returner;
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\form\Form', '\Form');
}
@@ -0,0 +1,989 @@
<?php
/**
* @defgroup FormBuilderVocabulary Form Builder Vocabulary
* Implements a form construction toolkit for generating standard form markup.
*/
/**
* @file classes/form/FormBuilderVocabulary.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 FormBuilderVocabulary
*
* @ingroup core
*
* @brief Class defining Form Builder Vocabulary methods.
*
* Form Builder Vocabulary - FBV
* Generates form markup in templates using {fbvX} calls.
* Group form areas with the {fbvFormArea} call. These sections mark off groups of semantically
* related form sections.
* Parameters:
* id: The form area ID
* class (optional): Any additional classes
* title (optional): Title of the area
* Group form sections with the {fbvFormSection} call. These sections organize directly related form elements.
* Parameters:
* id: The section ID
* class (optional): Any additional classes
* title (optional): Title of the area
* Form submit/cancel buttons should be created with {fbvFormButtons}
* Parameters:
* submitText (optional): Text to display for the submit link (default is 'Ok')
* submitDisabled (optional): Whether the submit button should be disabled
* confirmSubmit (optional): Text to display in a confirmation dialog that must be okayed
* before the form is submitted
* cancelText (optional): Text to display for the cancel link (default is 'Cancel')
* hideCancel (optional): Whether the submit button should be disabled
* cancelAction (optional): A LinkAction object to execute when cancel is clicked
* cancelUrl (optional): URL to redirect to when cancel is clicked
* Form elements are created with {fbvElement type="type"} plus any additional parameters.
* Each specific element type may have other additional attributes (see their method comments)
* Parameters:
* type: The form element type (one of the cases in the smartyFBVElement method)
* id: The element ID
* class (optional): Any additional classes
* required (optional) whether the section should have a 'required' label (adds span.required)
* for (optional): What the section's label is for
* inline: Adds .inline to the element's parent container and causes it to display inline with other elements
* size: One of $fbvStyles.size.SMALL (adds .quarter to element's parent container) or $fbvStyles.size.MEDIUM (adds
* .half to element's parentcontainer)
* required: Adds an asterisk and a .required class to the element's label
*/
namespace PKP\form;
use Exception;
class FormBuilderVocabulary
{
/** @var Form associated with this object, if any. Will inform smarty which forms to label as required */
public $_form;
/** @var array Styles organized by parameter name */
public $_fbvStyles;
/**
* Constructor.
*
* @param object $form Form associated with this object
*/
public function __construct($form = null)
{
$this->_fbvStyles = [
'size' => ['SMALL' => 'SMALL', 'MEDIUM' => 'MEDIUM', 'LARGE' => 'LARGE'],
'height' => ['SHORT' => 'SHORT', 'MEDIUM' => 'MEDIUM', 'TALL' => 'TALL']
];
}
//
// Setters and Getters
//
/**
* Set the form
*
* @param object $form
*/
public function setForm($form)
{
if ($form) {
assert($form instanceof \PKP\form\Form);
}
$this->_form = $form;
}
/**
* Get the form
*
* @return Form
*/
public function getForm()
{
return $this->_form;
}
/**
* Get the form style constants
*
* @return array
*/
public function getStyles()
{
return $this->_fbvStyles;
}
//
// Public Methods
//
/**
* A form area that contains form sections.
*
* @param array $params
* @param string $content
* @param object $smarty
* @param bool $repeat
*/
public function smartyFBVFormArea($params, $content, $smarty, &$repeat)
{
assert(isset($params['id']));
if (!$repeat) {
$smarty->assign([
'FBV_class' => $params['class'] ?? null,
'FBV_id' => $params['id'],
'FBV_content' => $content ?? null,
'FBV_translate' => $params['translate'] ?? true,
'FBV_title' => $params['title'] ?? null,
]);
return $smarty->fetch('form/formArea.tpl');
}
return '';
}
/**
* A form section that contains controls in a variety of layout possibilities.
*
* @param array $params
* @param string $content
* @param object $smarty
* @param bool $repeat
*/
public function smartyFBVFormSection($params, $content, $smarty, &$repeat)
{
$form = $this->getForm();
if (!$repeat) {
$smarty->assign('FBV_required', $params['required'] ?? false);
$smarty->assign('FBV_id', $params['id'] ?? null);
// Since $content will contain input fields that may have unique Ids appended, the 'for'
// attribute on the form section's label needs to include this. Look for the assigned
// form element within $content and extract the full id. Default to the passed in param
// otherwise.
if (!empty($params['for'])) {
if (preg_match('/id="(' . preg_quote($params['for'], '/') . '\-[^"]+)"/', $content, $matches)) {
$smarty->assign('FBV_labelFor', $matches[1]);
} else {
$smarty->assign('FBV_labelFor', $params['for']);
}
} else {
$smarty->assign('FBV_labelFor', null);
}
$smarty->assign([
'FBV_title' => $params['title'] ?? null,
'FBV_label' => $params['label'] ?? null,
'FBV_layoutInfo' => $this->_getLayoutInfo($params),
'FBV_description' => $params['description'] ?? null,
'FBV_content' => $content ?? null,
'FBV_translate' => $params['translate'] ?? true,
]);
$class = $params['class'] ?? null;
// Check if we are using the Form class and if there are any errors
if (isset($form) && !empty($form->formSectionErrors)) {
$class = $class . (empty($class) ? '' : ' ') . 'error';
$smarty->assign('FBV_sectionErrors', $form->formSectionErrors);
$form->formSectionErrors = [];
} else {
$smarty->assign('FBV_sectionErrors', null);
}
// If we are displaying checkboxes or radio options, we'll need to use a
// list to organize our elements -- Otherwise we use divs and spans
if (isset($params['list']) && $params['list'] != false) {
$smarty->assign('FBV_listSection', true);
} else {
// Double check that we don't have lists in the content.
// This is a kludge but the only way to make sure we've
// set the list parameter when we're using lists
if (substr(trim($content), 0, 4) == '<li>') {
throw new Exception('FBV: list attribute not set on form section containing lists');
}
$smarty->assign('FBV_listSection', false);
}
$smarty->assign('FBV_class', $class);
$smarty->assign('FBV_layoutColumns', empty($params['layout']) ? false : true);
return $smarty->fetch('form/formSection.tpl');
} else {
if (isset($form)) {
$form->formSectionErrors = [];
}
}
return '';
}
/**
* Submit and (optional) cancel button for a form.
*
* @param array $params
* @param object $smarty
*/
public function smartyFBVFormButtons($params, $smarty)
{
$smarty->assign([
'FBV_submitText' => $params['submitText'] ?? 'common.ok',
'FBV_submitDisabled' => isset($params['submitDisabled']) ? (bool)$params['submitDisabled'] : false,
'FBV_confirmSubmit' => $params['confirmSubmit'] ?? null,
'FBV_cancelText' => $params['cancelText'] ?? 'common.cancel',
'FBV_hideCancel' => isset($params['hideCancel']) ? (bool)$params['hideCancel'] : false,
'FBV_cancelAction' => $params['cancelAction'] ?? null,
'FBV_cancelUrl' => $params['cancelUrl'] ?? null,
'FBV_cancelUrlTarget' => $params['cancelUrlTarget'] ?? '',
'FBV_translate' => $params['translate'] ?? true,
'FBV_saveText' => $params['saveText'] ?? null,
]);
return $smarty->fetch('form/formButtons.tpl');
}
/**
* Base form element.
*
* @param array $params
* @param object $smarty
* @param null|mixed $content
*/
public function smartyFBVElement($params, $smarty, $content = null)
{
if (!isset($params['type'])) {
throw new Exception('FBV: Element type not set');
}
if (!isset($params['id'])) {
throw new Exception('FBV: Element ID not set');
}
// Set up the element template
$smarty->assign([
'FBV_id' => $params['id'],
'FBV_class' => $params['class'] ?? null,
'FBV_required' => $params['required'] ?? false,
'FBV_layoutInfo' => $this->_getLayoutInfo($params),
'FBV_label' => $params['label'] ?? null,
'FBV_for' => $params['for'] ?? null,
'FBV_tabIndex' => $params['tabIndex'] ?? null,
'FBV_translate' => $params['translate'] ?? true,
'FBV_keepLabelHtml' => $params['keepLabelHtml'] ?? false,
]);
// Unset these parameters so they don't get assigned twice
unset($params['class']);
// Find fields that the form class has marked as required and add the 'required' class to them
$params = $this->_addClientSideValidation($params);
$smarty->assign('FBV_validation', $params['validation'] ?? null);
// Set up the specific field's template
switch (strtolower_codesafe($params['type'])) {
case 'autocomplete':
$content = $this->_smartyFBVAutocompleteInput($params, $smarty);
break;
case 'button':
case 'submit':
$content = $this->_smartyFBVButton($params, $smarty);
break;
case 'checkbox':
$content = $this->_smartyFBVCheckbox($params, $smarty);
unset($params['label']);
break;
case 'checkboxgroup':
$content = $this->_smartyFBVCheckboxGroup($params, $smarty);
unset($params['label']);
break;
case 'file':
$content = $this->_smartyFBVFileInput($params, $smarty);
break;
case 'hidden':
$content = $this->_smartyFBVHiddenInput($params, $smarty);
break;
case 'keyword':
$content = $this->_smartyFBVKeywordInput($params, $smarty);
break;
case 'interests':
$content = $this->_smartyFBVInterestsInput($params, $smarty);
break;
case 'radio':
$content = $this->_smartyFBVRadioButton($params, $smarty);
unset($params['label']);
break;
case 'email':
case 'search':
case 'tel':
case 'text':
case 'url':
$content = $this->_smartyFBVTextInput($params, $smarty);
break;
case 'select':
$content = $this->_smartyFBVSelect($params, $smarty);
break;
case 'textarea':
$content = $this->_smartyFBVTextArea($params, $smarty);
break;
default: assert(false);
}
unset($params['type']);
return $content;
}
//
// Private methods
//
/**
* Form button.
* parameters: label (or value), disabled (optional), type (optional)
*
* @param array $params
* @param object $smarty
*/
public function _smartyFBVButton($params, $smarty)
{
// the type of this button. the default value is 'button' (but could be 'submit')
$buttonParams = '';
$smarty->assign(['FBV_label' => null, 'FBV_disabled' => false]);
foreach ($params as $key => $value) {
switch ($key) {
case 'inline':
case 'translate':
break;
case 'label':
case 'type':
case 'disabled':
$smarty->assign('FBV_' . $key, $value);
break;
default: $buttonParams .= htmlspecialchars($key, ENT_QUOTES) . '="' . htmlspecialchars($value, ENT_QUOTES) . '" ';
}
}
$smarty->assign('FBV_buttonParams', $buttonParams);
return $smarty->fetch('form/button.tpl');
}
/**
* Form Autocomplete text input. (actually two inputs, label and value)
* parameters: disabled (optional), name (optional - assigned value of 'id' by default)
*
* @param array $params
* @param object $smarty
*/
public function _smartyFBVAutocompleteInput($params, $smarty)
{
assert(isset($params['autocompleteUrl']) && isset($params['id']));
// This id will be used for the hidden input that should be read by the Form.
$autocompleteId = $params['id'];
// We then override the id parameter to differentiate it from the hidden element
// and make sure that the text input is not read by the Form class.
$params['id'] = $autocompleteId . '_input';
// We set this now, so that we unset the param for the text input.
$smarty->assign([
'FBV_autocompleteUrl' => $params['autocompleteUrl'],
'FBV_autocompleteValue' => $params['autocompleteValue'] ?? null,
'FBV_disableSync' => isset($params['disableSync']) ? true : null,
]);
unset($params['autocompleteUrl'], $params['autocompleteValue']);
$smarty->assign([
'FBV_textInput' => $this->_smartyFBVTextInput($params, $smarty),
'FBV_id' => $autocompleteId,
]);
return $smarty->fetch('form/autocompleteInput.tpl');
}
/**
* Form text input.
* parameters: disabled (optional), name (optional - assigned value of 'id' by default), multilingual (optional)
*
* @param array $params
* @param object $smarty
*/
public function _smartyFBVTextInput($params, $smarty)
{
$params['name'] = $params['name'] ?? $params['id'];
$params['subLabelTranslate'] = isset($params['subLabelTranslate']) ? (bool) $params['subLabelTranslate'] : true;
$params['uniqId'] = uniqid();
$textInputParams = '';
$smarty->assign([
'FBV_isPassword' => isset($params['password']) ? true : false,
'FBV_isTypeURL' => $params['type'] === 'url' ? true : false,
'FBV_isTypeSearch' => $params['type'] === 'search' ? true : false,
'FBV_isTypeEmail' => $params['type'] === 'email' ? true : false,
'FBV_isTypeTel' => $params['type'] === 'tel' ? true : false,
'FBV_disabled' => false,
'FBV_readonly' => false,
'FBV_multilingual' => false,
'FBV_name' => null,
'FBV_value' => null,
'FBV_label_content' => null,
'FBV_uniqId' => null,
'FBV_urlValidationErrorMessage' => null,
]);
foreach ($params as $key => $value) {
switch ($key) {
case 'label':
$smarty->assign('FBV_label_content', $this->_smartyFBVSubLabel($params, $smarty));
break;
case 'type':
case 'size':
case 'inline':
case 'subLabelTranslate':
break;
case 'urlValidationErrorMsg':
if ($params['type'] === 'url') {
$smarty->assign('FBV_urlValidationErrorMessage', __($value));
}
break;
case 'placeholder':
$textInputParams .= 'placeholder="' . htmlspecialchars(__($value), ENT_QUOTES) . '" ';
break;
case 'disabled':
case 'readonly':
case 'multilingual':
case 'name':
case 'id':
case 'value':
case 'uniqId':
$smarty->assign('FBV_' . $key, $value);
break;
case 'required':
$smarty->assign('FBV_' . $key, $value);
break;
case 'validation':
$smarty->assign('FBV_' . $key, $value);
// no break
default:
$textInputParams .= htmlspecialchars($key, ENT_QUOTES) . '="' . htmlspecialchars($value, ENT_QUOTES) . '" ';
}
}
$smarty->assign('FBV_textInputParams', $textInputParams);
return $smarty->fetch('form/textInput.tpl');
}
/**
* Form text area.
* parameters:
* - value: string for single language inputs, array (xx_YY => language_value) for multilingual
* - name: string (optional - assigned value based on ID by default)
* - disabled: boolean (default false)
* - multilingual: boolean (default false)
* - rich: false (default), true, or 'extended'
*
* @param array $params
* @param object $smarty
*/
public function _smartyFBVTextArea($params, $smarty)
{
$params['name'] = $params['name'] ?? $params['id'];
$params['rows'] = $params['rows'] ?? 10;
$params['cols'] = $params['cols'] ?? 80;
$params['subLabelTranslate'] = isset($params['subLabelTranslate']) ? (bool) $params['subLabelTranslate'] : true;
$params['uniqId'] = uniqid();
$textAreaParams = '';
$smarty->assign([
'FBV_label_content' => null,
'FBV_disabled' => false,
'FBV_readonly' => false,
'FBV_multilingual' => false,
'FBV_value' => null,
'FBV_height' => null,
'FBV_rich' => false,
'FBV_variables' => null,
'FBV_variablesType' => null,
]);
foreach ($params as $key => $value) {
switch ($key) {
case 'name':
case 'value':
case 'rows':
case 'cols':
case 'rich':
case 'disabled':
case 'readonly':
case 'multilingual':
case 'uniqId':
case 'variablesType':
case 'variables':
case 'required':
$smarty->assign('FBV_' . $key, $value);
break;
case 'label': $smarty->assign('FBV_label_content', $this->_smartyFBVSubLabel($params, $smarty));
break;
case 'type': break;
case 'size': break;
case 'inline': break;
case 'subLabelTranslate': break;
case 'height':
$styles = $this->getStyles();
switch ($value) {
case $styles['height']['SHORT']: $smarty->assign('FBV_height', 'short');
break;
case $styles['height']['MEDIUM']: $smarty->assign('FBV_height', 'medium');
break;
case $styles['height']['TALL']: $smarty->assign('FBV_height', 'tall');
break;
default:
throw new Exception('FBV: invalid height specified for textarea.');
}
break;
case 'id': break; // if we don't do this, the textarea ends up with two id attributes because FBV_id is also set.
default: $textAreaParams .= htmlspecialchars($key, ENT_QUOTES) . '="' . htmlspecialchars((string) $value, ENT_QUOTES) . '" ';
}
}
$smarty->assign('FBV_textAreaParams', $textAreaParams);
return $smarty->fetch('form/textarea.tpl');
}
/**
* Hidden input element.
* parameters: value, id, name (optional - assigned value of 'id' by default), disabled (optional), multilingual (optional)
*
* @param array $params
* @param object $smarty
*/
public function _smartyFBVHiddenInput($params, $smarty)
{
$params['name'] = $params['name'] ?? $params['id'];
$hiddenInputParams = '';
$smarty->assign(['FBV_id' => null, 'FBV_value' => null]);
foreach ($params as $key => $value) {
switch ($key) {
case 'name':
case 'id':
case 'value':
$smarty->assign('FBV_' . $key, $value);
break;
case 'label': break;
case 'type': break;
default: $hiddenInputParams .= htmlspecialchars($key, ENT_QUOTES) . '="' . htmlspecialchars($value, ENT_QUOTES) . '" ';
}
}
$smarty->assign('FBV_hiddenInputParams', $hiddenInputParams);
return $smarty->fetch('form/hiddenInput.tpl');
}
/**
* Form select control.
* parameters: from [array], selected [array index], defaultLabel (optional), defaultValue (optional), disabled (optional),
* translate (optional), name (optional - value of 'id' by default)
*
* @param array $params
* @param object $smarty
*/
public function _smartyFBVSelect($params, $smarty)
{
$params['name'] = $params['name'] ?? $params['id'];
$params['translate'] = $params['translate'] ?? true;
$params['subLabelTranslate'] = isset($params['subLabelTranslate']) ? (bool) $params['subLabelTranslate'] : true;
$selectParams = '';
$smarty->assign([
'FBV_label' => null,
'FBV_from' => null,
'FBV_selected' => null,
'FBV_label_content' => null,
'FBV_defaultValue' => null,
'FBV_defaultLabel' => null,
'FBV_required' => false,
'FBV_disabled' => false,
]);
foreach ($params as $key => $value) {
switch ($key) {
case 'from':
case 'selected':
case 'translate':
case 'defaultValue':
case 'defaultLabel':
case 'disabled':
case 'required':
$smarty->assign('FBV_' . $key, $value);
break;
case 'type':
case 'inline':
case 'size':
break;
case 'subLabelTranslate': break;
case 'label': $smarty->assign('FBV_label_content', $this->_smartyFBVSubLabel($params, $smarty));
break;
default: $selectParams .= htmlspecialchars($key, ENT_QUOTES) . '="' . htmlspecialchars($value ?? '', ENT_QUOTES) . '" ';
}
}
$smarty->assign('FBV_selectParams', $selectParams);
return $smarty->fetch('form/select.tpl');
}
/**
* Form checkbox group control.
* parameters: from [array], selected [array index], defaultLabel (optional), defaultValue (optional), disabled (optional),
* translate (optional), name (optional - value of 'id' by default)
*
* @param array $params
* @param object $smarty
*/
public function _smartyFBVCheckboxGroup($params, $smarty)
{
$params['name'] = $params['name'] ?? $params['id'];
$params['translate'] = isset($params['translate']) ? (bool)$params['translate'] : true;
$params['subLabelTranslate'] = isset($params['subLabelTranslate']) ? (bool) $params['subLabelTranslate'] : true;
$checkboxParams = '';
$smarty->assign(['FBV_from' => null, 'FBV_selected' => false, 'FBV_label_content' => null, 'FBV_defaultValue' => null, 'FBV_defaultLabel' => null]);
foreach ($params as $key => $value) {
switch ($key) {
case 'from':
case 'selected':
case 'defaultValue':
case 'defaultLabel':
case 'translate':
case 'name':
case 'validation':
case 'disabled':
$smarty->assign('FBV_' . $key, $value);
break;
case 'type': break;
case 'inline': break;
case 'subLabelTranslate': break;
default: $checkboxParams .= htmlspecialchars($key, ENT_QUOTES) . '="' . htmlspecialchars($value, ENT_QUOTES) . '" ';
}
}
$smarty->assign('FBV_checkboxParams', $checkboxParams);
return $smarty->fetch('form/checkboxGroup.tpl');
}
/**
* Checkbox input control.
* parameters: label, disabled (optional), translate (optional), name (optional - value of 'id' by default)
*
* @param array $params
* @param object $smarty
*/
public function _smartyFBVCheckbox($params, $smarty)
{
$params['name'] = $params['name'] ?? $params['id'];
$params['translate'] = isset($params['translate']) ? (bool)$params['translate'] : true;
$checkboxParams = '';
$smarty->assign(['FBV_id' => null, 'FBV_label' => null, 'FBV_checked' => false, 'FBV_disabled' => false]);
foreach ($params as $key => $value) {
switch ($key) {
case 'type': break;
case 'id':
case 'label':
case 'translate':
case 'checked':
case 'disabled':
$smarty->assign('FBV_' . $key, $value);
break;
default: $checkboxParams .= htmlspecialchars($key, ENT_QUOTES) . '="' . htmlspecialchars($value, ENT_QUOTES) . '" ';
}
}
$smarty->assign('FBV_checkboxParams', $checkboxParams);
return $smarty->fetch('form/checkbox.tpl');
}
/**
* Radio input control.
* parameters: label, disabled (optional), translate (optional), name (optional - value of 'id' by default)
*
* @param array $params
* @param object $smarty
*/
public function _smartyFBVRadioButton($params, $smarty)
{
$params['name'] = $params['name'] ?? $params['id'];
$params['translate'] = $params['translate'] ?? true;
if (isset($params['label']) && isset($params['content'])) {
throw new Exception('FBV: radio button cannot have both a content and a label parameter. Label has precedence.');
}
$radioParams = '';
$smarty->assign(['FBV_id' => null, 'FBV_label' => null, 'FBV_content' => null, 'FBV_checked' => false, 'FBV_disabled' => false]);
foreach ($params as $key => $value) {
switch ($key) {
case 'type': break;
case 'id':
case 'label':
case 'translate':
case 'checked':
case 'disabled':
case 'content':
$smarty->assign('FBV_' . $key, $value);
break;
default: $radioParams .= htmlspecialchars($key, ENT_QUOTES) . '="' . htmlspecialchars($value, ENT_QUOTES) . '" ';
}
}
$smarty->assign('FBV_radioParams', $radioParams);
return $smarty->fetch('form/radioButton.tpl');
}
/**
* File upload input.
* parameters: submit (optional - name of submit button to include), disabled (optional), name (optional - value of 'id' by default)
*
* @param array $params
* @param object $smarty
*/
public function _smartyFBVFileInput($params, $smarty)
{
$params['name'] = $params['name'] ?? $params['id'];
$params['translate'] = $params['translate'] ?? true;
$smarty->assign(['FBV_id' => null, 'FBV_label_content' => null, 'FBV_checked' => false, 'FBV_disabled' => false, 'FBV_submit' => null]);
foreach ($params as $key => $value) {
switch ($key) {
case 'type': break;
case 'id':
case 'submit':
case 'name':
case 'disabled':
$smarty->assign('FBV_' . $key, $value);
break;
case 'label': $smarty->assign('FBV_label_content', $this->_smartyFBVSubLabel($params, $smarty));
break;
}
}
return $smarty->fetch('form/fileInput.tpl');
}
/**
* Keyword input.
* parameters: available - all available keywords (for autosuggest); current - user's current keywords
*
* @param array $params
* @param object $smarty
*/
public function _smartyFBVKeywordInput($params, $smarty)
{
$params['uniqId'] = uniqid();
$smarty->assign(['FBV_id' => null, 'FBV_label' => null, 'FBV_label_content' => null, 'FBV_currentKeywords' => null, 'FBV_multilingual' => false, 'FBV_sourceUrl' => null, 'FBV_disabled' => false]);
$emptyFormLocaleMap = array_fill_keys(array_keys($smarty->getTemplateVars('formLocales')), []);
$smarty->assign('FBV_availableKeywords', $emptyFormLocaleMap);
foreach ($params as $key => $value) {
switch ($key) {
case 'type': break;
case 'id':
case 'uniqId':
case 'disabled':
case 'multilingual':
$smarty->assign('FBV_' . $key, $value);
break;
case 'label': $smarty->assign('FBV_label_content', $this->_smartyFBVSubLabel($params, $smarty));
break;
case 'available': $smarty->assign(
'FBV_availableKeywords',
$thing = array_merge(
$emptyFormLocaleMap,
$value
)
);
break;
case 'current': $smarty->assign('FBV_currentKeywords', $value);
break;
case 'sourceUrl': $smarty->assign('FBV_sourceUrl', $value);
break;
}
}
return $smarty->fetch('form/keywordInput.tpl');
}
/**
* Reviewing interests input.
* parameters: interests - current users's keywords (array)
*
* @param array $params
* @param object $smarty
*/
public function _smartyFBVInterestsInput($params, $smarty)
{
$smarty->assign(['FBV_id' => null, 'FBV_label' => null, 'FBV_label_content' => null, 'FBV_interests' => null]);
$params['subLabelTranslate'] = isset($params['subLabelTranslate']) ? (bool) $params['subLabelTranslate'] : true;
foreach ($params as $key => $value) {
switch ($key) {
case 'type': break;
case 'id':
case 'interests':
$smarty->assign('FBV_' . $key, $value);
break;
case 'label': $smarty->assign('FBV_label_content', $this->_smartyFBVSubLabel($params, $smarty));
break;
}
}
return $smarty->fetch('form/interestsInput.tpl');
}
/**
* Custom Smarty function for labelling/highlighting of form fields.
*
* @param array $params can contain 'name' (field name/ID), 'required' (required field), 'key' (localization key), 'label' (non-localized label string), 'suppressId' (boolean)
* @param \Smarty $smarty
*/
public function _smartyFBVSubLabel($params, $smarty)
{
assert(isset($params['label']));
$returner = '';
$smarty->assign(['FBV_suppressId' => null, 'FBV_label' => null, 'FBV_required' => false, 'FBV_uniqId' => null, 'FBV_multilingual' => false]);
foreach ($params as $key => $value) {
switch ($key) {
case 'subLabelTranslate': $smarty->assign('FBV_subLabelTranslate', $value);
break;
case 'label':
case 'uniqId':
case 'multilingual':
case 'suppressId':
case 'required':
$smarty->assign('FBV_' . $key, $value);
break;
}
}
$form = $this->getForm();
if (isset($form) && isset($form->errorFields[$params['name']])) {
$smarty->assign('FBV_error', true);
$errors = $form->getErrorsArray();
$smarty->assign('FBV_subLabelTranslate', false);
$smarty->assign('FBV_label', $errors[$params['name']]);
} else {
$smarty->assign('FBV_error', false);
}
$returner = $smarty->fetch('form/subLabel.tpl');
return $returner;
}
/**
* Assign the appropriate class name to the element for client-side validation
*
* @param array $params
* return array
*/
public function _addClientSideValidation($params)
{
$form = $this->getForm();
if (isset($form)) {
// Assign the appropriate class name to the element for client-side validation
$fieldId = $params['id'];
if (isset($form->cssValidation[$fieldId])) {
$params['validation'] = implode(' ', $form->cssValidation[$fieldId]);
}
}
return $params;
}
/**
* Cycle through layout parameters to add the appropriate classes to the element's parent container
*
* @param array $params
*
* @return string
*/
public function _getLayoutInfo($params)
{
$classes = [];
foreach ($params as $key => $value) {
switch ($key) {
case 'size':
switch ($value) {
case 'SMALL': $classes[] = 'pkp_helpers_quarter';
break;
case 'MEDIUM': $classes[] = 'pkp_helpers_half';
break;
case 'LARGE': $classes[] = 'pkp_helpers_threeQuarter';
break;
}
break;
case 'inline':
if ($value) {
$classes[] = 'inline';
} break;
}
}
if (!empty($classes)) {
return implode(' ', $classes);
} else {
return null;
}
}
/**
* Custom Smarty function for labelling/highlighting of form fields.
*
* @param array $params can contain 'name' (field name/ID), 'required' (required field), 'key' (localization key), 'label' (non-localized label string), 'suppressId' (boolean)
* @param \Smarty $smarty
*/
public function smartyFieldLabel($params, $smarty)
{
$returner = '';
if (isset($params) && !empty($params)) {
if (isset($params['key'])) {
$params['label'] = __($params['key'], $params ?? []);
}
$form = $this->getForm();
if (isset($form) && isset($form->errorFields[$params['name']])) {
$smarty->assign('FBV_class', 'error ' . $params['class']);
} else {
$smarty->assign('FBV_class', $params['class']);
}
$smarty->assign(['FBV_suppressId' => null, 'FBV_label' => null, 'FBV_required' => false, 'FBV_disabled' => false, 'FBV_name' => null]);
foreach ($params as $key => $value) {
switch ($key) {
case 'label':
case 'required':
case 'suppressId':
case 'disabled':
case 'name':
$smarty->assign('FBV_' . $key, $value);
break;
}
}
$returner = $smarty->fetch('form/fieldLabel.tpl');
}
return $returner;
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\form\FormBuilderVocabulary', '\FormBuilderVocabulary');
}
+62
View File
@@ -0,0 +1,62 @@
<?php
/**
* @file classes/form/FormError.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 FormError
*
* @ingroup form
*
* @brief Class to represent a form validation error.
*/
namespace PKP\form;
class FormError
{
/** @var string The name of the field */
public $field;
/** @var string The error message */
public $message;
/**
* Constructor.
*
* @param string $field the name of the field
* @param string $message the error message (i18n key)
*/
public function __construct($field, $message)
{
$this->field = $field;
$this->message = $message;
}
/**
* Get the field associated with the error.
*
* @return string
*/
public function getField()
{
return $this->field;
}
/**
* Get the error message (i18n key).
*
* @return string
*/
public function getMessage()
{
return $this->message;
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\form\FormError', '\FormError');
}
@@ -0,0 +1,193 @@
<?php
/**
* @defgroup form_validation Form Validation
*/
/**
* @file classes/form/validation/FormValidator.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 FormValidator
*
* @ingroup form_validation
*
* @brief Class to represent a form validation check.
*/
namespace PKP\form\validation;
use PKP\form\Form;
use PKP\validation\Validator;
class FormValidator
{
// The two allowed states for the type field
public const FORM_VALIDATOR_OPTIONAL_VALUE = 'optional';
public const FORM_VALIDATOR_REQUIRED_VALUE = 'required';
/** @var Form The Form associated with the check */
public $_form;
/** @var string The name of the field */
public $_field;
/** @var string The type of check ("required" or "optional") */
public $_type;
/** @var string The error message associated with a validation failure */
public $_message;
/** @var Validator The validator used to validate the field */
public $_validator;
/**
* Constructor.
*
* @param Form $form the associated form
* @param string $field the name of the associated field
* @param string $type the type of check, either "required" or "optional"
* @param string $message the error message for validation failures (i18n key)
* @param Validator $validator the validator used to validate this form field (optional)
*/
public function __construct(&$form, $field, $type, $message, $validator = null)
{
$this->_form = & $form;
$this->_field = $field;
$this->_type = $type;
$this->_message = $message;
$this->_validator = & $validator;
$form->cssValidation[$field] = [];
if ($type == self::FORM_VALIDATOR_REQUIRED_VALUE) {
array_push($form->cssValidation[$field], 'required');
}
}
//
// Setters and Getters
//
/**
* Get the field associated with the check.
*
* @return string
*/
public function getField()
{
return $this->_field;
}
/**
* Get the error message associated with a failed validation check.
*
* @return string
*/
public function getMessage()
{
return __($this->_message);
}
/**
* Get the form associated with the check
*
* @return Form
*/
public function &getForm()
{
return $this->_form;
}
/**
* Get the validator associated with the check
*
* @return Validator
*/
public function &getValidator()
{
return $this->_validator;
}
/**
* Get the type of the validated field ('optional' or 'required')
*
* @return string
*/
public function getType()
{
return $this->_type;
}
//
// Public methods
//
/**
* Check if field value is valid.
* Default check is that field is either optional or not empty.
*
* @return bool
*/
public function isValid()
{
if ($this->isEmptyAndOptional()) {
return true;
}
$validator = & $this->getValidator();
if (is_null($validator)) {
// Default check: field must not be empty.
$fieldValue = $this->getFieldValue();
if (is_scalar($fieldValue)) {
return $fieldValue !== '';
} else {
return $fieldValue !== [];
}
} else {
// Delegate to the validator for the field value check.
return $validator->isValid($this->getFieldValue());
}
}
//
// Protected helper methods
//
/**
* Get field value
*/
public function getFieldValue()
{
$form = & $this->getForm();
$fieldValue = $form->getData($this->getField());
if (is_null($fieldValue) || is_scalar($fieldValue)) {
$fieldValue = trim((string)$fieldValue);
}
return $fieldValue;
}
/**
* Check if field value is empty and optional.
*
* @return bool
*/
public function isEmptyAndOptional()
{
if ($this->getType() != self::FORM_VALIDATOR_OPTIONAL_VALUE) {
return false;
}
$fieldValue = $this->getFieldValue();
if (is_scalar($fieldValue)) {
return $fieldValue == '';
} else {
return empty($fieldValue);
}
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\form\validation\FormValidator', '\FormValidator');
define('FORM_VALIDATOR_OPTIONAL_VALUE', FormValidator::FORM_VALIDATOR_OPTIONAL_VALUE);
define('FORM_VALIDATOR_REQUIRED_VALUE', FormValidator::FORM_VALIDATOR_REQUIRED_VALUE);
}
@@ -0,0 +1,110 @@
<?php
/**
* @file classes/form/validation/FormValidatorArray.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 FormValidatorArray
*
* @ingroup form_validation
*
* @brief Form validation check that checks an array of fields.
*/
namespace PKP\form\validation;
class FormValidatorArray extends FormValidator
{
/** @var array Array of fields to check */
public $_fields;
/** @var array Array of field names where an error occurred */
public $_errorFields;
/**
* Constructor.
*
* @param \PKP\form\Form $form the associated form
* @param string $field the name of the associated field
* @param string $type the type of check, either "required" or "optional"
* @param string $message the error message for validation failures (i18n key)
* @param array $fields all subfields for each item in the array, i.e. name[][foo]. If empty it is assumed that name[] is a data field
*/
public function __construct(&$form, $field, $type, $message, $fields = [])
{
parent::__construct($form, $field, $type, $message);
$this->_fields = $fields;
$this->_errorFields = [];
}
//
// Setters and Getters
//
/**
* Get array of fields where an error occurred.
*
* @return array
*/
public function getErrorFields()
{
return $this->_errorFields;
}
//
// Public methods
//
/**
* @see FormValidator::isValid()
* Value is valid if it is empty and optional or all field values are set.
*
* @return bool
*/
public function isValid()
{
if ($this->getType() == FormValidator::FORM_VALIDATOR_OPTIONAL_VALUE) {
return true;
}
$data = $this->getFieldValue();
if (!is_array($data)) {
return false;
}
$isValid = true;
foreach ($data as $key => $value) {
if (count($this->_fields) == 0) {
// We expect all fields to contain values.
if (is_null($value) || trim((string)$value) == '') {
$isValid = false;
array_push($this->_errorFields, $this->getField() . "[{$key}]");
}
} else {
// In the two-dimensional case we always expect a value array.
if (!is_array($value)) {
$isValid = false;
array_push($this->_errorFields, $this->getField() . "[{$key}]");
continue;
}
// Go through all sub-sub-fields and check them explicitly
foreach ($this->_fields as $field) {
if (!isset($value[$field]) || trim((string)$value[$field]) == '') {
$isValid = false;
array_push($this->_errorFields, $this->getField() . "[{$key}][{$field}]");
}
}
}
}
return $isValid;
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\form\validation\FormValidatorArray', '\FormValidatorArray');
}
@@ -0,0 +1,190 @@
<?php
/**
* @file classes/form/validation/FormValidatorArrayCustom.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 FormValidatorArrayCustom
*
* @ingroup form_validation
*
* @brief Form validation check with a custom user function performing the validation check of an array of fields.
*/
namespace PKP\form\validation;
class FormValidatorArrayCustom extends FormValidator
{
/** @var array Array of fields to check */
public $_fields;
/** @var array Array of field names where an error occurred */
public $_errorFields;
/** @var bool is the field a multilingual-capable field */
public $_isLocaleField;
/** @var callable Custom validation function */
public $_userFunction;
/** @var array Additional arguments to pass to $userFunction */
public $_additionalArguments;
/** @var bool If true, field is considered valid if user function returns false instead of true */
public $_complementReturn;
/**
* Constructor.
*
* @param \PKP\form\Form $form the associated form
* @param string $field the name of the associated field
* @param string $type the type of check, either "required" or "optional"
* @param string $message the error message for validation failures (i18n key)
* @param callable $userFunction the user function to use for validation
* @param array $additionalArguments optional, a list of additional arguments to pass to $userFunction
* @param bool $complementReturn optional, complement the value returned by $userFunction
* @param array $fields all subfields for each item in the array, i.e. name[][foo]. If empty it is assumed that name[] is a data field
* @param bool $isLocaleField
*/
public function __construct(&$form, $field, $type, $message, $userFunction, $additionalArguments = [], $complementReturn = false, $fields = [], $isLocaleField = false)
{
parent::__construct($form, $field, $type, $message);
$this->_fields = $fields;
$this->_errorFields = [];
$this->_isLocaleField = $isLocaleField;
$this->_userFunction = $userFunction;
$this->_additionalArguments = $additionalArguments;
$this->_complementReturn = $complementReturn;
}
//
// Setters and Getters
//
/**
* Get array of fields where an error occurred.
*
* @return array
*/
public function getErrorFields()
{
return $this->_errorFields;
}
/**
* Is it a multilingual-capable field.
*
* @return bool
*/
public function isLocaleField()
{
return $this->_isLocaleField;
}
//
// Public methods
//
/**
* @see FormValidator::isValid()
*
* @return bool
*/
public function isValid()
{
if ($this->isEmptyAndOptional()) {
return true;
}
$data = $this->getFieldValue();
if (!is_array($data)) {
return false;
}
$isValid = true;
foreach ($data as $key => $value) {
// Bypass check for empty sub-fields if validation type is "optional"
if ($this->getType() == FormValidator::FORM_VALIDATOR_OPTIONAL_VALUE && ($value == [] || $value == '')) {
continue;
}
if (count($this->_fields) == 0) {
if ($this->isLocaleField()) {
$ret = call_user_func_array($this->_userFunction, array_merge([$value, $key], $this->_additionalArguments));
} else {
$ret = call_user_func_array($this->_userFunction, array_merge([$value], $this->_additionalArguments));
}
$ret = $this->_complementReturn ? !$ret : $ret;
if (!$ret) {
$isValid = false;
if ($this->isLocaleField()) {
$this->_errorFields[$key] = $this->getField() . "[{$key}]";
} else {
array_push($this->_errorFields, $this->getField() . "[{$key}]");
}
}
} else {
// In the two-dimensional case we always expect a value array.
if (!is_array($value)) {
$isValid = false;
if ($this->isLocaleField()) {
$this->_errorFields[$key] = $this->getField() . "[{$key}]";
} else {
array_push($this->_errorFields, $this->getField() . "[{$key}]");
}
continue;
}
foreach ($this->_fields as $field) {
// Bypass check for empty sub-sub-fields if validation type is "optional"
if ($this->getType() == FormValidator::FORM_VALIDATOR_OPTIONAL_VALUE) {
if (!isset($value[$field]) || $value[$field] == [] or $value[$field] == '') {
continue;
}
} else {
// Make sure that we pass in 'null' to the user function
// if the expected field doesn't exist in the value array.
if (!array_key_exists($field, $value)) {
$value[$field] = null;
}
}
if ($this->isLocaleField()) {
$ret = call_user_func_array($this->_userFunction, array_merge([$value[$field], $key], $this->_additionalArguments));
} else {
$ret = call_user_func_array($this->_userFunction, array_merge([$value[$field]], $this->_additionalArguments));
}
$ret = $this->_complementReturn ? !$ret : $ret;
if (!$ret) {
$isValid = false;
if ($this->isLocaleField()) {
if (!isset($this->_errorFields[$key])) {
$this->_errorFields[$key] = [];
}
array_push($this->_errorFields[$key], $this->getField() . "[{$key}][{$field}]");
} else {
array_push($this->_errorFields, $this->getField() . "[{$key}][{$field}]");
}
}
}
}
}
return $isValid;
}
/**
* Is the field an array.
*
* @return bool
*/
public function isArray()
{
return is_array($this->getFieldValue());
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\form\validation\FormValidatorArrayCustom', '\FormValidatorArrayCustom');
}
@@ -0,0 +1,70 @@
<?php
/**
* @file classes/form/validation/FormValidatorBoolean.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 FormValidatorBoolean
*
* @ingroup form_validation
*
* @brief Form validation check that checks if the value can be
* interpreted as a boolean value. An empty field is considered
* 'false', a value of '1' is considered 'true'.
*/
namespace PKP\form\validation;
class FormValidatorBoolean extends FormValidator
{
/**
* Constructor.
*
* @param \PKP\form\Form $form the associated form
* @param string $field the name of the associated field
* @param string $message the error message for validation failures (i18n key)
*/
public function __construct(&$form, $field, $message)
{
parent::__construct($form, $field, FormValidator::FORM_VALIDATOR_OPTIONAL_VALUE, $message);
}
//
// Public methods
//
/**
* Value is valid if it is empty (false) or has
* value '1' (true). This assumes checkbox
* behavior in the form.
*
* @see FormValidator::isValid()
*
* @return bool
*/
public function isValid()
{
$value = $this->getFieldValue();
$form = & $this->getForm();
if (empty($value) || $value == 'on') {
// Make sure that the form will contain a real
// boolean value after validation.
$value = ($value == 'on' ? true : false);
$form->setData($this->getField(), $value);
return true;
} elseif ($value === '1' || $value === '0') {
$value = ($value === '1' ? true : false);
$form->setData($this->getField(), $value);
return true;
} else {
return false;
}
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\form\validation\FormValidatorBoolean', '\FormValidatorBoolean');
}
@@ -0,0 +1,53 @@
<?php
/**
* @file classes/form/validation/FormValidatorCSRF.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 FormValidatorCSRF
*
* @ingroup form_validation
*
* @brief Form validation check to make sure the CSRF token is correct.
*/
namespace PKP\form\validation;
use APP\core\Application;
class FormValidatorCSRF extends FormValidator
{
/**
* Constructor.
*
* @param \PKP\form\Form $form
* @param string $message the locale key to use (optional)
*/
public function __construct(&$form, $message = 'form.csrfInvalid')
{
parent::__construct($form, 'dummy', FormValidator::FORM_VALIDATOR_REQUIRED_VALUE, $message);
}
//
// Public methods
//
/**
* Check if the CSRF token is correct.
* overrides FormValidator::isValid()
*
* @return bool
*/
public function isValid()
{
$request = Application::get()->getRequest();
return $request->checkCSRF();
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\form\validation\FormValidatorCSRF', '\FormValidatorCSRF');
}
@@ -0,0 +1,43 @@
<?php
/**
* @file classes/form/validation/FormValidatorControlledVocab.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 FormValidatorControlledVocab
*
* @ingroup form_validation
*
* @brief Form validation check that checks if value is within a certain set.
*/
namespace PKP\form\validation;
use PKP\validation\ValidatorControlledVocab;
class FormValidatorControlledVocab extends FormValidator
{
/**
* Constructor.
*
* @param \PKP\form\Form $form the associated form
* @param string $field the name of the associated field
* @param string $type the type of check, either "required" or "optional"
* @param string $message the error message for validation failures (i18n key)
* @param string $symbolic
* @param int $assocType
* @param int $assocId
*/
public function __construct(&$form, $field, $type, $message, $symbolic, $assocType, $assocId)
{
$validator = new ValidatorControlledVocab($symbolic, $assocType, $assocId);
parent::__construct($form, $field, $type, $message, $validator);
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\form\validation\FormValidatorControlledVocab', '\FormValidatorControlledVocab');
}
@@ -0,0 +1,91 @@
<?php
/**
* @file classes/form/validation/FormValidatorCustom.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 FormValidatorCustom
*
* @ingroup form_validation
*
* @brief Form validation check with a custom user function performing the validation check.
*/
namespace PKP\form\validation;
class FormValidatorCustom extends FormValidator
{
/** @var callable Custom validation function */
public $_userFunction;
/** @var array Additional arguments to pass to $userFunction */
public $_additionalArguments;
/** @var bool If true, field is considered valid if user function returns false instead of true */
public $_complementReturn;
/** @var mixed[] Arguments to pass to getMessage() */
public $_messageArgs = [];
/** @var array If present, additional arguments to pass to the getMessage translation function
* The user function is passed the form data as its first argument and $additionalArguments, if set, as the remaining arguments. This function must return a boolean value.
*
* @param \PKP\form\Form $form the associated form
* @param string $field the name of the associated field
* @param string $type the type of check, either "required" or "optional"
* @param string $message the error message for validation failures (i18n key)
* @param callable $userFunction function the user function to use for validation
* @param array $additionalArguments optional, a list of additional arguments to pass to $userFunction
* @param bool $complementReturn optional, complement the value returned by $userFunction
* @param array $messageArgs optional, arguments to pass to getMessage()
*/
public function __construct(&$form, $field, $type, $message, $userFunction, $additionalArguments = [], $complementReturn = false, $messageArgs = [])
{
parent::__construct($form, $field, $type, $message);
$this->_userFunction = $userFunction;
$this->_additionalArguments = $additionalArguments;
$this->_complementReturn = $complementReturn;
$this->_messageArgs = $messageArgs;
}
//
// Setters and Getters
//
/**
* @see FormValidator::getMessage()
*
* @return string
*/
public function getMessage()
{
return __($this->_message, $this->_messageArgs);
}
//
// Public methods
//
/**
* @see FormValidator::isValid()
* Value is valid if it is empty and optional or validated by user-supplied function.
*
* @return bool
*/
public function isValid()
{
if ($this->isEmptyAndOptional()) {
return true;
} else {
$ret = call_user_func_array($this->_userFunction, array_merge([$this->getFieldValue()], $this->_additionalArguments));
return $this->_complementReturn ? !$ret : $ret;
}
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\form\validation\FormValidatorCustom', '\FormValidatorCustom');
}
@@ -0,0 +1,48 @@
<?php
/**
* @file classes/form/validation/FormValidatorEmail.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 FormValidatorEmail
*
* @ingroup form_validation
*
* @see FormValidator
*
* @brief Form validation check for email addresses.
*/
namespace PKP\form\validation;
use PKP\validation\ValidatorEmail;
class FormValidatorEmail extends FormValidator
{
/**
* Constructor.
*
* @param \PKP\form\Form $form the associated form
* @param string $field the name of the associated field
* @param string $type the type of check, either "required" or "optional"
* @param string $message the error message for validation failures (i18n key)
*/
public function __construct(&$form, $field, $type = 'optional', $message = 'email.invalid')
{
$validator = new ValidatorEmail();
parent::__construct($form, $field, $type, $message, $validator);
array_push($form->cssValidation[$field], 'email');
}
public function getMessage()
{
return __($this->_message, ['email' => $this->getFieldValue()]);
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\form\validation\FormValidatorEmail', '\FormValidatorEmail');
}
@@ -0,0 +1,40 @@
<?php
/**
* @file classes/form/validation/FormValidatorISSN.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 FormValidatorISSN
*
* @ingroup form_validation
*
* @brief Form validation check for ISSNs.
*/
namespace PKP\form\validation;
use PKP\validation\ValidatorISSN;
class FormValidatorISSN extends FormValidator
{
/**
* Constructor.
*
* @param \PKP\form\Form $form the associated form
* @param string $field the name of the associated field
* @param string $type the type of check, either "required" or "optional"
* @param string $message the error message for validation failures (i18n key)
*/
public function __construct($form, $field, $type, $message)
{
$validator = new ValidatorISSN();
parent::__construct($form, $field, $type, $message, $validator);
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\form\validation\FormValidatorISSN', '\FormValidatorISSN');
}
@@ -0,0 +1,58 @@
<?php
/**
* @file classes/form/validation/FormValidatorInSet.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 FormValidatorInSet
*
* @ingroup form_validation
*
* @brief Form validation check that checks if value is within a certain set.
*/
namespace PKP\form\validation;
class FormValidatorInSet extends FormValidator
{
/** @var array of all values accepted as valid */
public $_acceptedValues;
/**
* Constructor.
*
* @param \PKP\form\Form $form the associated form
* @param string $field the name of the associated field
* @param string $type the type of check, either "required" or "optional"
* @param string $message the error message for validation failures (i18n key)
* @param array $acceptedValues all possible accepted values
*/
public function __construct(&$form, $field, $type, $message, $acceptedValues)
{
parent::__construct($form, $field, $type, $message);
$this->_acceptedValues = $acceptedValues;
}
//
// Public methods
//
/**
* Value is valid if it is empty and optional or is in the set of accepted values.
*
* @see FormValidator::isValid()
*
* @return bool
*/
public function isValid()
{
return $this->isEmptyAndOptional() || in_array($this->getFieldValue(), $this->_acceptedValues);
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\form\validation\FormValidatorInSet', '\FormValidatorInSet');
}
@@ -0,0 +1,97 @@
<?php
/**
* @file classes/form/validation/FormValidatorLength.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 FormValidatorLength
*
* @ingroup form_validation
*
* @brief Form validation check that checks if a field's length meets certain requirements.
*/
namespace PKP\form\validation;
use PKP\core\PKPString;
class FormValidatorLength extends FormValidator
{
/** @var string comparator to use (== | != | < | > | <= | >= ) */
public $_comparator;
/** @var int length to compare with */
public $_length;
/**
* Constructor.
*
* @param \PKP\form\Form $form the associated form
* @param string $field the name of the associated field
* @param string $type the type of check, either "required" or "optional"
* @param string $message the error message for validation failures (i18n key)
* @param string $comparator
* @param int $length
*/
public function __construct(&$form, $field, $type, $message, $comparator, $length)
{
parent::__construct($form, $field, $type, $message);
$this->_comparator = $comparator;
$this->_length = $length;
}
//
// Setters and Getters
//
/**
* @see FormValidator::getMessage()
*
* @return string
*/
public function getMessage()
{
return __($this->_message, ['length' => $this->_length]);
}
//
// Public methods
//
/**
* @see FormValidator::isValid()
* Value is valid if it is empty and optional or meets the specified length requirements.
*
* @return bool
*/
public function isValid()
{
if ($this->isEmptyAndOptional()) {
return true;
} else {
$length = PKPString::strlen($this->getFieldValue());
switch ($this->_comparator) {
case '==':
return $length == $this->_length;
case '!=':
return $length != $this->_length;
case '<':
return $length < $this->_length;
case '>':
return $length > $this->_length;
case '<=':
return $length <= $this->_length;
case '>=':
return $length >= $this->_length;
}
return false;
}
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\form\validation\FormValidatorLength', '\FormValidatorLength');
}
@@ -0,0 +1,84 @@
<?php
/**
* @file classes/form/validation/FormValidatorLocale.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 FormValidatorLocale
*
* @ingroup form_validation
*
* @brief Class to represent a form validation check for localized fields.
*/
namespace PKP\form\validation;
use PKP\facades\Locale;
class FormValidatorLocale extends FormValidator
{
/** @var string Symbolic name of the locale to require */
public $_requiredLocale;
/**
* Constructor.
*
* @param \PKP\form\Form $form the associated form
* @param string $field the name of the associated field
* @param string $type the type of check, either "required" or "optional"
* @param string $message the error message for validation failures (i18n key)
* @param \PKP\validation\Validator $validator the validator used to validate this form field (optional)
* @param string $requiredLocale The name of the required locale, i.e. en
*/
public function __construct(&$form, $field, $type, $message, $requiredLocale = null, $validator = null)
{
parent::__construct($form, $field, $type, $message, $validator);
if ($requiredLocale === null) {
$requiredLocale = Locale::getPrimaryLocale();
}
$this->_requiredLocale = $requiredLocale;
}
//
// Getters and Setters
//
/**
* Get the error message associated with a failed validation check.
*
* @see FormValidator::getMessage()
*
* @return string
*/
public function getMessage()
{
return parent::getMessage() . ' (' . Locale::getMetadata($this->_requiredLocale)->getDisplayName() . ')';
}
//
// Protected helper methods
//
/**
* @see FormValidator::getFieldValue()
*/
public function getFieldValue()
{
$form = & $this->getForm();
$data = $form->getData($this->getField());
$fieldValue = '';
if (is_array($data) && isset($data[$this->_requiredLocale])) {
$fieldValue = $data[$this->_requiredLocale];
if (is_scalar($fieldValue)) {
$fieldValue = trim((string)$fieldValue);
}
}
return $fieldValue;
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\form\validation\FormValidatorLocale', '\FormValidatorLocale');
}
@@ -0,0 +1,43 @@
<?php
/**
* @file classes/form/validation/FormValidatorLocaleEmail.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 FormValidatorLocaleEmail
*
* @ingroup form_validation
*
* @see FormValidatorLocale
*
* @brief Form validation check for email addresses.
*/
namespace PKP\form\validation;
use PKP\validation\ValidatorEmail;
class FormValidatorLocaleEmail extends FormValidatorLocale
{
/**
* Constructor.
*
* @param \PKP\form\Form $form the associated form
* @param string $field the name of the associated field
* @param string $type the type of check, either "required" or "optional"
* @param string $message the error message for validation failures (i18n key)
* @param string $requiredLocale The symbolic name of the required locale
*/
public function __construct(&$form, $field, $type, $message, $requiredLocale = null)
{
$validator = new ValidatorEmail();
parent::__construct($form, $field, $type, $message, $requiredLocale, $validator);
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\form\validation\FormValidatorLocaleEmail', '\FormValidatorLocaleEmail');
}
@@ -0,0 +1,44 @@
<?php
/**
* @file classes/form/validation/FormValidatorLocaleUrl.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 FormValidatorLocaleUrl
*
* @ingroup form_validation
*
* @see FormValidatorLocale
*
* @brief Form validation check for URL addresses.
*/
namespace PKP\form\validation;
;
use PKP\validation\ValidatorUrl;
class FormValidatorLocaleUrl extends FormValidatorLocale
{
/**
* Constructor.
*
* @param \PKP\form\Form $form the associated form
* @param string $field the name of the associated field
* @param string $type the type of check, either "required" or "optional"
* @param string $message the error message for validation failures (i18n key)
* @param string $requiredLocale The symbolic name of the required locale
*/
public function __construct(&$form, $field, $type, $message, $requiredLocale = null)
{
$validator = new ValidatorUrl();
parent::__construct($form, $field, $type, $message, $requiredLocale, $validator);
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\form\validation\FormValidatorLocaleUrl', '\FormValidatorLocaleUrl');
}
@@ -0,0 +1,41 @@
<?php
/**
* @file classes/form/validation/FormValidatorORCID.php
*
* Copyright (c) 2013-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 FormValidatorORCID
*
* @ingroup form_validation
*
* @brief Form validation check for ORCID iDs.
*/
namespace PKP\form\validation;
;
use PKP\validation\ValidatorORCID;
class FormValidatorORCID extends FormValidator
{
/**
* Constructor.
*
* @param \PKP\form\Form $form the associated form
* @param string $field the name of the associated field
* @param string $type the type of check, either "required" or "optional"
* @param string $message the error message for validation failures (i18n key)
*/
public function __construct($form, $field, $type, $message)
{
$validator = new ValidatorORCID();
parent::__construct($form, $field, $type, $message, $validator);
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\form\validation\FormValidatorORCID', '\FormValidatorORCID');
}
@@ -0,0 +1,53 @@
<?php
/**
* @file classes/form/validation/FormValidatorPost.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 FormValidatorPost
*
* @ingroup form_validation
*
* @brief Form validation check to make sure the form is POSTed.
*/
namespace PKP\form\validation;
use APP\core\Application;
class FormValidatorPost extends FormValidator
{
/**
* Constructor.
*
* @param \PKP\form\Form $form
* @param string $message the locale key to use (optional)
*/
public function __construct(&$form, $message = 'form.postRequired')
{
parent::__construct($form, 'dummy', FormValidator::FORM_VALIDATOR_REQUIRED_VALUE, $message);
}
//
// Public methods
//
/**
* Check if form was posted.
* overrides FormValidator::isValid()
*
* @return bool
*/
public function isValid()
{
$request = Application::get()->getRequest();
return $request->isPost();
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\form\validation\FormValidatorPost', '\FormValidatorPost');
}
@@ -0,0 +1,135 @@
<?php
/**
* @file classes/form/validation/FormValidatorReCaptcha.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 FormValidatorReCaptcha
*
* @ingroup form_validation
*
* @brief Form validation check reCaptcha values.
*/
namespace PKP\form\validation;
use APP\core\Application;
use Exception;
use InvalidArgumentException;
use PKP\config\Config;
use PKP\form\Form;
class FormValidatorReCaptcha extends FormValidator
{
/** @var string The response field containing the reCaptcha response */
private const RECAPTCHA_RESPONSE_FIELD = 'g-recaptcha-response';
/** @var string The request URL */
private const RECAPTCHA_URL = 'https://www.google.com/recaptcha/api/siteverify';
/** @var string The initiating IP address of the user */
private $_userIp;
/** @var string The hostname to expect in the validation response */
private $_hostname;
/**
* Constructor.
*
* @param \PKP\form\Form $form
* @param string $userIp IP address of user request
* @param string $message Key of message to display on mismatch
* @param string|null $hostname Hostname to expect in validation response
*/
public function __construct(Form $form, string $userIp, string $message, ?string $hostname = null)
{
parent::__construct($form, self::RECAPTCHA_RESPONSE_FIELD, FormValidator::FORM_VALIDATOR_REQUIRED_VALUE, $message);
$this->_userIp = $userIp;
$this->_hostname = $hostname;
}
//
// Public methods
//
/**
* @see FormValidator::isValid()
* Determine whether or not the form meets this ReCaptcha constraint.
*
*/
public function isValid(): bool
{
$form = $this->getForm();
try {
$this->validateResponse($form->getData(self::RECAPTCHA_RESPONSE_FIELD), $this->_userIp, $this->_hostname);
return true;
} catch (Exception $exception) {
$this->_message = 'common.captcha.error.missing-input-response';
return false;
}
}
/**
* Validates the reCaptcha response
*
* @param string|null $response The reCaptcha response
* @param string|null $ip The user IP address (defaults to null)
* @param string|null $hostname The application hostname (defaults to null)
*
* @throws Exception Throws in case the validation fails
*/
public static function validateResponse(?string $response, ?string $ip = null, ?string $hostname = null): void
{
if (!empty($ip) && !filter_var($ip, FILTER_VALIDATE_IP)) {
throw new InvalidArgumentException('Invalid IP address.');
}
if (empty($response)) {
throw new InvalidArgumentException('The reCaptcha user response is required.');
}
$privateKey = Config::getVar('captcha', 'recaptcha_private_key');
if (empty($privateKey)) {
throw new Exception('The reCaptcha is not configured correctly, the secret key is missing.');
}
$httpClient = Application::get()->getHttpClient();
$response = $httpClient->request(
'POST',
self::RECAPTCHA_URL,
[
'multipart' => [
['name' => 'secret', 'contents' => $privateKey],
['name' => 'response', 'contents' => $response],
['name' => 'remoteip', 'contents' => $ip]
]
]
);
$response = json_decode($response->getBody(), true);
if (Config::getVar('captcha', 'recaptcha_enforce_hostname') && ($response['hostname'] ?? null) != $hostname) {
throw new Exception('The hostname validation of the reCaptcha response failed.');
}
$errorMap = [
'missing-input-secret' => 'The secret parameter is missing.',
'invalid-input-secret' => 'The secret parameter is invalid or malformed.',
'missing-input-response' => 'The response parameter is missing.',
'invalid-input-response' => 'The response parameter is invalid or malformed.',
'invalid-keys' => 'The configured keys are invalid.',
'bad-request' => 'The request is invalid or malformed.',
'timeout-or-duplicate' => 'The response is no longer valid: either is too old or has been used previously.'
];
if (!($response['success'] ?? false)) {
$errors = [];
foreach ($response['error-codes'] ?? [] as $error) {
$errors[] = $errorMap[$error] ?? $error;
}
throw new Exception(implode("\n", $errors) ?: 'The reCaptcha validation failed.');
}
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\form\validation\FormValidatorReCaptcha', '\FormValidatorReCaptcha');
}
@@ -0,0 +1,42 @@
<?php
/**
* @file classes/form/validation/FormValidatorRegExp.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 FormValidatorRegExp
*
* @ingroup form_validation
*
* @brief Form validation check using a regular expression.
*/
namespace PKP\form\validation;
;
use PKP\validation\ValidatorRegExp;
class FormValidatorRegExp extends FormValidator
{
/**
* Constructor.
*
* @param \PKP\form\Form $form the associated form
* @param string $field the name of the associated field
* @param string $type the type of check, either "required" or "optional"
* @param string $message the error message for validation failures (i18n key)
* @param string $regExp the regular expression (PCRE form)
*/
public function __construct(&$form, $field, $type, $message, $regExp)
{
$validator = new ValidatorRegExp($regExp);
parent::__construct($form, $field, $type, $message, $validator);
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\form\validation\FormValidatorRegExp', '\FormValidatorRegExp');
}
@@ -0,0 +1,44 @@
<?php
/**
* @file classes/form/validation/FormValidatorUrl.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 FormValidatorUrl
*
* @ingroup form_validation
*
* @see FormValidator
*
* @brief Form validation check for URLs.
*/
namespace PKP\form\validation;
;
use PKP\validation\ValidatorUrl;
class FormValidatorUrl extends FormValidator
{
/**
* Constructor.
*
* @param \PKP\form\Form $form the associated form
* @param string $field the name of the associated field
* @param string $type the type of check, either "required" or "optional"
* @param string $message the error message for validation failures (i18n key)
*/
public function __construct(&$form, $field, $type, $message)
{
$validator = new ValidatorUrl();
parent::__construct($form, $field, $type, $message, $validator);
array_push($form->cssValidation[$field], 'url');
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\form\validation\FormValidatorUrl', '\FormValidatorUrl');
}
@@ -0,0 +1,48 @@
<?php
/**
* @file classes/form/validation/FormValidatorUsername.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 FormValidatorUsername
*
* @ingroup form_validation
*
* @see FormValidator
*
* @brief Form validation check for usernames (lowercase alphanumeric with interior dash/underscore
*/
namespace PKP\form\validation;
;
use PKP\validation\ValidatorRegExp;
class FormValidatorUsername extends FormValidator
{
/**
* Constructor.
*
* @param \PKP\form\Form $form the associated form
* @param string $field the name of the associated field
* @param string $type the type of check, either "required" or "optional"
* @param string $message the error message for validation failures (i18n key)
*/
public function __construct(&$form, $field, $type, $message)
{
parent::__construct(
$form,
$field,
$type,
$message,
new ValidatorRegExp('/^[a-z0-9]+([\-_][a-z0-9]+)*$/')
);
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\form\validation\FormValidatorUsername', '\FormValidatorUsername');
}