Skip to content
Snippets Groups Projects
Select Git revision
  • 502e6a104f200acc2f8ff81ba09257a6035406e5
  • main default protected
  • studip-rector
  • ci-opt
  • course-members-export-as-word
  • data-vue-app
  • pipeline-improvements
  • webpack-optimizations
  • rector
  • icon-renewal
  • http-client-and-factories
  • jsonapi-atomic-operations
  • vueify-messages
  • tic-2341
  • 135-translatable-study-areas
  • extensible-sorm-action-parameters
  • sorm-configuration-trait
  • jsonapi-mvv-routes
  • docblocks-for-magic-methods
19 results

Form.php

Blame
  • Forked from Stud.IP / Stud.IP
    Source project has a limited visibility.
    Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    Form.php 15.11 KiB
    <?php
    
    namespace Studip\Forms;
    
    class Form extends Part
    {
    
        //models:
        protected $store_callbacks = [];
    
        //internals
        protected $inputs = [];
        protected $parts = [];
    
        //appearance in html-form
        protected $url = null;
        protected $save_button_text = '';
        protected $save_button_name = '';
    
        protected $cancel_button_text = '';
        protected $cancel_button_name = '';
        protected $autoStore = false;
        protected $debugmode = false;
        protected $success_message = '';
    
        protected $collapsable = false;
        protected $data_secure = true;
    
        //to identify a form element
        protected $id = null;
    
        /**
         * Creates a new Form object from a SORM object so that each field of the db-table becomes
         * an input-field of the form. You can modify the form by the params.
         * @param \SimpleORMap $object
         * @param array $params
         * @param string|null $url
         * @return Form
         */
        public static function fromSORM(\SimpleORMap $object, $params = [], $url = null)
        {
            $form = static::create();
            $form->addSORM($object, $params);
            if ($url) {
                $form->setURL($url);
            }
            return $form;
        }
    
    
        /**
         * A static constructor for an empty Form object.
         * @return Form
         */
        public static function create() : Form
        {
            $form = new static();
            return $form;
        }
    
        /**
         * Finalized constructor.
         *
         * @param mixed[] ...$parts
         */
        final public function __construct(...$parts)
        {
            parent::__construct(...$parts);
            //Set a default for the success message:
            $this->success_message = _('Daten wurden gespeichert.');
        }
    
        /**
         * Adds a new Fieldset to the Form object with the SORM object's fields as
         * input fields. These fields can be modified or specified by the $params array.
         * @param \SimpleORMap $object
         * @param array $params
         * @return Form $this
         */
        public function addSORM(\SimpleORMap $object, array $params = [])
        {
            $metadata = $object->getTableMetadata();
    
            // Normalize parameters
            $params = array_merge([
                'types'   => [],
                'fields'  => [],
                'without' => [],
            ], $params);
    
            if ($params['fields']) {
                //Setting the label
                foreach ($params['fields'] as $fieldname => $fielddata) {
                    if (is_string($fielddata)) {
                        $params['fields'][$fieldname] = [
                            'label' => $fielddata
                        ];
                    }
                }
                //Setting the type and name
                foreach ($params['fields'] as $fieldname => $fielddata) {
                    if (is_array($fielddata)) {
                        $meta = $metadata['fields'][$fieldname] ?? null;
                        if (!isset($fielddata['type'])) {
                            if ($meta) {
                                $fielddata = array_merge(Input::getFielddataFromMeta($meta, $object), $fielddata);
                            } else {
                                $fielddata['type'] = 'text';
                            }
    
                            $params['fields'][$fieldname] = $fielddata;
                        }
                        $params['fields'][$fieldname]['name'] = $fieldname;
                    }
                }
            } else {
                foreach ($metadata['fields'] as $attribute => $meta) {
                    if (!in_array($attribute, (array) $params['without'])) {
                        $fielddata = [
                            'label' => $attribute
                        ];
                        $fielddata = array_merge(Input::getFielddataFromMeta($meta, $object), $fielddata);
    
                        $params['fields'][$attribute] = $fielddata;
                    }
                }
            }
            foreach ($params['fields'] as $fieldname => $fielddata) {
                if (is_array($fielddata) && !array_key_exists('value', $fielddata)) {
                    if ($object->isField($fieldname)) {
                        $params['fields'][$fieldname]['value'] = $object[$fieldname];
                    }
                }
            }
            foreach ((array) $params['types'] as $fieldname => $type) {
                $params['fields'][$fieldname]['type'] = $type;
            }
            //respect the without param:
            foreach ((array) $params['without'] as $fieldname) {
                unset($params['fields'][$fieldname]);
            }
            $fields = $params['fields'];
    
            //Now initializing the fieldset:
            $fieldset = new Fieldset($params['legend'] ?: _("Daten"));
            $fieldset->setContextObject($object);
            $this->addPart($fieldset);
    
            foreach ($fields as $fieldname => $fielddata) {
                if (is_array($fielddata)) {
                    $fieldset->addInput($fieldset->getInputFromArray($fielddata));
                } elseif(is_subclass_of($fielddata, Part::class)) {
                    $fieldset->addPart($fielddata);
                } elseif(is_subclass_of($fielddata, Input::class)) {
                    $fieldset->addInput($fielddata);
                }
            }
            return $this;
        }
    
        /**
         * Sets the URL where the Form should be leading after submitting.
         * @param $url
         * @return Form $this
         */
        public function setURL($url)
        {
            $this->url = $url;
            return $this;
        }
    
        /**
         * Returns the URL where the Form is leading to after the submit.
         * @return string|null
         */
        public function getURL()
        {
            return $this->url;
        }
    
        /**
         * Sets the text for the "save" button in the form.
         *
         * @param string $text The text for the button to save the form.
         * @return $this
         */
        public function setSaveButtonText(string $text): Form
        {
            $this->save_button_text = $text;
            return $this;
        }
    
        /**
         * @return string The text for the "save" button in the form.
         */
        public function getSaveButtonText() : string
        {
            return $this->save_button_text ?: _('Speichern');
        }
    
        public function setSaveButtonName(string $name): Form
        {
            $this->save_button_name = $name;
            return $this;
        }
    
        public function getSaveButtonName() : string
        {
            return $this->save_button_name ?: $this->getSaveButtonText();
        }
    
        public function setCancelButtonText(string $text): Form
        {
            $this->cancel_button_text = $text;
            return $this;
        }
    
        /**
         * @return string The text for the "save" button in the form.
         */
        public function getCancelButtonText() : string
        {
            return $this->cancel_button_text ?: _('Abbrechen');
        }
    
        public function setCancelButtonName(string $name): Form
        {
            $this->cancel_button_name = $name;
            return $this;
        }
    
        public function getCancelButtonName() : string
        {
            return $this->cancel_button_name ?: $this->getCancelButtonText();
        }
    
        public function setSuccessMessage(string $success_message): Form
        {
            $this->success_message = $success_message;
            return $this;
        }
    
        public function setDebugMode(bool $debug = true): Form
        {
            $this->debugmode = $debug;
            return $this;
        }
    
        public function getDebugMode(): bool
        {
            return $this->debugmode;
        }
    
        public function getSuccessMessage() : string
        {
            return $this->success_message;
        }
    
        public function setCollapsable($collapsing = true)
        {
            $this->collapsable = $collapsing;
            return $this;
        }
    
        public function isCollapsable()
        {
            return $this->collapsable;
        }
    
        /**
         * Stores the Form object if this is a POST-request. This also erases the URL so that the auto-save URL
         * will be set automatically to the current $_SERVER['REQUEST_URI'].
         * @return $this
         * @throws \AccessDeniedException
         */
        public function autoStore()
        {
            $this->autoStore = true;
            if (\Request::isPost() && \Request::isAjax() && !\Request::isDialog()) {
                if (\Request::submitted('STUDIPFORM_SERVERVALIDATION')) {
                    //verify the user input:
                    $output = [];
                    foreach ($this->getAllInputs() as $input) {
                        if ($input->validate) {
                            $callback = $input->getValidationCallback();
                            $value = $this->getStorableValueFromRequest($input);
                            $valid = $callback($value, $input);
                            if ($valid !== true) {
                                $output[$input->getName()] = [
                                    'name' => $input->getName(),
                                    'label' => $input->getTitle(),
                                    'error' => $callback($value, $input)
                                ];
                            }
                        }
                    }
                    echo json_encode($output);
                    page_close();
                    die();
                } else {
                    //storing the input
                    $this->store();
                    if ($this->success_message) {
                        \PageLayout::postSuccess($this->success_message);
                    }
                    page_close();
                    die();
                }
            }
            return $this;
        }
    
        public function isAutoStoring()
        {
            return $this->autoStore;
        }
    
        /**
         * Adds a callback function that is executed right after the store-method. That callback receives this
         * Form object as the only parameter.
         * @param callable $c
         * @return Form $this
         */
        public function addStoreCallback(Callable $c): Form
        {
            $this->store_callbacks[] = $c;
            return $this;
        }
    
        /**
         * Sets if the form should be secured against accidental leaving of the page. Standard is on.
         * @param $data_secure
         * @return $this
         */
        public function setDataSecure($data_secure)
        {
            $this->data_secure = $data_secure;
            return $this;
        }
    
        public function getDataSecure() {
            return $this->data_secure;
        }
    
        /**
         * Sets the ID if this form. This ID is only relevant for plugins to identify this Form object.
         * @param string|null $id
         * @return Form $this
         */
        public function setId($id)
        {
            $this->id = $id;
            return $this;
        }
    
        /**
         * Returns the ID if this form. This ID is only relevant for plugins to identify this Form object.
         * @return string|null
         */
        public function getId()
        {
            return $this->id;
        }
    
        /**
         * Returns the number of storing processes
         * @return: a number of storing processes. 0 if nothing was stored.
         */
        public function store()
        {
            if (!\CSRFProtection::verifyRequest()) {
                throw new \AccessDeniedException();
            }
            \NotificationCenter::postNotification('FormWillStore', $this);
    
            $stored = 0;
    
            foreach ($this->getAllInputs() as $input) {
                if ($input->validate) {
                    $callback = $input->getValidationCallback();
                    $value = $this->getStorableValueFromRequest($input);
                    $valid = $callback($value, $input);
                    if ($valid !== true) {
                        return $stored;
                    }
                }
            }
    
            //store by each input
            $all_values = [];
            foreach ($this->getAllInputs() as $input) {
                $value = $this->getStorableValueFromRequest($input);
                $callback = $this->getStoringCallback($input);
                if (is_callable($callback)) {
                    $stored += $callback($value, $input);
                }
                $all_values[$input->getName()] = $value;
            }
    
            foreach ($this->parts as $part) {
                $context = $part->getContextObject();
                if ($context && method_exists($context, 'store')) {
                    $stored += $context->store();
                }
            }
    
            foreach ($this->store_callbacks as $callback) {
                if (is_callable($callback)) {
                    $stored += call_user_func($callback, $this, $all_values);
                } else {
                    //throw warning if callback is not available:
                    if ($callback === null) {
                        $callback = 'NULL';
                    }
                    trigger_error(sprintf('Could not execute callback %s in Form object.', $callback), E_USER_WARNING);
                }
            }
            return $stored;
        }
    
        /**
         * Adds a Part object to this form like a fieldset
         * @param Part $part
         * @return Form|void
         */
        public function addPart(Part $part)
        {
            $part->setParent($this);
            $this->parts[] = $part;
        }
    
        /**
         * Returns all the Part objects like Fieldsets as an array.
         * @return array
         */
        public function getParts() : array
        {
            return $this->parts;
        }
    
        /**
         * Returns the last part of the form. If there is none yet, it will create a fieldset and return that.
         * @return Part
         */
        public function getLastPart() : Part
        {
            if (count($this->parts) === 0) {
                $this->parts[] = new Fieldset();
            }
            return $this->parts[count($this->parts) - 1];
        }
    
        /**
         * Renders the whole form as a string.
         * @return string
         * @throws \Flexi_TemplateNotFoundException
         */
        public function render()
        {
            \NotificationCenter::postNotification('FormWillRender', $this);
            $template = $GLOBALS['template_factory']->open('forms/form');
            $template->form = $this;
            return $template->render();
        }
    
        /**
         * Returns the function to be used to store the value into the input. If the given Input has no storing
         * function it will generate a Closuer to set the value to the SimpleORMap context object.
         * @param $input
         * @return \Closure|void
         */
        protected function getStoringCallback(Input $input)
        {
            if ($input->store) {
                return $input->store;
            }
            $context = $input->getParent()->getContextObject();
            if (
                $context
                && is_subclass_of($context, \SimpleORMap::class)
                && ($context->isField($input->getName()) || $context->isRelation($input->getName()))
            ) {
                return function ($value) use ($context, $input) {
                    $context[$input->getName()] = $value;
                };
            }
        }
    
        /**
         * Returns the value for the Input object from the $_REQUEST. This value will also be mapped by
         * the Input's dataMapper function and after that by a special mapper-callback the Input
         * probably has.
         * @param Input $input
         * @return mixed
         */
        protected function getStorableValueFromRequest(Input $input)
        {
            $requestparam = $input->getName();
            $bracket_pos = strpos($requestparam, "[");
            if ($bracket_pos !== false) {
                $requestparam = substr($requestparam, 0, $bracket_pos);
                $value = \Request::getArray($requestparam);
                foreach ($value as $i => $v) {
                    $value[$i] = $input->dataMapper($v);
                }
            } else {
                $value = $input->getRequestValue();
                $value = $input->dataMapper($value);
            }
            if ($input->mapper && is_callable($input->mapper)) {
                $mapper = $input->mapper;
                $value = $mapper($value, $input->getContextObject());
            }
            return $value;
        }
    }