Файловый менеджер - Редактировать - /home/lmsyaran/public_html/joomla5/components/com_fabrik/models/form.php
Назад
<?php /** * Fabrik Form Model * * @package Joomla * @subpackage Fabrik * @copyright Copyright (C) 2005-2020 Media A-Team, Inc. - All rights reserved. * @license GNU/GPL http://www.gnu.org/copyleft/gpl.html */ // No direct access defined('_JEXEC') or die('Restricted access'); use Joomla\CMS\Language\Text; use Joomla\CMS\Table\Table; use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Layout\LayoutInterface; use Joomla\CMS\Filter\InputFilter; use Joomla\CMS\Filter\OutputFilter; use Joomla\CMS\Router\Route; use Joomla\CMS\Profiler\Profiler; use Joomla\CMS\Session\Session; use Joomla\CMS\HTML\HTMLHelper; use Joomla\CMS\Filesystem\File; use Joomla\CMS\Filesystem\Folder; use Fabrik\Helpers\Uploader; use Joomla\Registry\Registry; use Joomla\Utilities\ArrayHelper; use Joomla\String\StringHelper; use Joomla\CMS\Factory; jimport('joomla.application.component.model'); require_once 'fabrikmodelform.php'; /** * Fabrik Form Model * * @package Joomla * @subpackage Fabrik * @since 3.0 */ #[\AllowDynamicProperties] class FabrikFEModelForm extends FabModelForm { /** * id * @var int */ public $id = null; /** * Set to -1 if form in ajax module, set to 1+ if in package * * @var int */ public $packageId = 0; /** * Form's group elements * * @var array */ protected $elements = null; /** * List model associated with form * * @var FabrikFEModelList */ protected $listModel = null; /** * Group ids that are actually tablejoins [groupid->joinid] * * @var array */ public $aJoinGroupIds = array(); /** * If editable if 0 then show view only version of form * * @var bol true */ public $editable = true; /** * Validation rule classes * * @var array */ protected $validationRuleClasses = null; /** * The form running as a mambot or module(true) * * @var bool */ public $isMambot = false; /** * Join objects for the form * * @var array */ protected $aJoinObjs = array(); /** * Concat string to create full element names * * @var string */ public $joinTableElementStep = '___'; /** * Parameters * * @var Registry */ protected $params = null; /** * Row id to submit * * @var int */ public $rowId = null; /** * Submitted as ajax * * @since 3.0 * @var bool */ public $ajax = null; /** * Form table * * @var Table */ public $form = null; /** * Last current element found in hasElement() * * @var object */ protected $currentElement = null; /** * @var InputFilter */ protected $filter; /** * If true encase table and element names with "`" when getting element list * * @var bool */ protected $addDbQuote = false; /** * Form Data * * @var array */ public $formData = null; /** * Form errors * * @var array */ public $errors = array(); /** * Uploader helper * * @var Uploader */ protected $uploader = null; /** * Pages (array containing group ids for each page in the form) * * @var array */ protected $pages = null; /** * Session model deals with storing incomplete pages * * @var FabrikFEModelFormsession */ public $sessionModel = null; /** * Modified data by any validation rule that uses replace functionality * * @var array */ public $modifiedValidationData = null; /** * Group Models * * @var array */ public $groups = null; /** * Store the form's previous data when processing * * @var array */ public $origData = null; /** * Stores elements not shown in the list view * @var array */ protected $elementsNotInList = null; /** * Form data * * @var array */ public $data = null; /** * Form data - ready for use in template. Contains HTML output for listname___elementname * and raw value for listname___elementname_raw * * @var array */ public $tmplData = array(); /** * Form data - keys use the full element name (listname___elementname) * @var array */ public $formDataWithTableName = null; /** * Should the form store the main row? Set to false in juser * plugin if fabrik table is also #__users * * @var bool */ public $storeMainRow = true; /** * Query used to load form record. * * @var string */ public $query = null; /** * Specifies element name that have been overridden from a form plugin, * so encrypted RO data should be ignored * * @var array */ protected $pluginUpdatedElements = array(); /** * Linked fabrik lists * * @var array */ protected $linkedFabrikLists = null; /** * Are we copying a row? i.e. using form's Copy button. Plugin manager needs to know. * * @var bool */ public $copyingRow = false; /** * Container string for form plugin JS ini code * * @since 3.1b * * @var array */ public $formPluginJS = array(); /** * Form plugin files to load * * @since 3.1b * * @var array */ public $formPluginShim = array(); /** * JS options on load, only used when calling onJSOpts plugin * so plugin code can access and modify them * * @since 3.2 * * @var array */ public $jsOpts = null; /** * @var array */ public $_origData; /** * Original Row id before form is saved. * * @var string */ public $origRowId; /** * Is the form being posted via ajax. * * @var bool */ protected $ajaxPost = false; /** * Posted form data with full names? * * @var array */ public $fullFormData = array(); /** * Use this lastInsertId to store the main table's lastInsertId, so we can use this rather * than the list model lastInsertId, which could be for the last joined table rather than * the form's main table. * * @since 3.3 * * @var mixed */ public $lastInsertId = null; /** * Form plugins can set this to trigger a validation fail which isn't specific to an element * * @since 3.4 * * @var mixed */ public $formErrorMsg = null; /** * Form sessionData * * @var array */ public $sessionData = null; /** * cache tmpl name * * @since 3.7 * * @var string */ public $tmpl = null; /** * did we find any data in getData() * * @since 3.8 * * @var bool */ public $noData = false; /** * Constructor * * @param array $config An array of configuration options (name, state, dbo, table_path, ignore_request). * * @since 1.5 */ public function __construct($config = array()) { parent::__construct($config); $usersConfig = ComponentHelper::getParams('com_fabrik'); $id = $this->app->getInput()->getInt('formid', $usersConfig->get('formid')); $this->setId($id); } /** * Method to set the form id * * @param int $id list ID number * * @since 3.0 * * @return void */ public function setId($id) { // Set new form ID $this->id = $id; $this->setState('form.id', $id); // $$$ rob not sure why but we need this getState() here when assigning id from admin view $this->getState(); } /** * Set row id * * @param string $id primary key value * * @since 3.0.7 * * @return void */ public function setRowId($id) { $this->rowId = $id; } /** * Method to get the form id * * @return int */ public function getId() { return $this->getState('form.id'); } /** * Get form table (alias to getTable()) * * @return FabTable form table */ public function getForm() { return $this->getTable(); } /** * Checks if the params object has been created and if not creates and returns it * * @return object params */ public function getParams() { if (!isset($this->params)) { $form = $this->getForm(); $this->params = new Registry($form->params); } return $this->params; } /** * Should the form load up rowid=-1 usekey=foo * * @param string $priority Request priority menu or request * * @return boolean */ protected function isUserRowId($priority = 'menu') { $rowId = FabrikWorker::getMenuOrRequestVar('rowid', '', $this->isMambot, $priority); return $rowId === '-1' || $rowId === ':1'; } /** * Makes sure that the form is not viewable based on the list's access settings * * Also sets the form's editable state, if it can record in to a db table * * @return int 0 = no access, 1 = view only , 2 = full form view, 3 = add record only */ public function checkAccessFromListSettings() { $form = $this->getForm(); if ($form->record_in_database == 0) { return 2; } $listModel = $this->getListModel(); if (!is_object($listModel)) { return 2; } $data = $this->getData(); $ret = 0; if ($listModel->canViewDetails(FArrayHelper::toObject($data))) { $ret = 1; } else { if ($this->app->getInput()->get('view', 'form') == 'details') { return 0; } } //$isUserRowId = $this->isUserRowId(); /* New form can we add? * * NOTE - testing to see if $data exists rather than looking at rowid to decide if editing, as when using * rowid=-1, things get funky, as rowid is never empty, even for new form, as it's set to user id */ if (empty($data) || !array_key_exists('__pk_val', $data) || empty($data['__pk_val'])) { if ($listModel->canAdd()) { $ret = 3; } else if ($listModel->canEdit($data)) { $ret = 2; } } else { // Editing from - can we edit if ($listModel->canEdit($data)) { $ret = 2; } } // If no access (0) or read only access (1) set the form to not be editable $editable = ($ret <= 1) ? false : true; $this->setEditable($editable); if ($this->app->getInput()->get('view', 'form') == 'details') { $this->setEditable(false); } return $ret; } /** * Get the template name * * @since 3.0 * * @return string tmpl name */ public function getTmpl() { if (!isset($this->tmpl)) { $input = $this->app->getInput(); $params = $this->getParams(); $item = $this->getForm(); $tmpl = ''; // $default = FabrikWorker::j3() ? 'bootstrap' : 'default'; $default = 'bootstrap'; // $jTmplFolder = FabrikWorker::j3() ? 'tmpl' : 'tmpl25'; $jTmplFolder = 'tmpl'; $document = Factory::getDocument(); if ($document->getType() === 'pdf') { $tmpl = $params->get('pdf_template', '') !== '' ? $params->get('pdf_template') : $default; } else { if ($this->app->isClient('administrator')) { $tmpl = $this->isEditable() ? $params->get('admin_form_template') : $params->get('admin_details_template'); $tmpl = $tmpl == '' ? $default : $tmpl; } if ($tmpl == '') { if ($this->isEditable()) { $tmpl = $item->form_template == '' ? $default : $item->form_template; } else { $tmpl = $item->view_only_template == '' ? $default : $item->view_only_template; } } $tmpl = FabrikWorker::getMenuOrRequestVar('fabriklayout', $tmpl, $this->isMambot); } // Finally see if the options are overridden by a querystring var $baseTmpl = $tmpl; $tmpl = $input->get('layout', $tmpl); // Test it exists - otherwise revert to baseTmpl tmpl $folder = $this->isEditable() ? 'form' : 'details'; if (!Folder::exists(JPATH_SITE . '/components/com_fabrik/views/' . $folder . '/' . $jTmplFolder . '/' . $tmpl)) { $tmpl = $baseTmpl; } $this->isEditable() ? $item->form_template = $tmpl : $item->view_only_template = $tmpl; $this->tmpl = $tmpl; } return $this->tmpl; } /** * loads form's css files * Checks : custom css file, template css file. Including them if found * * @return void */ public function getFormCss() { $input = $this->app->getInput(); // $jTmplFolder = FabrikWorker::j3() ? 'tmpl' : 'tmpl25'; $jTmplFolder = 'tmpl'; $tmpl = $this->getTmpl(); $v = $this->isEditable() ? 'form' : 'details'; // Check for a form template file (code moved from view) if ($tmpl != '') { $qs = '?c=' . $this->getId(); $qs .= '&rowid=' . $this->getRowId(); /* $$$ need & for pdf output which is parsed through xml parser otherwise fails * If FabrikHelperHTML::styleSheetajax loaded then don't do & * J!4/F4: Never do & this will now break the parameters for css-files and it's not longer needed for pdfs */ $view = $this->isEditable() ? 'form' : 'details'; //if (FabrikHelperHTML::cssAsAsset()) //{ $qs .= '&view=' . $v; $qs .= '&rowid=' . $this->getRowId(); /*} else { $qs .= '&view=' . $v; $qs .= '&rowid=' . $this->getRowId(); } */ $tmplPath = 'templates/' . $this->app->getTemplate() . '/html/com_fabrik/' . $view . '/' . $tmpl . '/template_css.php' . $qs; if (!FabrikHelperHTML::stylesheetFromPath($tmplPath)) { FabrikHelperHTML::stylesheetFromPath('components/com_fabrik/views/' . $view . '/' . $jTmplFolder . '/' . $tmpl . '/template_css.php' . $qs); } /* $$$ hugh - as per Skype convos with Rob, decided to re-instate the custom.css convention. So I'm adding two files: * custom.css - for backward compat with existing 2.x custom.css * custom_css.php - what we'll recommend people use for custom css moving forward. */ if (!FabrikHelperHTML::stylesheetFromPath('templates/' . $this->app->getTemplate() . '/html/com_fabrik/' . $view . '/' . $tmpl . '/custom.css' . $qs)) { FabrikHelperHTML::stylesheetFromPath('components/com_fabrik/views/' . $view . '/' . $jTmplFolder . '/' . $tmpl . '/custom.css' . $qs); } $path = 'templates/' . $this->app->getTemplate() . '/html/com_fabrik/' . $view . '/' . $tmpl . '/custom_css.php' . $qs; if (!FabrikHelperHTML::stylesheetFromPath($path)) { $displayData = new stdClass; $displayData->view = $view; $displayData->tmpl = $tmpl; $displayData->qs = $qs; $displayData->jTmplFolder = $jTmplFolder; $displayData->formModel = $this; $layout = $this->getLayout('form.fabrik-custom-css-qs'); $path = $layout->render($displayData); FabrikHelperHTML::stylesheetFromPath($path); } } if ($this->app->isClient('administrator') && $input->get('tmpl') === 'components') { FabrikHelperHTML::stylesheet('administrator/templates/system/css/system.css'); } } /** * Load the JS files into the document * * @param array &$scripts Js script sources to load in the head * * @return null */ public function getCustomJsAction(&$scripts) { // $$$ hugh - added ability to use form_XX, as am adding custom list_XX $view = $this->isEditable() ? 'form' : 'details'; /** * $$$ hugh - need to use an assoc key name for the scripts array, as it gets used in the requirejs * to pass in as a function arg, which then blows up with "unexpected number" if we don't use a key name */ $scriptsKey = $view . '_' . $this->getId(); if (File::exists(COM_FABRIK_FRONTEND . '/js/' . $this->getId() . '.js')) { $scripts[$scriptsKey] = 'components/com_fabrik/js/' . $this->getId() . '.js'; } elseif (File::exists(COM_FABRIK_FRONTEND . '/js/' . $view . '_' . $this->getId() . '.js')) { $scripts[$scriptsKey] = 'components/com_fabrik/js/' . $view . '_' . $this->getId() . '.js'; } } /** * Set the browser title * * @param string $title Default browser title set by menu items' 'page_title' property * * @return string Browser title */ public function getPageTitle($title = '') { $title = $title == '' ? $this->getLabel() : $title; $groups = $this->getGroupsHiarachy(); foreach ($groups as $groupModel) { $elementModels = $groupModel->getPublishedElements(); foreach ($elementModels as $elementModel) { $element = $elementModel->getElement(); if ($element->use_in_page_title == '1') { $title .= ' ' . $elementModel->getTitlePart($this->data); } } } return $title; } /** * Compares the forms table with its groups to see if any of the groups are in fact table joins * * @param array $joins tables joins * * @return array array(group_id =>join_id) */ public function getJoinGroupIds($joins = null) { $listModel = $this->getlistModel(); if (is_null($joins)) { $joins = $listModel->getJoins(); } $arJoinGroupIds = array(); $groups = $this->getGroupsHiarachy(); foreach ($groups as $groupModel) { foreach ($joins as $join) { if ($join->element_id == 0 && $groupModel->getGroup()->id == $join->group_id) { $arJoinGroupIds[$groupModel->getId()] = $join->id; } } } $this->aJoinGroupIds = $arJoinGroupIds; return $arJoinGroupIds; } /** * Gets the javascript actions the forms elements * * @return array javascript actions */ public function getJsActions() { if (isset($this->jsActions)) { return $this->jsActions; } $this->jsActions = array(); $db = FabrikWorker::getDbo(true); $aJsActions = array(); $aElIds = array(); $groups = $this->getGroupsHiarachy(); foreach ($groups as $groupModel) { $elementModels = $groupModel->getPublishedElements(); foreach ($elementModels as $elementModel) { /* $$$ hugh - only needed getParent when we weren't saving changes to parent params to child * which we should now be doing ... and getParent() causes an extra table lookup for every child * element on the form. */ $aJsActions[$elementModel->getElement()->id] = array(); $aElIds[] = (int) $elementModel->getElement()->id; } } if (!empty($aElIds)) { $query = $db->getQuery(true); $query->select('*')->from('#__fabrik_jsactions')->where('element_id IN (' . implode(',', $aElIds) . ')'); $db->setQuery($query); $res = $db->loadObjectList(); } else { $res = array(); } if (is_array($res)) { foreach ($res as $r) { // Merge the js attributes back into the array $a = json_decode($r->params); foreach ($a as $k => $v) { $r->$k = $v; } unset($r->params); if (!isset($r->js_published) || (int) $r->js_published === 1) { $this->jsActions[$r->element_id][] = $r; } } } return $this->jsActions; } /** * Test to try to load all group data in one query and then bind that data to group table objects * in getGroups() * * @return array */ public function getPublishedGroups() { $db = FabrikWorker::getDbo(true); if (!isset($this->_publishedformGroups) || empty($this->_publishedformGroups)) { $params = $this->getParams(); $query = $db->getQuery(true); $query->select(' *, fg.group_id AS group_id, RAND() AS rand_order') ->from('#__fabrik_formgroup AS fg') ->join('INNER', '#__fabrik_groups as g ON g.id = fg.group_id') ->where('fg.form_id = ' . (int) $this->getId() . ' AND published = 1'); if ($params->get('randomise_groups') == 1) { $query->order('rand_order'); } else { $query->order('fg.ordering'); } $db->setQuery($query); $sql = (string)$query; $groups = $db->loadObjectList('group_id'); $this->_publishedformGroups = $this->mergeGroupsWithJoins($groups); } return $this->_publishedformGroups; } /** * Get the ids of all the groups in the form * * @return array group ids */ public function getGroupIds() { $groups = $this->getPublishedGroups(); return array_keys($groups); } /** * Merge in Join Ids into an array of groups * * @param array $groups form groups * * @return array */ private function mergeGroupsWithJoins($groups) { $db = FabrikWorker::getDbo(true); $form = $this->getForm(); if ($form->record_in_database) { $listModel = $this->getListModel(); $listId = (int) $listModel->getId(); if (is_object($listModel) && $listId !== 0) { $query = $db->getQuery(true); $query->select('g.id, j.id AS joinid')->from('#__fabrik_joins AS j') ->join('INNER', '#__fabrik_groups AS g ON g.id = j.group_id')->where('list_id = ' . $listId . ' AND g.published = 1'); // Added as otherwise you could potentially load a element joinid as a group join id. 3.1 $query->where('j.element_id = 0'); $db->setQuery($query); $joinGroups = $db->loadObjectList('id'); foreach ($joinGroups as $k => $o) { if (array_key_exists($k, $groups)) { $groups[$k]->join_id = $o->joinid; } } } } return $groups; } /** * Get the forms published group objects * * @return FabrikFEModelGroup[] Group model objects with table row loaded */ public function getGroups() { if (!isset($this->groups)) { $this->groups = array(); $listModel = $this->getListModel(); $groupModel = Factory::getApplication()->bootComponent('com_fabrik')->getMVCFactory()->createModel('Group', 'FabrikFEModel'); $groupData = $this->getPublishedGroups(); foreach ($groupData as $id => $groupD) { $thisGroup = clone ($groupModel); $thisGroup->setId($id); $thisGroup->setContext($this, $listModel); // $$ rob 25/02/2011 this was doing a query per group - pointless as we bind $groupD to $row afterwards // $row = $thisGroup->getGroup(); $row = FabTable::getInstance('Group', 'FabrikTable'); $row->bind($groupD); $thisGroup->setGroup($row); if ($row->published == 1) { $this->groups[$id] = $thisGroup; } } } return $this->groups; } /** * Gets each element in the form along with its group info * * @param bool $excludeUnpublished included unpublished elements in the result * * @return array element objects */ public function getFormGroups($excludeUnpublished = true) { $params = $this->getParams(); $db = FabrikWorker::getDbo(true); $query = $db->getQuery(true); $query ->select( '*, #__fabrik_groups.params AS gparams, #__fabrik_elements.id as element_id , #__fabrik_groups.name as group_name, RAND() AS rand_order')->from('#__fabrik_formgroup') ->join('LEFT', '#__fabrik_groups ON #__fabrik_formgroup.group_id = #__fabrik_groups.id') ->join('LEFT', '#__fabrik_elements ON #__fabrik_groups.id = #__fabrik_elements.group_id') ->where('#__fabrik_formgroup.form_id = ' . (int) $this->getState('form.id')); if ($excludeUnpublished) { $query->where('#__fabrik_elements.published = 1'); } if ($params->get('randomise_groups') == 1) { $query->order('rand_order, #__fabrik_elements.ordering'); } else { $query->order('#__fabrik_formgroup.ordering, #__fabrik_formgroup.group_id, #__fabrik_elements.ordering'); } $db->setQuery($query); $groups = $db->loadObjectList(); $this->elements = $groups; return $groups; } /** * Similar to getFormGroups() except that this returns a data structure of * form * --->group * -------->element * -------->element * --->group * if run before then existing data returned * * @return FabrikFEModelGroup[] Group & element objects */ public function getGroupsHiarachy() { if (!isset($this->groups)) { $this->getGroups(); $this->groups = FabrikWorker::getPluginManager()->getFormPlugins($this); } return $this->groups; } /** * Get an list of elements that aren't shown in the table view * * @return array of element table objects */ public function getElementsNotInTable() { if (!isset($this->elementsNotInList)) { $this->elementsNotInList = array(); $groups = $this->getGroupsHiarachy(); foreach ($groups as $group) { $elements = $group->getPublishedElements(); foreach ($elements as $elementModel) { if ($elementModel->canView() || $elementModel->canUse()) { $element = $elementModel->getElement(); if (!isset($element->show_in_list_summary) || !$element->show_in_list_summary) { $this->elementsNotInList[] = $element; } } } } } return $this->elementsNotInList; } /** * This checks to see if the form has a file upload element * and returns the correct encoding type for the form * * @return string form encoding type */ public function getFormEncType() { $groups = $this->getGroupsHiarachy(); foreach ($groups as $groupModel) { $elementModels = $groupModel->getPublishedElements(); foreach ($elementModels as $elementModel) { if ($elementModel->isUpload()) { return "multipart/form-data"; } } } return "application/x-www-form-urlencoded"; } /** * Get the plugin manager * * @deprecated use return FabrikWorker::getPluginManager(); instead since 3.0b * * @return object plugin manager */ public function getPluginManager() { return FabrikWorker::getPluginManager(); } /** * When the form is submitted we want to get the original record it * is updating - this is used in things like the file upload element * to check for changes in uploaded files and process the difference * * @return array */ protected function setOrigData() { $input = $this->app->getInput(); if ($this->isNewRecord() || !$this->getForm()->record_in_database) { $this->_origData = array(new stdClass); } else { /* * $$$ hugh - when loading origdata on editing of a rowid=-1/usekey form, * the rowid will be set to the actual form tables's rowid, not the userid, * so we need to unset 'usekey', otherwise we end up with the wrong row. * I thought we used to take care of this elsewhere? * * $$$ 7/25/2017 - don't think this is true any more? * * $$$ 9/5/2017 = yup, still seems to be necesaasy, definitely when submitting with a juser plugin. So * reverted changes made on 7/25, and will keep an eye out for situations where doing this causes problems. */ $isUserRow = $this->isUserRowId(); if ($isUserRow) { $origUseKey = $input->get('usekey', ''); $input->set('usekey', ''); } $listModel = $this->getListModel(); $fabrikDb = $listModel->getDb(); $sql = $this->buildQuery(); $fabrikDb->setQuery($sql); $this->_origData = $fabrikDb->loadObjectList(); if ($isUserRow) { $input->set('usekey', $origUseKey); } } } /** * Get the form record's original data - before any alterations were made to it * in the form * * @return array */ public function getOrigData() { if (!isset($this->_origData)) { $this->setOrigData(); } return $this->_origData; } /** * test if orig data is empty. Made this a function, as it's not a simple test * for empty(), and code outside the model shouldn't need to know it'll be a one * entry array with an empty stdClass in it. * * @return bool */ public function origDataIsEmpty() { if (!isset($this->_origData)) { $this->setOrigData(); } return (empty($this->_origData) || (count($this->_origData) == 1 && count((array) $this->_origData[0]) == 0)); } /** * Are we copying a row? Usually set in controller process(). * * @param bool $set if true, set copyingRow to true * * @return bool */ public function copyingRow($set = false) { if ($set) { $this->copyingRow = true; } return $this->copyingRow; } /** * Processes the form data and decides what action to take * * @return bool false if one of the plugins returns an error otherwise true */ public function process() { $profiler = Profiler::getInstance('Application'); JDEBUG ? $profiler->mark('process: start') : null; $input = $this->app->getInput(); error_reporting(error_reporting() ^ (E_WARNING | E_NOTICE)); @set_time_limit(300); $form = $this->getForm(); $pluginManager = FabrikWorker::getPluginManager(); $sessionModel = Factory::getApplication()->bootComponent('com_fabrik')->getMVCFactory()->createModel('Formsession', 'FabrikFEModel'); $sessionModel->setFormId($this->getId()); $sessionModel->setRowId($this->rowId); /* $$$ rob rowId can be updated by jUser plugin so plugin can use check (for new/edit) * now looks at origRowId */ $this->origRowId = $this->rowId; JDEBUG ? $profiler->mark('process, getGroupsHiarachy: start') : null; $this->getGroupsHiarachy(); if ($form->record_in_database == '1') { JDEBUG ? $profiler->mark('process, setOrigData: start') : null; $this->setOrigData(); } /* * $$$ hugh - we do this prior to processToDb(), but turns out we need formDataWithTableName in * some plugins, like 'php', which run $formModel->getProcessData(). But it's kind of a chicken * and egg, because those same plugins my change $formData. Anyway, only solution for now is * set up $this->formDataWithTaleName here, so they at least have the posted data to work with, * then do it again after all the plugins have run. So, rule of thumb ... plugins running onBeforeProcess * or onBeforeStore need to modify formData, not formDataWithTableName. */ $this->formDataWithTableName = $this->formData; JDEBUG ? $profiler->mark('process, onBeforeProcess plugins: start') : null; if (in_array(false, $pluginManager->runPlugins('onBeforeProcess', $this))) { return false; } $this->removeEmptyNoneJoinedGroupData($this->formData); JDEBUG ? $profiler->mark('process, setFormData: start') : null; $this->setFormData(); JDEBUG ? $profiler->mark('process, _doUpload: start') : null; if (!$this->_doUpload()) { return false; } /** $$$ rob 27/10/2011 - moved above _doUpload as code in there is trying to update formData which is not yet set * this->setFormData(); */ JDEBUG ? $profiler->mark('process, onBeforeStore plugins: start') : null; if (in_array(false, $pluginManager->runPlugins('onBeforeStore', $this))) { return false; } $this->formDataWithTableName = $this->formData; if ($form->record_in_database == '1') { $rowid = $this->processToDB(); /* * I want to add the following, but have a feeling some things will break if I do. * Currently when adding a new form, getRowId() will never give you the new rowid, * which kinda sucks. But ... I'm pretty sure if I set it, then there's existing code * which will break it does yield the new rowid after submission. Need to test this * thoroughly before enabling it. */ /* if ($this->getRowId() === '') { $this->setRowId($rowid); } */ } // Clean the cache. $cache = Factory::getCache($input->get('option')); $cache->clean(); // $$$rob run this before as well as after onAfterProcess (ONLY for redirect plugin) // so that any redirect urls are available for the plugin (e.g twitter) JDEBUG ? $profiler->mark('process, onLastProcess plugins: start') : null; $pluginManager->runPlugins('onLastProcess', $this); JDEBUG ? $profiler->mark('process, onAfterProcess plugins: start') : null; if (in_array(false, $pluginManager->runPlugins('onAfterProcess', $this))) { // $$$ rob this no longer stops default redirect (not needed any more) // returning false here stops the default redirect occurring return false; } // Need to remove the form session before redirect plugins occur $sessionModel->remove(); // $$$rob used ONLY for redirect plugins JDEBUG ? $profiler->mark('process, onLastProcess plugins: start') : null; if (in_array(false, $pluginManager->runPlugins('onLastProcess', $this))) { // $$$ rob this no longer stops default redirect (not needed any more) // returning false here stops the default redirect occurring return false; } // Clean both admin and front end cache. parent::cleanCache('com_fabrik', 1); parent::cleanCache('com_fabrik', 0); JDEBUG ? $profiler->mark('process: end') : null; return true; } /** * Perform file uploads * * @return bool */ protected function _doUpload() { $oUploader = $this->getUploader(); $oUploader->upload(); if ($oUploader->moveError) { return false; } return true; } /** * Update the data that gets posted via the form and stored by the form * model. Used in elements to modify posted data see file upload * * @param string $key in key.dot.format to set a recursive array * @param string|array $val value to set to * @param bool $update_raw automatically update _raw key as well * @param bool $override_ro update data even if element is RO * * @return void */ public function updateFormData($key, $val, $update_raw = false, $override_ro = false) { if (strstr($key, '.')) { $nodes = explode('.', $key); $count = count($nodes); $pathNodes = $count - 1; if ($pathNodes < 0) { $pathNodes = 0; } $ns = &$this->formData; for ($i = 0; $i <= $pathNodes; $i++) { // If any node along the registry path does not exist, create it if (!isset($ns[$nodes[$i]])) { $ns[$nodes[$i]] = array(); } $ns = &$ns[$nodes[$i]]; } $ns = $val; // $$$ hugh - changed name of $ns, as re-using after using it to set by reference was borking things up! $nsTable = &$this->formDataWithTableName; for ($i = 0; $i <= $pathNodes; $i++) { // If any node along the registry path does not exist, create it if (!isset($nsTable[$nodes[$i]])) { $nsTable[$nodes[$i]] = array(); } $nsTable = &$nsTable[$nodes[$i]]; } $nsTable = $val; // $$$ hugh - changed name of $ns, as re-using after using it to set by reference was borking things up! $nsFull = &$this->fullFormData; for ($i = 0; $i <= $pathNodes; $i++) { // If any node along the registry path does not exist, create it if (!isset($nsFull[$nodes[$i]])) { $nsFull[$nodes[$i]] = array(); } $nsFull = &$nsFull[$nodes[$i]]; } $nsFull = $val; // $$$ hugh - FIXME - nope, this won't work! We don't know which path node is the element name. // $$$ hugh again - should now work, with little preg_replace hack, if last part is numeric, then second to last will be element name if ($update_raw) { if (preg_match('#\.\d+$#', $key)) { $key = preg_replace('#(.*)(\.\d+)$#', '$1_raw$2', $key); } else { $key .= '_raw'; } $nodes = explode('.', $key); $count = count($nodes); $pathNodes = $count - 1; if ($pathNodes < 0) { $pathNodes = 0; } $nsRaw = &$this->formData; for ($i = 0; $i <= $pathNodes; $i++) { // If any node along the registry path does not exist, create it if (!isset($nsRaw[$nodes[$i]])) { $nsRaw[$nodes[$i]] = array(); } $nsRaw = &$nsRaw[$nodes[$i]]; } $nsRaw = $val; $nsRawFull = $this->fullFormData; for ($i = 0; $i <= $pathNodes; $i++) { // If any node along the registry path does not exist, create it if (!isset($nsRawFull[$nodes[$i]])) { $nsRawFull[$nodes[$i]] = array(); } $nsRawFull = &$nsRawFull[$nodes[$i]]; } $nsRawFull = $val; } } else { if (isset($this->formData)) { $this->formData[$key] = $val; $this->formDataWithTableName[$key] = $val; } // Check if set - for case where you have a fileupload element & confirmation plugin - when plugin is trying to update non-existent data if (isset($this->fullFormData)) { $this->fullFormData[$key] = $val; } /* * Need to allow RO (encrypted) elements to be updated. Consensus is that * we should actually modify the actual encrypted element in the $_REQUEST, * but turns out this is a major pain in the butt (see _cryptViewOnlyElements() in the * form view for details!). Main problem is we need to know if it's a join and/or repeat group, * which means loading up the element model. So for now, just going to add the element name to a * class array, $this->pluginUpdatedElements[], which we'll check in addDefaultDataFromRO() * in the table model, or wherever else we need it. */ /* if (array_key_exists('fabrik_vars', $_REQUEST) && array_key_exists('querystring', $_REQUEST['fabrik_vars']) && array_key_exists($key, $_REQUEST['fabrik_vars']['querystring'])) { $crypt = FabrikWorker::getCrypt(); // turns out it isn't this simple, of course! see above $_REQUEST['fabrik_vars']['querystring'][$key] = $crypt->encrypt($val); } */ // add element name to this array, which will then cause this element to be skipped // during the RO data phase of writing the row. Don't think it really matter what we set it to, // might as well be the value. Note that we need the new $override_ro arg, as some elements // use updateFormData() as part of normal operation, which should default to NOT overriding RO. if ($override_ro) { $this->pluginUpdatedElements[$key] = $val; } if ($update_raw) { $key .= '_raw'; $this->formData[$key] = $val; $this->formDataWithTableName[$key] = $val; if (isset($this->fullFormData)) { $this->fullFormData[$key] = $val; } if ($override_ro) { $this->pluginUpdatedElements[$key] = $val; } } } } /** * Intended for use by things like PHP form plugin code, PHP validations, etc., * so folk don't have to access formData directly. * * @param string $fullName full element name * @param bool $raw get raw data * @param mixed $default value * @param string $repeatCount repeat count if needed * * @since 3.0.6 * * @return mixed */ public function getElementData($fullName, $raw = false, $default = '', $repeatCount = null) { $data = isset($this->formData) ? $this->formData : $this->data; $value = null; if ($raw) { $fullName .= '_raw'; } // Simplest case, element name exists in main group if (is_array($data) && array_key_exists($fullName, $data)) { $value = $data[$fullName]; } /* Maybe we are being called from onAfterProcess hook, or somewhere else * running after store, when non-joined data names have been reduced to short * names in formData, so peek in formDataWithTableName */ elseif (isset($this->formDataWithTableName) && array_key_exists($fullName, $this->formDataWithTableName)) { $value = $this->formDataWithTableName[$fullName]; } if (isset($value) && isset($repeatCount) && is_array($value)) { $value = FArrayHelper::getValue($value, $repeatCount, $default); } // If we didn't find it, set to default if (!isset($value)) { $value = $default; } return $value; } /** * This will strip the html from the form data according to the * filter settings applied from article manager->parameters * see here - http://forum.joomla.org/index.php/topic,259690.msg1182219.html#msg1182219 * * @return array form data */ public function &setFormData() { if (isset($this->formData)) { return $this->formData; } list($this->dofilter, $this->filter) = FabrikWorker::getContentFilter(); $this->ajaxPost = $this->app->getInput()->getBool('fabrik_ajax'); // Set up post data, and copy values to raw (for failed form submissions) $data = $_POST; $this->copyToRaw($data); /** * $$$ hugh - quite a few places in code that runs after this want __pk_val, * so if it doesn't exist, grab it from the PK element. */ if (!array_key_exists('__pk_val', $data)) { /** * $$$ hugh - There HAS to be an easier way of getting the PK element name, that doesn't involve calling getPrimaryKeyAndExtra(), * which is a horribly expensive operation. */ $primaryKey = $this->getListModel()->getPrimaryKey(true); $data['__pk_val'] = FArrayHelper::getValue($data, $primaryKey . '_raw', FArrayHelper::getValue($data, $primaryKey, '')); } // Apply querystring values if not already in post (so qs values doesn't overwrite the submitted values for dbjoin elements) $data = array_merge($data, $_REQUEST); array_walk_recursive($data, array($this, '_clean')); // Set here so element can call formModel::updateFormData() $this->formData = $data; $this->fullFormData = $this->formData; $this->session->set('com_fabrik.form.data', $this->formData); return $this->formData; } /** * Called from setFormData to clean up posted data from either ajax or posted form * used in array_walk_recursive() method * * @param mixed &$item (string or array) * * @return void */ protected function _clean(&$item) { if (is_array($item)) { array_walk_recursive($item, array($this, '_clean')); } else { if ($this->dofilter) { //$item = preg_replace('/%([0-9A-F]{2})/mei', "chr(hexdec('\\1'))", $item); $item = preg_replace_callback('/%([0-9A-F]{2})/mi', function ($matches) { return chr(hexdec($matches[1])); }, $item); if ($this->ajaxPost) { $item = rawurldecode($item); } if ($this->dofilter) { @$item = $this->filter->clean($item); } } else { if ($this->ajaxPost) { $item = rawurldecode($item); } } } } /** * Loop over elements and call their preProcess() method * * @return void */ private function callElementPreProcess() { $input = $this->app->getInput(); $repeatTotals = $input->get('fabrik_repeat_group', array(0), 'array'); $groups = $this->getGroupsHiarachy(); // Currently this is just used by calculation elements foreach ($groups as $groupModel) { $group = $groupModel->getGroup(); $repeatedGroupCount = FArrayHelper::getValue($repeatTotals, $group->id, 0, 'int'); $elementModels = $groupModel->getPublishedElements(); for ($c = 0; $c < $repeatedGroupCount; $c++) { foreach ($elementModels as $elementModel) { $elementModel->preProcess($c); } } } } /** * Without this the first groups repeat data was always being saved (as it was posted but hidden * on the form. * * @param array &$data posted form data * * @return void */ protected function removeEmptyNoneJoinedGroupData(&$data) { $repeats = FArrayHelper::getValue($data, 'fabrik_repeat_group', array()); $groups = $this->getGroups(); foreach ($repeats as $groupId => $c) { if ($c == 0) { $group = $groups[$groupId]; if ($group->isJoin()) { continue; } $elements = $group->getPublishedElements(); foreach ($elements as $elementModel) { $name = $elementModel->getElement()->name; $data[$name] = ''; $data[$name . '_raw'] = ''; } } } } /** * Prepare the submitted form data for copying * * @return string Original records reference */ protected function prepareForCopy() { $listModel = $this->getListModel(); $item = $listModel->getTable(); $k = $item->db_primary_key; $k = FabrikString::safeColNameToArrayKey($k); $origId = FArrayHelper::getValue($this->formData, $k, ''); // COPY function should create new records if (array_key_exists('Copy', $this->formData)) { $this->rowId = ''; $this->formData[$k] = ''; $this->formData['rowid'] = ''; } // set the repeat group counts (mutated from the base view code which creates the hidden input) $groups = $this->getGroupsHiarachy(); $fabrikRepeatGroup = array(); foreach ($groups as $groupModel) { // Check if group is actually a table join $repeatGroup = 1; $foreignKey = null; if ($groupModel->canRepeat()) { if ($groupModel->isJoin()) { $joinModel = $groupModel->getJoinModel(); $joinTable = $joinModel->getJoin(); if (is_object($joinTable)) { $elementModels = $groupModel->getPublishedElements(); reset($elementModels); $tmpElement = current($elementModels); $smallerElHTMLName = $tmpElement->getFullName(true, false); $repeatGroup = count($this->formData[$smallerElHTMLName]); } } } $groupModel->repeatTotal = $repeatGroup; $fabrikRepeatGroup[$groupModel->getId()] = $repeatGroup; } $this->app->getInput()->set('fabrik_repeat_group', $fabrikRepeatGroup); return $origId; } /** * As part of the form process we may need to update the referring url if making a copy * * @param string $origId Original record ref * @param string $insertId New insert reference * * @return void referrer */ protected function updateReferrer($origId, $insertId) { $input = $this->app->getInput(); // Set the redirect page to the form's url if making a copy and set the id to the new insert id if (array_key_exists('Copy', $this->formData)) { $u = str_replace('rowid=' . $origId, 'rowid=' . $insertId, $input->get('HTTP_REFERER', '', 'string')); $input->set('fabrik_referrer', $u); } } /** * Set various request / input arrays with the main records insert id * * @param string $insertId The records insert id * * @return void */ public function setInsertId($insertId) { $input = $this->app->getInput(); $listModel = $this->getListModel(); $item = $listModel->getTable(); $tmpKey = str_replace("`", "", $item->db_primary_key); $tmpKey = str_replace('.', '___', $tmpKey); $this->formData[$tmpKey] = $insertId; $this->formData[$tmpKey . '_raw'] = $insertId; $this->formData[FabrikString::shortColName($item->db_primary_key)] = $insertId; $this->formData[FabrikString::shortColName($item->db_primary_key) . '_raw'] = $insertId; $this->fullFormData[$tmpKey] = $insertId; $this->fullFormData[$tmpKey . '_raw'] = $insertId; $this->fullFormData['rowid'] = $insertId; $this->formData['rowid'] = $insertId; $this->formDataWithTableName[$tmpKey] = $insertId; $this->formDataWithTableName[$tmpKey . '_raw'] = $insertId; $this->formDataWithTableName['rowid'] = $insertId; $input->set($tmpKey, $insertId); $input->set('rowid', $insertId); // $$$ hugh - pretty sure we need to unset 'usekey' now, as it is not relevant to joined data, // and it messing with storeRow of joins $input->set('usekey', ''); } /** * Process groups when the form is submitted * * @param int $parentId insert ID of parent table * * @return void */ protected function processGroups($parentId = null) { $groupModels = $this->getGroups(); foreach ($groupModels as $groupModel) { // Jaanus: if group is visible if ($groupModel->canView() && $groupModel->canEdit()) { $groupModel->process($parentId); } } } /** * Process individual elements when submitting the form * Used for multi-select join elements which need to store data in * related tables * * @since 3.1rc2 * * @return void */ protected function processElements() { $groups = $this->getGroupsHiarachy(); foreach ($groups as $groupModel) { $elementModels = $groupModel->getPublishedElements(); foreach ($elementModels as $elementModel) { $elementModel->onFinalStoreRow($this->formData); } } } /** * Process the form to the database * * @return string Insert id */ public function processToDB() { $profiler = Profiler::getInstance('Application'); JDEBUG ? $profiler->mark('processToDb: start') : null; $pluginManager = FabrikWorker::getPluginManager(); $listModel = $this->getListModel(); $origId = $this->prepareForCopy(); $this->formData = $listModel->removeTableNameFromSaveData($this->formData, '___'); JDEBUG ? $profiler->mark('processToDb, submitToDatabase: start') : null; $insertId = $this->storeMainRow ? $this->submitToDatabase($this->rowId) : $this->rowId; $this->updateReferrer($origId, $insertId); $this->setInsertId($insertId); // Store join data JDEBUG ? $profiler->mark('processToDb, processGroups: start') : null; $this->processGroups($insertId); // Enable db join checkboxes in repeat groups to save data JDEBUG ? $profiler->mark('processToDb, processElements: start') : null; $this->processElements(); JDEBUG ? $profiler->mark('processToDb, onBeforeCalculations plugins: start') : null; if (in_array(false, $pluginManager->runPlugins('onBeforeCalculations', $this))) { return $insertId; } JDEBUG ? $profiler->mark('processToDb, doCalculations: start') : null; $this->listModel->doCalculations(); JDEBUG ? $profiler->mark('processToDb: end') : null; return $insertId; } /** * Saves the form data to the database * * @param string $rowId If '' then insert a new row - otherwise update this row id * * @return mixed insert id (or rowid if updating existing row) if ok, else string error message */ protected function submitToDatabase($rowId = '') { $this->getGroupsHiarachy(); $groups = $this->getGroupsHiarachy(); $listModel = $this->getListModel(); $listModel->encrypt = array(); $data = array(); foreach ($groups as $groupModel) { // Joined groups stored in groupModel::process(); if ($groupModel->isJoin()) { continue; } $elementModels = $groupModel->getPublishedElements(); foreach ($elementModels as $elementModel) { if ($elementModel->encryptMe()) { $listModel->encrypt[] = $elementModel->getElement()->name; } // Following line added to fix importcsv where data from first row is used for every row. $elementModel->defaults = null; $elementModel->onStoreRow($data); } } $listModel = $this->getListModel(); $listModel->setFormModel($this); $listModel->getTable(); $listModel->storeRow($data, $rowId); $this->lastInsertId = $listModel->lastInsertId; $useKey = $this->app->getInput()->get('usekey', ''); if (!empty($useKey)) { return $listModel->lastInsertId; } else { return ($rowId == '') ? $listModel->lastInsertId : $rowId; } } /** * Get the form's list model * (was getTable but that clashed with J1.5 func) * * @return FabrikFEModelList fabrik list model */ public function getListModel() { if (!isset($this->listModel)) { $this->listModel = Factory::getApplication()->bootComponent('com_fabrik')->getMVCFactory()->createModel('List', 'FabrikFEModel'); $item = $this->getForm(); $this->listModel->loadFromFormId($item->id); $this->listModel->setFormModel($this); } return $this->listModel; } /** * Get the class names for each of the validation rules * * @deprecated (was only used in element label) * * @return array (validationruleid => classname ) */ public function loadValidationRuleClasses() { if (is_null($this->validationRuleClasses)) { $validationRules = FabrikWorker::getPluginManager()->getPlugInGroup('validationrule'); $classes = array(); foreach ($validationRules as $rule) { $classes[$rule->name] = $rule->name; } $this->validationRuleClasses = $classes; } return $this->validationRuleClasses; } /** * Give elements a way to re-instate data after a validation failure, for example upload elements, * where the value won't be in the submitted data, in order to preserve state ** * @return void */ public function setValidationFailedData() { $this->getGroupsHiarachy(); $groups = $this->getGroupsHiarachy(); foreach ($groups as $groupModel) { $elementModels = $groupModel->getPublishedElements(); foreach ($elementModels as $elementModel) { $elementModel->setValidationFailedData($this->formData); } } } /** * Add in any encrypted stuff, in case we fail validation ... * otherwise it won't be in $data when we rebuild the page. * Need to do it here, so _raw fields get added in the next chunk 'o' code. * * @param array &$post posted form data passed by reference * * @return null */ public function addEncrytedVarsToArray(&$post) { if (array_key_exists('fabrik_vars', $_REQUEST) && array_key_exists('querystring', $_REQUEST['fabrik_vars'])) { $groups = $this->getGroupsHiarachy(); $crypt = FabrikWorker::getCrypt(); $w = new FabrikWorker; foreach ($groups as $g => $groupModel) { $elementModels = $groupModel->getPublishedElements(); foreach ($elementModels as $elementModel) { $elementModel->getElement(); foreach ($_REQUEST['fabrik_vars']['querystring'] as $key => $encrypted) { if ($elementModel->getFullName(true, false) == $key) { /* $$$ rob - don't test for !canUse() as confirmation plugin dynamically sets this * if ($elementModel->canView()) * $$$ hugh - testing adding non-viewable, non-editable elements to encrypted vars */ if (is_array($encrypted)) { // Repeat groups $v = array(); foreach ($encrypted as $e) { // $$$ rob urldecode when posting from ajax form $e = urldecode($e); $e = empty($e) ? '' : $crypt->decrypt($e); $e = FabrikWorker::JSONtoData($e); $v[] = $w->parseMessageForPlaceHolder($e, $post); } } else { // $$$ rob urldecode when posting from ajax form $encrypted = urldecode($encrypted); $v = empty($encrypted) ? '' : $crypt->decrypt($encrypted); /* * $$$ hugh - things like element list elements (radios, etc) seem to use * their JSON data for encrypted read only values, need to decode. */ if (is_subclass_of($elementModel, 'PlgFabrik_ElementList')) { $v = FabrikWorker::JSONtoData($v, true); } $v = $w->parseMessageForPlaceHolder($v, $post); } $elementModel->setGroupModel($groupModel); $elementModel->setValuesFromEncryt($post, $key, $v); /* $$ rob set both normal and rawvalues to encrypted - otherwise validate method doesn't * pick up decrypted value */ $elementModel->setValuesFromEncryt($post, $key . '_raw', $v); } } } } } } /** * When submitting data copy values to _raw equivalent * * @param array &$post Form data * @param bool $override Override existing raw data when copying to raw * * @return null */ public function copyToRaw(&$post, $override = false) { $this->copyToFromRaw($post, 'toraw', $override); } /** * Copy raw data to non-raw data * * @param array &$post Form data * @param bool $override Override existing raw data when copying from raw * * @return null */ public function copyFromRaw(&$post, $override = false) { $this->copyToFromRaw($post, 'fromraw', $override); } /** * Copy raw data to non-raw data OR none-raw to raw * * @param array &$post Form data * @param string $direction Either - toraw OR fromraw - defines which data to copy to where raw/none-raw * @param bool $override Override existing raw data when copying from raw * * @return null */ protected function copyToFromRaw(&$post, $direction = 'toraw', $override = false) { $groups = $this->getGroupsHiarachy(); $input = $this->app->getInput(); foreach ($groups as $groupModel) { $elementModels = $groupModel->getPublishedElements(); foreach ($elementModels as $elementModel) { $elName2 = $elementModel->getFullName(true, false); $elName2Raw = $elName2 . '_raw'; if ($direction === 'toraw') { if (!array_key_exists($elName2Raw, $post) || $override) { // Post required getValue() later on $input->set($elName2Raw, FArrayHelper::getValue($post, $elName2, '')); $post[$elName2Raw] = FArrayHelper::getValue($post, $elName2, ''); } } else { if (!array_key_exists($elName2 . '_raw', $post) || $override) { // Post required getValue() later on $input->set($elName2, FArrayHelper::getValue($post, $elName2Raw, '')); $post[$elName2] = FArrayHelper::getValue($post, $elName2Raw, ''); } } } } } /** * Has the form failed a validation * * @return bool */ public function failedValidation() { return $this->hasErrors(); } /** * Validate the form * modifies post data to include validation replace data * * @return bool true if form validated ok */ public function validate() { $input = $this->app->getInput(); if ((bool) $input->getBool('fabrik_ignorevalidation', false) === true) { // Put in when saving page of form return true; } $pluginManager = Factory::getApplication()->bootComponent('com_fabrik')->getMVCFactory()->createModel('Pluginmanager', 'FabrikFEModel'); $pluginManager->getPlugInGroup('validationrule'); $post = $this->setFormData(); // Contains any data modified by the validations $this->modifiedValidationData = array(); $w = new FabrikWorker; $ok = true; // $$$ rob 01/07/2011 fileupload needs to examine records previous data for validations on editing records $this->setOrigData(); // $$$ rob copy before addEncrytedVarsToArray as well as after // so that any placeholders(.._raw) contained in the encrypted vars are correctly replaced $this->copyToRaw($post); /* $$$ rob for PHP 5.2.1 (and potential up to before 5.2.6) $post is not fully associated with formData - * so the above copToRaw does not update $this->formData. * $$$ hugh - had to add the &, otherwise replace validations weren't work, as modifying * $post wasn't modifying $this->formData. Which is weird, as I thought all array assignments * were by reference? * $$$ hugh - FIXME - wait ... what ... hang on ... we assign $this->formData in $this->setFormData(), * which we assigned to $post a few lines up there ^^. Why are we now assigning $post back to $this->formData?? */ $this->formData = &$post; /* $$$ hugh - add in any encrypted stuff, in case we fail validation ... * otherwise it won't be in $data when we rebuild the page. * Need to do it here, so _raw fields get added in the next chunk 'o' code. */ $this->addEncrytedVarsToArray($post); // $$$ hugh - moved this to after addEncryptedVarsToArray(), so read only data is // available to things like calcs running in preProcess phase. $this->callElementPreProcess(); // Add in raw fields - the data is already in raw format so just copy the values $this->copyToRaw($post); $groups = $this->getGroupsHiarachy(); $repeatTotals = $input->get('fabrik_repeat_group', array(0), 'array'); $ajaxPost = $input->getBool('fabrik_ajax'); $joinData = array(); foreach ($groups as $groupModel) { $groupCounter = $groupModel->getGroup()->id; $elementModels = $groupModel->getPublishedElements(); if ($groupModel->isJoin()) { $joinModel = $groupModel->getJoinModel(); } foreach ($elementModels as $elementModel) { // If the user can't view or edit the element, then don't validate it. Otherwise user sees failed validation but no indication of what failed if (!$elementModel->canUse() && !$elementModel->canView()) { continue; } $elDbValues = array(); $elementModel->getElement(); $validationRules = $elementModel->validator->findAll(); // $$ rob incorrect for ajax validation on joined elements // $elName = $elementModel->getFullName(true, false); $elName = $input->getBool('fabrik_ajax') ? $elementModel->getHTMLId(0) : $elementModel->getFullName(true, false); $this->errors[$elName] = array(); $elName2 = $elementModel->getFullName(true, false); // $$$rob fix notice on validation of multi-page forms if (!array_key_exists($groupCounter, $repeatTotals)) { $repeatTotals[$groupCounter] = 1; } for ($c = 0; $c < $repeatTotals[$groupCounter]; $c++) { $this->errors[$elName][$c] = array(); // $$$ rob $this->formData was $_POST, but failed to get anything for calculation elements in php 5.2.1 $formData = $elementModel->getValue($this->formData, $c, array('runplugins' => 0, 'use_default' => false, 'use_querystring' => false)); // Internal element plugin validations if (!$elementModel->validate(@$formData, $c)) { $ok = false; $this->errors[$elName][$c][] = $elementModel->getValidationErr(); } /** * $$$ rob 11/04/2012 was stopping multiselect/chx dbjoin elements from saving in normal group. * if ($groupModel->canRepeat() || $elementModel->isJoin()) */ if ($groupModel->canRepeat()) { // $$$ rob for repeat groups no join setting to array() means that $_POST only contained the last repeat group data // $elDbValues = array(); $elDbValues[$c] = $formData; } else { $elDbValues = $formData; } // Validations plugins attached to elements if (!$elementModel->mustValidate()) { continue; } foreach ($validationRules as $plugin) { $plugin->formModel = $this; if ($plugin->shouldValidate($formData, $c)) { if (!$plugin->validate($formData, $c)) { $this->errors[$elName][$c][] = $w->parseMessageForPlaceHolder($plugin->getMessage()); $ok = false; } if (method_exists($plugin, 'replace')) { if ($groupModel->canRepeat()) { $elDbValues[$c] = $formData; $testReplace = $plugin->replace($elDbValues[$c], $c); if ($testReplace != $elDbValues[$c]) { $elDbValues[$c] = $testReplace; $this->modifiedValidationData[$elName][$c] = $testReplace; $joinData[$elName2 . '_raw'][$c] = $testReplace; $post[$elName . '_raw'][$c] = $testReplace; } } else { $testReplace = $plugin->replace($elDbValues, $c); if ($testReplace != $elDbValues) { $elDbValues = $testReplace; $this->modifiedValidationData[$elName] = $testReplace; $input->set($elName . '_raw', $elDbValues); $post[$elName . '_raw'] = $elDbValues; } } } } } } if ($groupModel->isJoin() || $elementModel->isJoin()) { $joinData[$elName2] = $elDbValues; } else { $input->set($elName, $elDbValues); $post[$elName] = $elDbValues; } // Unset the defaults or the orig submitted form data will be used (see date plugin mysql vs form format) $elementModel->defaults = null; } } // Insert join data into request array foreach ($joinData as $key => $val) { $input->set($key, $val); $post[$key] = $val; } if (!empty($this->errors)) { FabrikWorker::getPluginManager()->runPlugins('onError', $this); } FabrikHelperHTML::debug($this->errors, 'form:errors'); //echo "<pre>";print_r($this->errors);exit; $this->setErrors($this->errors); return $ok; } /** * Helper method to get the session context - apply row id only if not '' as * accessing session data with a path '..' appears not to be possible * * @return string */ public function getSessionContext() { $context = 'com_fabrik.form.' . $this->getId() . '.'; $rowId = $this->getRowId(); if ($rowId !== '') { $context .= $rowId . '.'; } return $context; } /** * Get form validation errors - if empty test session for errors * 31/01/13 - no longer restoring from session errors - see http://fabrikar.com/forums/showthread.php?t=31377 * 19/02/13 - Changed from http_referer test to this->isMambot to restore session errors when redirecting from a non-ajax form * in module that has failed validation - see http://fabrikar.com/forums/showthread.php?t=31870 * * @return array errors */ public function getErrors() { // Store errors in local array as clearErrors() removes $this->errors $errors = array(); if (empty($this->errors)) { if ($this->isMambot) { $errors = $this->session->get($this->getSessionContext() . 'errors', array()); } } else { $errors = $this->errors; } $this->clearErrors(); $this->errors = $errors; return $this->errors; } /** * Clear form validation errors * * @return void */ public function clearErrors() { $this->errors = array(); $context = $this->getSessionContext(); $this->session->clear($context . 'errors'); /* $$$ rob this was commented out, but putting back in to test issue that if we have ajax validations on * and a field is validated, then we don't submit the form, and go back to add the form, the previously validated * values are shown in the form. */ $this->session->set($context . 'session.on', false); } /** * Set form validation errors in session * * @param array $errors error messages * * @return void */ public function setErrors($errors) { $context = $this->getSessionContext(); $this->session->set($context . 'errors', $errors); $this->session->set($context . 'session.on', true); } /** * Get a JSON encoded string of error and modified data messages * * @return string */ public function getJsonErrors() { $data = array('modified' => $this->modifiedValidationData, 'errors' => $this->errors); return json_encode($data); } /** * Should the form do a spoof check * * @return bool */ public function spoofCheck() { $fbConfig = ComponentHelper::getParams('com_fabrik'); return $this->getParams()->get('spoof_check', $fbConfig->get('spoofcheck_on_formsubmission', true)); } /** * Get an instance of the uploader object * * @return object uploader */ public function &getUploader() { if (is_null($this->uploader)) { $this->uploader = new Uploader($this); } return $this->uploader; } /** * Get the forms table name * * @deprecated - not used? * * @return string table name */ public function getTableName() { $this->getListModel(); return $this->getListModel()->getTable()->db_table_name; } /** * Get the form row * * @param string $name table name * @param string $prefix table name prefix * @param array $options initial state options * * @return FabTable form row */ public function getTable($name = '', $prefix = 'Table', $options = array()) { if (is_null($this->form)) { $this->form = parent::getTable('Form', 'FabrikTable'); } $id = $this->getId(); if ($this->form->id != $id) { $this->form->load($id); } return $this->form; } /** * Determines if the form can be published * * @return bool true if publish dates are ok */ public function canPublish() { $form = $this->getForm(); $publishUp = FabrikWorker::isNullDate($form->publish_up) ? null : Factory::getDate($form->publish_up)->toUnix(); $publishDown = FabrikWorker::isNullDate($form->publish_down) ? null : Factory::getDate($form->publish_down)->toUnix(); $now = $this->date->toUnix(); if ($form->published == '1') { if ($now >= $publishUp) { if ($now <= $publishDown || $publishDown === null) { return true; } } } return false; } /** * Create a drop down list of all the elements in the form * * @param string $name Drop down name * @param string $default Current value * @param bool $excludeUnpublished Add elements that are unpublished * @param bool $useStep Concat table name and el name with '___' (true) or "." (false) * @param bool $incRaw Include raw labels default = true * @param string $key What value should be used for the option value 'name' (default) or 'id' @since 3.0.7 * @param string $attribs Select list attributes @since 3.1b * * @return string html list */ public function getElementList($name = 'order_by', $default = '', $excludeUnpublished = false, $useStep = false, $incRaw = true, $key = 'name', $attribs = 'class="inputbox" size="1"') { $aEls = $this->getElementOptions($useStep, $key, false, $incRaw); asort($aEls); // Paul - Prepend rather than append "none" option. array_unshift($aEls, HTMLHelper::_('select.option', '', '-')); return HTMLHelper::_('select.genericlist', $aEls, $name, $attribs, 'value', 'text', $default); } /** * Get an array of the form's element's ids * * @param array $ignore ClassNames to ignore e.g. array('FabrikModelFabrikCascadingdropdown') * @param array $opts Property 'includePublished' can be set to 0; @since 3.0.7 * Property 'loadPrefilters' @since 3.0.7.1 - used to ensure that pre-filter elements are loaded in inline edit * * @return array int ids */ public function getElementIds($ignore = array(), $opts = array()) { $aEls = array(); $groups = $this->getGroupsHiarachy(); foreach ($groups as $groupModel) { $elementModels = $groupModel->getPublishedElements(); foreach ($elementModels as $elementModel) { $this->getElementIds_check($elementModel, $ignore, $opts, $aEls); } } if (FArrayHelper::getValue($opts, 'loadPrefilters', false)) { $listModel = $this->getListModel(); list($afilterFields, $afilterConditions, $afilterValues, $afilterAccess, $afilterEval, $afilterJoins, $aFilterType) = $listModel->prefilterSetting(); foreach ($afilterFields as $name) { $raw = preg_match("/_raw$/", $name) > 0; $name = $name ? FabrikString::rtrimword($name, '_raw') : $name; $elementModel = $this->getElement($name); } } return $aEls; } /** * Helper function for getElementIds(), test if the element should be added * * @param plgFabrik_Element $elementModel Element model * @param array $ignore ClassNames to ignore e.g. array('FabrikModelFabrikCascadingdropdown') * @param array $opts Filter options * @param array &$aEls Array of element ids to load * * @return void */ private function getElementIds_check($elementModel, $ignore, $opts, &$aEls) { $class = get_class($elementModel); if (!in_array($class, $ignore)) { $element = $elementModel->getElement(); if (!(FArrayHelper::getValue($opts, 'includePublised', true) && $element->published == 0)) { $aEls[] = (int) $element->id; } } } /** * Creates options array to be then used by getElementList to create a drop down of elements in the form * separated as elements need to collate this options from multiple forms * * @param bool $useStep concat table name and el name with '___' (true) or "." (false) * @param string $key name of key to use (default "name") * @param bool $show_in_list_summary only show those elements shown in table summary * @param bool $incRaw include raw labels in list (default = false) Only works if $key = name * @param array $filter list of plugin names that should be included in the list - if empty include all plugin types * @param string $labelMethod An element method that if set can alter the option's label * Used to only show elements that can be selected for search all * @param bool $noJoins do not include elements in joined tables (default false) * @param bool $translate run label through translation (default true) * * @return array html options */ public function getElementOptions($useStep = false, $key = 'name', $show_in_list_summary = false, $incRaw = false, $filter = array(), $labelMethod = '', $noJoins = false, $translate = true) { $groups = $this->getGroupsHiarachy(); $aEls = array(); foreach ($groups as $gid => $groupModel) { if ($noJoins && $groupModel->isJoin()) { continue; } $elementModels = $groupModel->getMyElements(); $prefix = $groupModel->isJoin() ? $groupModel->getJoinModel()->getJoin()->table_join . '.' : ''; foreach ($elementModels as $elementModel) { $el = $elementModel->getElement(); if (!empty($filter) && !in_array($el->plugin, $filter)) { continue; } if ($show_in_list_summary == true && $el->show_in_list_summary != 1) { continue; } $val = $el->$key; $label = strip_tags($prefix . $el->label); if ($translate) { $label = Text::_($label); } if ($labelMethod !== '') { $elementModel->$labelMethod($label); } if ($key != 'id') { $val = $elementModel->getFullName($useStep, false); if ($this->addDbQuote) { $val = FabrikString::safeColName($val); } if ($incRaw && is_a($elementModel, 'PlgFabrik_ElementDatabasejoin')) { /* @FIXME - next line had been commented out, causing undefined warning for $rawVal * on following line. Not sure if getrawColumn is right thing to use here though, * like, it adds filed quotes, not sure if we need them. */ if ($elementModel->getElement()->published != 0) { $rawVal = $elementModel->getRawColumn($useStep); if (!$this->addDbQuote) { $rawVal = str_replace('`', '', $rawVal); } $aEls[$label . '(raw)'] = HTMLHelper::_('select.option', $rawVal, $label . '(raw)'); } } } $aEls[] = HTMLHelper::_('select.option', $val, $label); } } // Paul - Sort removed so that list is presented in group/id order regardless of whether $key is name or id // asort($aEls); return $aEls; } /** * Called via ajax nav * * @param int $dir 1 - move forward, 0 move back * * @return bool new row id loaded. */ public function paginateRowId($dir) { $db = FabrikWorker::getDbo(); $input = $this->app->getInput(); $c = $dir == 1 ? '>=' : '<='; $intLimit = $dir == 1 ? 2 : 0; $listModel = $this->getListModel(); $item = $listModel->getTable(); $rowId = $input->getString('rowid', '', 'string'); $query = $db->getQuery(true); $query->select($item->db_primary_key . ' AS ' . FabrikString::safeColNameToArrayKey($item->db_primary_key))->from($item->db_table_name) ->where($item->db_primary_key . ' ' . $c . ' ' . $db->q($rowId)); $query = $listModel->buildQueryOrder($query); $db->setQuery($query, 0, $intLimit); $ids = $db->loadColumn(); if ($dir == 1) { if (count($ids) >= 2) { $input->set('rowid', $ids[$dir]); return true; } else { return false; } } if (count($ids) - 2 >= 0) { $input->set('rowid', $ids[count($ids) - 2]); return true; } return false; } /** * Get the last insert id, for situations where we need the 'rowid' for newly inserted forms, * and can't use getRowId() because it caches rowid as empty. For example, in plugins running * onAfterProcess, like upsert. * * Note that $this->lastInsertId is getting set in the */ public function getInsertId() { return $this->lastInsertId; } /** * Are we creating a new record or editing an existing one? * Put here to ensure compat when we go from 3.0 where rowid = 0 = new, to row id '' = new * * @since 3.0.9 * * @return boolean */ public function isNewRecord() { if ($this->getRowId() === '') { return true; } else { /* * special case when using 'useley', rowid will be set on submission, even on a new record, * so test for hidden 'nodata' field, which is set on form load if getData() finds no existing data. */ if ($this->app->getInput()->get('task', '') === 'form.process') { $opts = array( 'formid' => $this->getId() ); $useKey = FabrikWorker::getMenuOrRequestVar('usekey', '', $this->isMambot, 'var', $opts); if ($useKey && $this->app->getInput()->get('nodata', '') === '1') { return true; } } } return false; } /** * Get the current records row id * setting a rowid of -1 will load in the current users record (used in * conjunction with usekey variable * * setting a rowid of -2 will load in the last created record * * @return string rowid */ public function getRowId() { if (isset($this->rowId)) { return $this->rowId; } $input = $this->app->getInput(); $usersConfig = ComponentHelper::getParams('com_fabrik'); // $$$rob if we show a form module when in a fabrik form component view - we shouldn't use // the request rowid for the content plugin as that value is destined for the component if ($this->isMambot && $input->get('option') == 'com_fabrik') { $this->rowId = $usersConfig->get('rowid'); } else { $this->rowId = FabrikWorker::getMenuOrRequestVar('rowid', $usersConfig->get('rowid'), $this->isMambot); if ($this->rowId == -2) { // If the default was set to -2 (load last row) then a pagination form plugin's row id should override menu settings $this->rowId = FabrikWorker::getMenuOrRequestVar('rowid', $usersConfig->get('rowid'), $this->isMambot, 'request'); } } if ($this->getListModel()->getParams()->get('sef-slug', '') !== '') { $this->rowId = explode(':', $this->rowId); $this->rowId = array_shift($this->rowId); } // $$$ hugh - for some screwed up reason, when using SEF, rowid=-1 ends up as :1 // $$$ rob === compare as otherwise 0 == ":1" which meant that the users record was loaded if ($this->isUserRowId()) { $this->rowId = '-1'; } // Set rowid to -1 to load in the current users record switch ($this->rowId) { case '-1': // New rows (no logged in user) should be '' $this->rowId = $this->user->get('id') == 0 ? '' : $this->user->get('id'); break; case '-2': // Set rowid to -2 to load in the last recorded record $this->rowId = $this->getMaxRowId(); break; } /** * $$$ hugh - added this as a Hail Mary sanity check, make sure * rowId is an empty string if for whatever reason it's still null, * as we have code in various place that checks for $this->rowId === '' * to detect adding new form. So if at this point rowid is null, we have * to assume it's a new form, and set rowid to empty string. */ if (is_null($this->rowId)) { $this->rowId = ''; } /** * $$$ hugh - there's a couple of places, like calendar viz, that add &rowid=0 to * query string for new form, so check for that and set to empty string. */ if ($this->rowId === '0') { $this->rowId = ''; } FabrikWorker::getPluginManager()->runPlugins('onSetRowId', $this); return $this->rowId; } /** * Collates data to write out the form * * @return mixed bool */ public function render() { $profiler = Profiler::getInstance('Application'); JDEBUG ? $profiler->mark('formmodel render: start') : null; // $$$rob required in paolo's site when rendering modules with ajax option turned on $this->listModel = null; $this->setRowId($this->getRowId()); /* * $$$ hugh - need to call this here as we set $this->editable here, which is needed by some plugins * hmmmm, this means that getData() is being called from checkAccessFromListSettings(), * so plugins running onBeforeLoad will have to unset($formModel->_data) if they want to * do something funky like change the rowid being loaded. Not a huge problem, but caught me out * when a custom PHP onBeforeLoad plugin I'd written for a client suddenly broke. */ $this->checkAccessFromListSettings(); $pluginManager = FabrikWorker::getPluginManager(); $res = $pluginManager->runPlugins('onBeforeLoad', $this); if (in_array(false, $res)) { return false; } JDEBUG ? $profiler->mark('formmodel render: getData start') : null; $data = $this->getData(); JDEBUG ? $profiler->mark('formmodel render: getData end') : null; $res = $pluginManager->runPlugins('onLoad', $this); if (in_array(false, $res)) { return false; } // @TODO - relook at this: // $this->_reduceDataForXRepeatedJoins(); JDEBUG ? $profiler->mark('formmodel render end') : null; $this->session->set('com_fabrik.form.' . $this->getId() . '.data', $this->data); // $$$ rob return res - if its false the the form will not load return $res; } /** * Get the max row id - used when requesting rowid=-2 to return the last recorded detailed view * * @return int max row id */ protected function getMaxRowId() { if (!$this->getForm()->record_in_database) { return $this->rowId; } $listModel = $this->getListModel(); $fabrikDb = $listModel->getDb(); $item = $listModel->getTable(); $k = FabrikString::safeNameQuote($item->db_primary_key); // @TODO JQuery this $fabrikDb->setQuery("SELECT MAX($k) FROM " . FabrikString::safeColName($item->db_table_name) . $listModel->buildQueryWhere()); return $fabrikDb->loadResult(); } /** * If a submit plugin wants to fail validation not specific to an element * * @param string $errMsg */ public function setFormErrorMsg($errMsg) { $this->formErrorMsg = $errMsg; } /** * Does the form contain user errors * * @return bool */ public function hasErrors() { $errorsFound = false; if (isset($this->formErrorMsg)) { $errorsFound = true; } $allErrors = $this->isMambot ? $this->session->get($this->getSessionContext() . 'errors', array()) : $this->errors; foreach ($allErrors as $field => $errors) { if (!empty($errors) & is_array($errors)) { foreach ($errors as $error) { if (!empty($error[0])) { $errorsFound = true; } } } } if ($this->saveMultiPage(false)) { $sessionRow = $this->getSessionData(); /* * Test if its a resumed paged form * if so _arErrors will be filled so check all elements had no errors */ $multiPageErrors = false; if ($sessionRow->data != '') { foreach ($this->errors as $err) { if (!empty($err[0])) { $multiPageErrors = true; } } if (!$multiPageErrors) { $errorsFound = false; } } } return $errorsFound; } /** * Main method to get the data to insert into the form * * @return array Form's data */ public function getData() { // If already set return it. If not was causing issues with the juser form plugin // when it tried to modify the form->data info, from within its onLoad method, when sync user option turned on. if (isset($this->data)) { return $this->data; } $this->getRowId(); $input = $this->app->getInput(); $profiler = Profiler::getInstance('Application'); JDEBUG ? $profiler->mark('formmodel getData: start') : null; $this->data = array(); $f = InputFilter::getInstance(); /* * $$$ hugh - we need to remove any elements from the query string, * if the user doesn't have access, otherwise ACL's on elements can * be bypassed by just setting value on form load query string! * * Also remove all form data if task is form.process, in case some plugin is trying to reload * form data. */ $clean_request = $f->clean($_REQUEST, 'array'); $qs_request = array(); foreach ($clean_request as $key => $value) { $test_key = FabrikString::rtrimword($key, '_raw'); $elementModel = $this->getElement($test_key, false, false); if ($elementModel !== false) { if (!$elementModel->canUse() || $this->app->getInput()->get('task', '') === 'form.process' || $this->app->getInput()->get('task', '') === 'process' || $this->hasErrors() ) { unset($clean_request[$key]); } else { if (is_array($value)) { $newValue = []; foreach ($value as $key => $item) { $newValue[$key] = urldecode($item); } } else { $newValue = urldecode($value); } $clean_request[$key] = $qs_request[$key] = $newValue; } } } $data = $clean_request; $form = $this->getForm(); $this->getGroupsHiarachy(); JDEBUG ? $profiler->mark('formmodel getData: groups loaded') : null; if (!$form->record_in_database) { FabrikHelperHTML::debug($data, 'form:getData from $_REQUEST'); $data = $f->clean($_REQUEST, 'array'); } else { JDEBUG ? $profiler->mark('formmodel getData: start get list model') : null; $listModel = $this->getListModel(); JDEBUG ? $profiler->mark('formmodel getData: end get list model') : null; $fabrikDb = $listModel->getDb(); JDEBUG ? $profiler->mark('formmodel getData: db created') : null; $listModel->getTable(); JDEBUG ? $profiler->mark('formmodel getData: table row loaded') : null; $this->aJoinObjs = $listModel->getJoins(); JDEBUG ? $profiler->mark('formmodel getData: joins loaded') : null; if ($this->hasErrors()) { // $$$ hugh - if we're a mambot, reload the form session state we saved in // process() when it banged out. if ($this->isMambot) { $sessionRow = $this->getSessionData(); $this->sessionModel->last_page = 0; if ($sessionRow->data != '') { $sData = unserialize($sessionRow->data); $data = FArrayHelper::toObject($sData, 'stdClass', false); OutputFilter::objectHTMLSafe($data); $data = array($data); FabrikHelperHTML::debug($data, 'form:getData from session (form in Mambot and errors)'); } } else { // $$$ rob - use setFormData rather than $_GET // as it applies correct input filtering to data as defined in article manager parameters $this->setValidationFailedData($this->formData); $data = $this->setFormData(); $data = FArrayHelper::toObject($data, 'stdClass', false); // $$$rob ensure "<tags>text</tags>" that are entered into plain text areas are shown correctly OutputFilter::objectHTMLSafe($data); $data = ArrayHelper::fromObject($data); FabrikHelperHTML::debug($data, 'form:getData from POST (form not in Mambot and errors)'); } } else { $sessionLoaded = false; // Test if its a resumed paged form if ($this->saveMultiPage()) { $sessionRow = $this->getSessionData(); JDEBUG ? $profiler->mark('formmodel getData: session data loaded') : null; if ($sessionRow->data != '') { $sessionLoaded = true; /* * $$$ hugh - this chunk should probably go in setFormData, but don't want to risk any side effects just now * problem is that later failed validation, non-repeat join element data is not formatted as arrays, * but from this point on, code is expecting even non-repeat join data to be arrays. */ $tmp_data = unserialize($sessionRow->data); /* $groups = $this->getGroupsHiarachy(); foreach ($groups as $groupModel) { if ($groupModel->isJoin() && !$groupModel->canRepeat()) { foreach ($tmp_data['join'][$groupModel->getJoinId()] as &$el) { $el = array($el); } } } */ $bits = $data; $bits = array_merge($tmp_data, $bits); //$data = array(FArrayHelper::toObject($bits)); $data = $bits; FabrikHelperHTML::debug($data, 'form:getData from session (form not in Mambot and no errors'); } } if (!$sessionLoaded) { /* Only try and get the row data if its an active record * use !== '' as rowid may be alphanumeric. * Unlike 3.0 rowId does equal '' if using rowid=-1 and user not logged in */ $opts = array( 'formid' => $this->getId() ); $useKey = FabrikWorker::getMenuOrRequestVar('usekey', '', $this->isMambot, 'var', $opts); if (!empty($useKey) || $this->rowId !== '') { // $$$ hugh - once we have a few join elements, our select statements are // getting big enough to hit default select length max in MySQL. $listModel->setBigSelects(); // Otherwise lets get the table record /** * $$$ hugh - 11/14/2015 - ran into issue with the order by from a list being added to the form query, when * rendering a form with a content plugin in a list intro. And I don't think we ever need to * apply ordering to a form's select, by definition it's only one row. Leaving this here for * now just as a reminder in case there's any unforeseen side effects. * * $$$ hugh - 4/25/2016 - yes, there is an issue, as (duh!) ordering is needed for repeat groups. * Changing this back to original for now, will need to work out how to handle that corner case */ $opts = $input->get('task') == 'form.inlineedit' ? array('ignoreOrder' => true) : array(); // $opts = array('ignoreOrder' => true); $sql = $this->buildQuery($opts); $fabrikDb->setQuery($sql); FabrikHelperHTML::debug((string) $fabrikDb->getQuery(), 'form:render'); $rows = $fabrikDb->loadObjectList(); if (is_null($rows)) { // JError::raiseWarning(500, $fabrikDb->getErrorMsg()); \Joomla\CMS\Factory::getApplication()->enqueueMessage($fabrikDb->getErrorMsg(), 'error'); } JDEBUG ? $profiler->mark('formmodel getData: rows data loaded') : null; // $$$ rob Ack above didn't work for joined data where there would be n rows returned for "this rowid = $this->rowId \n"; if (!empty($rows)) { // Only do this if the query returned some rows (it wont if usekey on and userid = 0 for example) $data = array(); foreach ($rows as &$row) { if (empty($data)) { // If loading in a rowid=-1 set the row id to the actual row id $this->rowId = isset($row->__pk_val) ? $row->__pk_val : $this->rowId; } $row = empty($row) ? array() : ArrayHelper::fromObject($row); $request = $clean_request; $request = array_merge($row, $request); $data[] = FArrayHelper::toObject($request); } $this->noData = false; } else { $this->noData = true; } FabrikHelperHTML::debug($data, 'form:getData from querying rowid= ' . $this->rowId . ' (form not in Mambot and no errors)'); // If empty data return and trying to edit a record then show error JDEBUG ? $profiler->mark('formmodel getData: empty test') : null; // Was empty($data) but that is never empty. Had issue where list prefilter meant record was not loaded, but no message shown in form if (empty($rows) && $this->rowId != '') { // $$$ hugh - special case when using -1, if user doesn't have a record yet if ($this->isUserRowId()) { // set data to just elements that have been set on the qs (and "cleaned" / ACL checked) $this->data = $qs_request; return; } else { // If no key found set rowid to 0 so we can insert a new record. if (empty($useKey) && !$this->isMambot && in_array($input->get('view'), array('form', 'details'))) { $this->rowId = ''; /** * runtime exception is a little obtuse for people getting here from legitimate links, * like from an email, but aren't logged in so run afoul of a pre-filter, etc * So do the 3.0 thing, and raise a warning */ //throw new RuntimeException(Text::_('COM_FABRIK_COULD_NOT_FIND_RECORD_IN_DATABASE')); // JError::raiseWarning(500, Text::_('COM_FABRIK_COULD_NOT_FIND_RECORD_IN_DATABASE')); \Joomla\CMS\Factory::getApplication()->enqueueMessage(Text::_('COM_FABRIK_COULD_NOT_FIND_RECORD_IN_DATABASE'), 'warning'); } else { // If we are using usekey then there's a good possibility that the record // won't yet exist - so in this case suppress this error message $this->rowId = ''; } } } } // No need to setJoinData if you are correcting a failed validation if (!empty($data)) { $this->setJoinData($data); } } } } $this->data = $data; FabrikHelperHTML::debug($data, 'form:data'); JDEBUG ? $profiler->mark('queryselect: getData() end') : null; return $this->data; } /** * Checks if user is logged in and form multi-page settings to determine * if the form saves to the session table on multi-page navigation * * @param bool $useSessionOn Return true if Session contains session.on - used in confirmation * plugin to re-show the previously entered form data. Not used in $this->hasErrors() otherwise logged in users * can not get the confirmation plugin to work * * @return bool */ public function saveMultiPage($useSessionOn = true) { $params = $this->getParams(); // Set in plugins such as confirmation plugin $pluginManager = FabrikWorker::getPluginManager(); $pluginManager->runPlugins('usesSession', $this, 'form'); if (in_array(true, $pluginManager->data)) { if ($this->session->get($this->getSessionContext() . 'session.on') == true && $useSessionOn) { return true; } } $save = (int) $params->get('multipage_save', 0); if ($this->user->get('id') !== 0) { return $save === 0 ? false : true; } else { return $save === 2 ? true : false; } } /** * If editing a record which contains repeated join data then on start $data is an * array with each records being a row in the database. * * We need to take this structure and convert it to the same format as when the form * is submitted * * @param array &$data form data * * @return void */ public function setJoinData(&$data) { $this->_joinDefaultData = array(); if (empty($data)) { return; } // No joins so leave ! if (!is_array($this->aJoinObjs) || $this->rowId === '') { return; } if (!array_key_exists(0, $data)) { $data[0] = new stdClass; } $groups = $this->getGroupsHiarachy(); /** * $$$ hugh - adding the "PK's seen" stuff, otherwise we end up adding multiple * rows when we have multiple repeat groups. For instance, if we had two repeated * groups, one with 2 repeats and one with 3, we ended up with 6 repeats for each * group, with 3 and 2 copies of each respectively. So we need to track which * instances of each repeat we have already copied into the main row. * * So $joinPksSeen will be indexed by $joinPksSeen[groupid][elementid] */ $joinPksSeen = array(); /** * Have to copy the data for the PK's seen stuff, as we're modifying the original $data * as we go, which screws up the PK logic once we've modified the PK value itself in the * original $data. Probably only needed for $data[0], as that's the only row we actually * modify, but for now I'm just copying the whole thing, which then gets used for doing the ... * $joinPkVal = $data_copy[$row_index]->$joinPk; * ... inside the $data iteration below. * * PS, could probably just do a $data_copy = $data, as our usage of the copy isn't going to * involve nested arrays (which get copied by reference when using =), but I've been burned * so many times with array copying, I'm going to do a "deep copy" using serialize/unserialize! */ $data_copy = unserialize(serialize($data)); foreach ($groups as $groupId => $groupModel) { $group = $groupModel->getGroup(); $joinPksSeen[$groupId] = array(); $elementModels = $groupModel->getMyElements(); foreach ($elementModels as $elementModelID => $elementModel) { if ($groupModel->isJoin() || $elementModel->isJoin()) { if ($groupModel->isJoin()) { $joinModel = $groupModel->getJoinModel(); $joinPk = $joinModel->getForeignID(); $joinPksSeen[$groupId][$elementModelID] = array(); } $names = $elementModel->getJoinDataNames(); foreach ($data as $row_index => $row) { // Might be a string if new record ? $row = (object) $row; if ($groupModel->isJoin()) { /** * If the join's PK element isn't published or for any other reason not * in $data, we're hosed! */ if (!isset($data_copy[$row_index]->$joinPk)) { continue; } $joinPkVal = $data_copy[$row_index]->$joinPk; /** * if we've seen the PK value for this element's row before, skip it. * Check for empty as well, just in case - as we're loading existing data, * it darn well should have a value! */ if (empty($joinPkVal) || in_array($joinPkVal, $joinPksSeen[$groupId][$elementModelID])) { continue; } } for ($i = 0; $i < count($names); $i ++) { $name = $names[$i]; if (property_exists($row, $name)) { $v = $row->$name; $v = FabrikWorker::JSONtoData($v, $elementModel->isJoin()); // New record or csv export if (!isset($data[0]->$name)) { $data[0]->$name = $v; } if (!is_array($data[0]->$name)) { if ($groupModel->isJoin() && $groupModel->canRepeat()) { $v = array($v); } $data[0]->$name = $v; } else { if ($groupModel->isJoin() && $groupModel->canRepeat()) { $n =& $data[0]->$name; $n[] = $v; } } } } if ($groupModel->isJoin()) { /** * Make a Note To Self that we've now handled the data for this element's row, * and can skip it from now on. */ $joinPksSeen[$groupId][$elementModelID][] = $joinPkVal; } } } } } // Remove the additional rows - they should have been merged into [0] above. if no [0] then use main array $data = ArrayHelper::fromObject(FArrayHelper::getValue($data, 0, $data)); } /** * Get the forms session data (used when using multi-page forms) * * @return object session data */ protected function getSessionData() { if (isset($this->sessionData)) { return $this->sessionData; } $params = $this->getParams(); $this->sessionModel = Factory::getApplication()->bootComponent('com_fabrik')->getMVCFactory()->createModel('Formsession', 'FabrikFEModel'); $this->sessionModel->setFormId($this->getId()); $this->sessionModel->setRowId($this->rowId); $useCookie = (int) $params->get('multipage_save', 0) === 2 ? true : false; if (!$useCookie) { // In case a plugin is using cookie session (e.g. confirmation plugin) $useCookie = $this->sessionModel->canUseCookie(); } $this->sessionModel->useCookie($useCookie); $this->sessionData = $this->sessionModel->load(); return $this->sessionData; } /** * Create the sql query to get the rows data for insertion into the form * * @param array $opts key: ignoreOrder ignores order by part of query * Needed for inline edit, as it only selects certain fields, order by on a db join element returns 0 results * * @deprecated use buildQuery() instead * * @return string sql query to get row */ public function _buildQuery($opts = array()) { return $this->buildQuery($opts); } /** * Create the sql query to get the rows data for insertion into the form * * @param array $opts key: ignoreOrder ignores order by part of query * Needed for inline edit, as it only selects certain fields, order by on a db join element returns 0 results * * @return string query */ public function buildQuery($opts = array()) { if (isset($this->query)) { return $this->query; } $db = FabrikWorker::getDbo(); $input = $this->app->getInput(); $form = $this->getForm(); if (!$form->record_in_database) { return; } $listModel = $this->getListModel(); $item = $listModel->getTable(); $sql = $listModel->buildQuerySelect('form'); $sql .= $listModel->buildQueryJoin(); $emptyRowId = $this->rowId === '' ? true : false; $random = $input->get('random'); $opts = array( 'formid' => $this->getId() ); $useKey = FabrikWorker::getMenuOrRequestVar('usekey', '', $this->isMambot, 'var', $opts); if ($useKey != '') { $useKey = explode('|', $useKey); foreach ($useKey as &$tmpK) { $tmpK = !strstr($tmpK, '.') ? $item->db_table_name . '.' . $tmpK : $tmpK; $tmpK = FabrikString::safeColName($tmpK); } if (!is_array($this->rowId)) { $aRowIds = explode('|', $this->rowId); } } $comparison = $input->get('usekey_comparison', '='); $viewPk = $input->get('view_primary_key'); // $$$ hugh - changed this to !==, as in rowid=-1/usekey situations, we can have a rowid of 0 // I don't THINK this will have any untoward side effects, but ... if ((!$random && !$emptyRowId) || !empty($useKey)) { $sql .= ' WHERE '; if (!empty($useKey)) { $sql .= "("; $parts = array(); for ($k = 0; $k < count($useKey); $k++) { /** * * For gory reasons, we have to assume that an empty string cannot be a valid rowid * when using usekey, so just create a 1=-1 if it is. */ if ($aRowIds[$k] === '') { $parts[] = ' 1=-1'; continue; } // Ensure that the key value is not quoted as we Quote() afterwards if ($comparison == '=') { $parts[] = ' ' . $useKey[$k] . ' = ' . $db->q($aRowIds[$k]); } else { $parts[] = ' ' . $useKey[$k] . ' LIKE ' . $db->q('%' . $aRowIds[$k] . '%'); } } $sql .= implode(' AND ', $parts); $sql .= ')'; } else { $sql .= ' ' . $item->db_primary_key . ' = ' . $db->q($this->rowId); } } else { if ($viewPk != '') { $sql .= ' WHERE ' . $viewPk . ' '; } elseif ($random) { // $$$ rob Should this not go after prefilters have been applied ? $sql .= ' ORDER BY RAND() LIMIT 1 '; } } // Get pre-filter conditions from table and apply them to the record // the false, ignores any filters set by the table $where = $listModel->buildQueryWhere(false); if (strstr($sql, 'WHERE')) { // Do it this way as queries may contain sub-queries which we want to keep the where $firstWord = StringHelper::substr($where, 0, 5); if ($firstWord == 'WHERE') { $where = StringHelper::substr_replace($where, 'AND', 0, 5); } } // Set rowId to -2 to indicate random record if ($random) { $this->setRowId(-2); } // $$$ rob ensure that all prefilters are wrapped in brackets so that // only one record is loaded by the query - might need to set $word = and? if (trim($where) != '') { $where = explode(' ', $where); $word = array_shift($where); $sql .= $word . ' (' . implode(' ', $where) . ')'; } if (!$random && FArrayHelper::getValue($opts, 'ignoreOrder', false) === false) { // $$$ rob if showing joined repeat groups we want to be able to order them as defined in the table $sql .= $listModel->buildQueryOrder(); } $this->query = $sql; return $sql; } /** * Attempts to determine if the form contains the element * * @param string $searchName Element name to search for * @param bool $checkInt Check search name against element id * @param bool $checkShort Check short element name * * @return bool true if found, false if not found */ public function hasElement($searchName, $checkInt = false, $checkShort = true) { $groups = $this->getGroupsHiarachy(); foreach ($groups as $groupModel) { $elementModels = $groupModel->getMyElements(); if (!is_array($groupModel->elements)) { continue; } foreach ($groupModel->elements as $elementModel) { $element = $elementModel->getElement(); if ($checkInt) { if ($searchName == $element->id) { $this->currentElement = $elementModel; return true; } } if ($searchName == $element->name && $checkShort) { $this->currentElement = $elementModel; return true; } if ($searchName == $elementModel->getFullName(true, false)) { $this->currentElement = $elementModel; return true; } if ($searchName == $elementModel->getFullName(false, false)) { $this->currentElement = $elementModel; return true; } } } return false; } /** * Get an element * * @param string $searchName Name to search for * @param bool $checkInt Check search name against element id * @param bool $checkShort Check short element name * * @return PlgFabrik_Element ok: element model not ok: false */ public function getElement($searchName, $checkInt = false, $checkShort = true) { return $this->hasElement($searchName, $checkInt, $checkShort) ? $this->currentElement : false; } /** * Set the list model * * @param object &$listModel List model * * @return void */ public function setListModel(&$listModel) { $this->listModel = $listModel; } /** * Is the page a multi-page form? * * @return bool */ public function isMultiPage() { $groups = $this->getGroupsHiarachy(); $view = $this->app->getInput()->get('view', 'form'); foreach ($groups as $groupModel) { $params = $groupModel->getParams(); switch ($params->get('split_page')) { default: case '0': break; case '1': return true; break; case '2': if ($view === 'form') { return true; } break; case '3': if ($view == 'details') { return true; } break; } } return false; } /** * Get an object of pages, keyed on page counter and containing an array of the page's group ids * * @return object */ public function getPages() { if (!is_null($this->pages)) { return $this->pages; } $this->pages = new stdClass; $pageCounter = 0; $groups = $this->getGroupsHiarachy(); $c = 0; foreach ($groups as $groupModel) { $params = $groupModel->getParams(); if ($groupModel->isSplitPage() && $c != 0 && $groupModel->canView()) { $pageCounter++; } if ($groupModel->canView()) { if (!isset($this->pages->$pageCounter)) { $this->pages->$pageCounter = array(); } array_push($this->pages->$pageCounter, $groupModel->getId()); } $c++; } return $this->pages; } /** * Should the form submit via ajax or not? * * @return bool */ public function isAjax() { if (is_null($this->ajax)) { $this->ajax = $this->app->getInput()->getBool('ajax', false); // $$$ rob - no element requires AJAX submission! /* $groups = $this->getGroupsHiarachy(); foreach ($groups as $groupModel) { $elementModels = $groupModel->getPublishedElements(); foreach ($elementModels as $elementModel) { if ($elementModel->requiresAJAXSubmit()) { $this->ajax = true; } } } */ } return (bool) $this->ajax; } /** * Used in special case where you have 2 + n-n joins in a single table * In this case the sql query will most likely create four rows of data for * each combination of possibilities * * E.g. * * tbl classes (id, label) * * left joined to: * tbl student_classes (id, label, student_id) * * left joined to * tbl student_teachers (id, label, teacher_id) * * entering one records with 2 students and 2 teachers gives you 4 rows in the query * * classid student_id, teacher_id * 1 1 1 * 1 2 1 * 1 1 2 * 1 2 2 * * @since 2.0rc1 * * @return void */ protected function _reduceDataForXRepeatedJoins() { $groups = $this->getGroupsHiarachy(); $listModel = $this->getListModel(); $pkField = ''; foreach ($groups as $groupModel) { /** * $$$ hugh - we need to do this for non-repeat joins as well */ if ($groupModel->isJoin()) { $joinModel = $groupModel->getJoinModel(); $tblJoin = $joinModel->getJoin(); // $$$ hugh - slightly modified these lines so we don't create $this->data['join'] if there is no // join data, because that then messes up code subsequent code that checks for empty($this->data) if (!isset($this->data['join'])) { // $this->data['join'] = array(); return; } if (!array_key_exists($tblJoin->id, $this->data['join'])) { continue; } if ($tblJoin->table_join == '') { continue; } $jData = &$this->data['join'][$tblJoin->id]; $db = $listModel->getDb(); $fields = $db->getTableColumns($tblJoin->table_join, false); $keyCount = 0; unset($pkField); foreach ($fields as $f) { if ($f->Key == 'PRI') { if (!isset($pkField)) { $pkField = $tblJoin->table_join . '___' . $f->Field; } $keyCount ++; } } if (!isset($pkField)) { $pkField = ''; } /* * Corner case if you link to #__user_profile - its primary key is made of 2 elements, so * simply checking on the user_id (the first col) will find duplicate results and incorrectly * merge down. */ if ($keyCount > 1) { return; } $usedKeys = array(); if (!empty($jData) && array_key_exists($pkField, $jData)) { foreach ($jData[$pkField] as $key => $value) { /* * $$$rob * added : || ($value === '' && !empty($this->errors)) * this was incorrectly reducing empty data * when re-viewing form after failed validation * with a form with repeating groups (with empty data in the key fields * */ if (!in_array($value, $usedKeys) || ($value === '' && !empty($this->errors))) { $usedKeys[$key] = $value; } } } $keysToKeep = array_keys($usedKeys); // Remove unneeded data from array foreach ($jData as $key => $value) { foreach ($value as $key2 => $v) { if (!in_array($key2, $keysToKeep)) { unset($jData[$key][$key2]); } } } // Reduce the keys so that we don't have keys of 0, 2 foreach ($jData as $key => $array) { if ($groupModel->canRepeat()) { $jData[$key] = array_values($array); } else { // $$$ hugh - if it's a one-to-one, it should be a single value $aVals = array_values($array); $jData[$key] = FArrayHelper::getValue($aVals, 0, ''); } } } } } /** * Query all active form plugins to see if they inject custom html into the top * or bottom of the form * * @return array plugin top html, plugin bottom html (inside <form>) plugin end (after form) */ public function getFormPluginHTML() { $pluginManager = FabrikWorker::getPluginManager(); $pluginManager->getPlugInGroup('form'); $form = $this->getForm(); $pluginManager->runPlugins('getBottomContent', $this, 'form'); $pluginBottom = implode("<br />", array_filter($pluginManager->data)); $pluginManager->runPlugins('getTopContent', $this, 'form'); $pluginTop = implode("<br />", array_filter($pluginManager->data)); // Inserted after the form's closing </form> tag $pluginManager->runPlugins('getEndContent', $this, 'form'); $pluginEnd = implode("<br />", array_filter($pluginManager->data)); return array($pluginTop, $pluginBottom, $pluginEnd); } /** * Presuming that our introduction looks like this: * * {new:this is an intro} * {edit:You're editing a record} * some more text * * creating a new form record will show the intro text as: * * this is an intro * some more text * * and editing an existing record will show: * * You're editing a record * some more text * * @return string modified intro */ public function getIntro() { $intro = $this->getForm()->intro; return $this->parseIntroOutroPlaceHolders($intro); } /** * Parse into and outro text * * @param string $text Text to parse * * @since 3.0.7 * * @return string */ public function parseIntroOutroPlaceHolders($text) { if (empty($text)) return $text; if (!$this->isEditable()) { $remove = "/{new:\s*.*?}/is"; $text = preg_replace($remove, '', $text); $remove = "/{edit:\s*.*?}/is"; $text = preg_replace($remove, '', $text); $match = "/{details:\s*.*?}/is"; $text = preg_replace_callback($match, array($this, '_getIntroOutro'), $text); } else { $match = $this->isNewRecord() ? 'new' : 'edit'; $remove = $this->isNewRecord() ? 'edit' : 'new'; $match = "/{" . $match . ":\s*.*?}/is"; $remove = "/{" . $remove . ":\s*.*?}/is"; $text = preg_replace_callback($match, array($this, '_getIntroOutro'), $text); $text = preg_replace($remove, '', $text); $text = preg_replace("/{details:({(?>[^{}]+|(?1))*})}/is", '', $text); $text = preg_replace("/{details:\s*.*?}/is", '', $text); } $this->data['fabrik_view_url'] = $this->getListModel()->viewDetailsLink($this->data); $this->data['fabrik_edit_url'] = $this->getListModel()->editLink($this->data); $w = new FabrikWorker; $text = $w->parseMessageForPlaceHolder($text, $this->data, true); // Jaanus: to remove content plugin code from intro and/or outro when plugins are not processed $params = $this->getParams(); $jPlugins = (int) $params->get('process-jplugins', '2'); if ($jPlugins === 0 || ($jPlugins === 2 && $this->isEditable())) { $text = preg_replace("/{\s*.*?}/i", '', $text); } $text = FabrikString::translate($text); return $text; } /** * Used from getIntro as preg_replace_callback function to strip * undesired text from form label intro * * @param array $match Preg matched strings * * @return string intro text */ private function _getIntroOutro($match) { $m = explode(":", $match[0]); array_shift($m); $m = implode(":", $m); $m = FabrikString::rtrimword($m, "}"); $m = preg_replace('/\[(\S+?)\]/', '{${1}}', $m); return $m; } /** * Jaanus: see text above about intro * * @return string Outro */ public function getOutro() { $params = $this->getParams(); $outro = $params->get('outro'); return $this->parseIntroOutroPlaceHolders($outro); } /** * Get the form's label * * @return string Label */ public function getLabel() { $label = $this->getForm()->label; if (!$this->isEditable()) { return str_replace("{Add/Edit}", '', $label); } if (!empty($label) && StringHelper::stristr($label, "{Add/Edit}")) { $replace = $this->isNewRecord() ? Text::_('COM_FABRIK_ADD') : Text::_('COM_FABRIK_EDIT'); $label = str_replace("{Add/Edit}", $replace, $label); } return Text::_($label); } /** * Currently only called from listModel _createLinkedForm when copying existing table * * @TODO should move this to the admin model * * @return object Form table */ public function copy() { // Array key = old id value new id $this->groupidmap = array(); $input = $this->app->getInput(); $groupModels = $this->getGroups(); $this->form = null; $form = $this->getTable(); $form->id = null; // $$$ rob newFormLabel set in table copy if ($input->get('newFormLabel', '', 'string') !== '') { $form->label = $input->get('newFormLabel', '', 'string'); } $res = $form->store(); $newElements = array(); foreach ($groupModels as $groupModel) { $oldId = $groupModel->getId(); // $$$rob use + rather than array_merge to preserve keys $groupModel->_newFormid = $form->id; $newElements = $newElements + $groupModel->copy(); $this->groupidmap[$oldId] = $groupModel->getGroup()->id; } // Need to do finalCopyCheck() on form elements $pluginManager = FabrikWorker::getPluginManager(); // @TODO something not right here when copying a cascading dropdown element in a join group foreach ($newElements as $origId => $newId) { $plugin = $pluginManager->getElementPlugin($newId); $plugin->finalCopyCheck($newElements); } // Update the model's table to the copied one $this->form = $form; $this->setId($form->id); $this->newElements = $newElements; return $form; } /** * Get the related lists (relations defined by db join foreign keys) * * @return array Links to view the related lists */ public function getRelatedTables() { $input = $this->app->getInput(); $links = array(); $params = $this->getParams(); if (!$params->get('show-referring-table-releated-data', false)) { return $links; } $listModel = $this->getListModel(); $referringTable = Factory::getApplication()->bootComponent('com_fabrik')->getMVCFactory()->createModel('List', 'FabrikFEModel'); // $$$ rob - not sure that referring_table is anything other than the form's table id // but for now just defaulting to that if no other variable found (e.g when links in sef urls) $tid = $input->getInt('referring_table', $input->getInt('listid', $listModel->getTable()->id)); $referringTable->setId($tid); $tableParams = $referringTable->getParams(); $table = $referringTable->getTable(); $joinsToThisKey = $referringTable->getJoinsToThisKey(); $linksToForms = $referringTable->getLinksToThisKey(); $row = $this->getData(); $facetedLinks = $tableParams->get('facetedlinks', null); if (is_null($facetedLinks)) { return; } $linkedLists = $facetedLinks->linkedlist; $aExisitngLinkedForms = $facetedLinks->linkedform; $linkedform_linktype = $facetedLinks->linkedform_linktype; $linkedtable_linktype = $facetedLinks->linkedlist_linktype; $f = 0; $allPks = array(); foreach ($joinsToThisKey as $joinKey => $element) { $key = $element->list_id . '-' . $element->form_id . '-' . $element->element_id; $qsKey = $referringTable->getTable()->db_table_name . '___' . $element->name; $val = $input->get($qsKey); if ($val == '') { // Default to row id if we are coming from a main link (and not a related data link) $val = $input->get($qsKey . '_raw', '', 'string'); if (empty($val)) { $thisKey = $this->getListModel()->getTable()->db_table_name . '___' . $element->join_key_column . '_raw'; $val = FArrayHelper::getValue($this->data, $thisKey, $val); if (empty($val)) { $val = $input->get('rowid'); } } } /* $$$ tom 2012-09-14 - If we don't have a key value, get all. If we have a key value, * use it to restrict the count to just this entry. */ $pks = array(); if (!empty($val)) { $pks[] = $val; } $allPks[$key] = $pks; if (isset($linkedLists->$key) && $linkedLists->$key != 0) { $recordCounts = $referringTable->getRecordCounts($element, $pks); // Jaanus - 18.10.2013 - get correct element fullnames as link keys $linkKey = $recordCounts['linkKey']; /* $$$ hugh - changed to use _raw as key, see: * http://fabrikar.com/forums/showthread.php?t=20020 */ $linkKeyRaw = $linkKey . '_raw'; $popUpLink = FArrayHelper::getValue($linkedtable_linktype->$key, $f, false); $count = is_array($recordCounts) && array_key_exists($val, $recordCounts) ? $recordCounts[$val]->total : 0; $label = $facetedLinks->linkedlistheader->$key == '' ? $element->listlabel : $facetedLinks->linkedlistheader->$key; $links[$element->list_id][] = $label . ': ' . $referringTable->viewDataLink($popUpLink, $element, null, $linkKey, $val, $count, $f); } $f++; } $f = 0; // Create columns containing links which point to forms associated with this table foreach ($linksToForms as $element) { if ($element !== false) { $key = $element->list_id . '-' . $element->form_id . '-' . $element->element_id; $linkedForm = $aExisitngLinkedForms->$key; $popUpLink = $linkedform_linktype->$key; if ($linkedForm !== '0') { if (is_object($element)) { $linkKeyData = $referringTable->getRecordCounts($element, $allPks[$key]); $linkKey = $linkKeyData['linkKey']; $val = $input->get($linkKey, '', 'string'); if ($val == '') { $qsKey = $referringTable->getTable()->db_table_name . '___' . $element->name; $val = $input->get($qsKey . '_raw', $input->get('rowid')); } // Jaanus: when no link to list and no form headers then people still know where they add data $fKey = $facetedLinks->linkedformheader->$key; $label = $fKey != '' ? ': ' . $fKey : (isset($linkedLists->$key) && $linkedLists->$key != 0 ? '' : ': ' . $element->listlabel); // Jaanus: label after add link if no list link helps to make difference between data view links and only add links. $links[$element->list_id][] = $referringTable->viewFormLink($popUpLink, $element, null, $linkKey, $val, false, $f) . $label; } } $f++; } } return $links; } /** * Create the form's html class name. * Based on column counts etc. as to whether form-horizontal applied * * @return string */ public function getFormClass() { return 'fabrikForm'; } /** * Strip out any element names from url qs vars * * @param string $url URL * * @return string */ protected function stripElementsFromUrl($url) { $url = explode('?', $url); if (count($url) == 1) { return $url; } $filtered = array(); $bits = explode('&', $url[1]); foreach ($bits as $bit) { $parts = explode('=', $bit); $key = $parts[0]; $key = FabrikString::rtrimword($key, '_raw'); if (!$this->hasElement($key)) { $filtered[] = implode('=', $parts); } } $url = $url[0] . '?' . implode('&', $filtered); return $url; } /** * Get the url to use as the form's action property * * @return string Url */ public function getAction() { $option = $this->app->getInput()->get('option'); $router = $this->app->getRouter(); $is_sef = (bool)Factory::getApplication()->getConfig()->get('sef'); if ($this->app->isClient('administrator')) { $action = filter_var(ArrayHelper::getValue($_SERVER, 'REQUEST_URI', 'index.php'), FILTER_SANITIZE_URL); $action = $this->stripElementsFromUrl($action); $action = str_replace("&", "&", $action); return $action; } if ($option === 'com_fabrik') { $page = 'index.php?'; // Get array of all querystring vars $uri = JURI::getInstance(); /** * Was $router->parse($uri); * but if you had a module + form on a page using sef urls and * Joomla's language switcher - calling parse() would re-parse the url and * mung it well good and proper like. * */ $queryVars = $router->getVars(); if ($this->isAjax()) { $queryVars['format'] = 'raw'; unset($queryVars['view']); $queryVars['task'] = 'form.process'; } $qs = array(); foreach ($queryVars as $k => $v) { if ($k == 'rowid') { $v = $this->getRowId(); } /* $$$ hugh - things get weird if we have been passed a urlencoded URL as a qs arg, * which the $router->parse() above will have urldecoded, and it gets used as part of the URI path * when we Route::_() below. So we need to re-urlencode stuff and junk. * Ooops, make sure it isn't an array, which we'll get if they have something like * &table___foo[value]=bar */ if (!is_array($v)) { $v = urlencode($v); $qs[] = $k . '=' . $v; } else { foreach ($v as $subV) { $qs[] = $k . '[]=' . urlencode($subV); } } } $action = $page . implode("&", $qs); $action = Route::_($action); } else { // In plugin & SEF URLs //J!4 ->getMode() not supported; if ((int) $router->getMode() === (int) JROUTER_MODE_SEF) ) if ($is_sef) { // $$$ rob if embedding a form in a form, then the embedded form's url will contain // the id of the main form - not sure if its an issue for now $action = filter_var(ArrayHelper::getValue($_SERVER, 'REQUEST_URI', 'index.php'), FILTER_SANITIZE_URL); } else { // In plugin and no sef (routing dealt with in form controller) $action = 'index.php'; } } return $action; } /** * If the group is a joined group we want to ensure that * its id field is contained with in the group's elements * * @param object &$groupTable Group table * * @return string HTML hidden field */ protected function _makeJoinIdElement(&$groupTable) { $listModel = $this->getListModel(); $joinId = $this->aJoinGroupIds[$groupTable->id]; $element = new stdClass; // Add in row id for join data $element->label = ''; $element->labels = ''; $element->error = ''; $element->value = ''; $element->id = ''; $element->startRow = 0; $element->endRow = 0; $element->errorTag = ''; $element->column = ''; $element->className = ''; $element->containerClass = ''; foreach ($listModel->getJoins() as $oJoin) { if ($oJoin->id == $joinId) { $key = $oJoin->table_join . $this->joinTableElementStep . $oJoin->table_join_key; if (array_key_exists('join', $this->data)) { // $$$ rob if join element is a db join the data $key contains label and not foreign key value if (@array_key_exists($key . '_raw', $this->data['join'][$joinId])) { $val = $this->data['join'][$joinId][$key . '_raw']; } else { $val = @$this->data['join'][$joinId][$key]; } if (is_array($val)) { $val = array_key_exists(0, $val) ? $val[0] : ''; } } else { $val = ''; } if ($val == '') { // Something's gone wrong - lets take the main table's key $k = $oJoin->join_from_table . $this->joinTableElementStep . $oJoin->table_key; $val = @$this->data[$k]; } if (is_array($val)) { $val = array_shift($val); } $element->value = $val; $element->element = '<input type="hidden" id="join.' . $joinId . '.rowid" name="join[' . $joinId . '][rowid]" value="' . $val . '" />'; $element->hidden = true; $element->containerClass = 'fabrikElementContainer fabrikHide'; } } return $element; } /** * Get an array of read only values * * @return array */ public function getreadOnlyVals() { return $this->readOnlyVals; } /** * Prepare the elements for rendering * * @param string $tmpl Form template * * @since 3.0 * * @return array */ public function getGroupView($tmpl = '') { if (isset($this->groupView)) { return $this->groupView; } $input = $this->app->getInput(); // $$$rob - do regardless of whether form is editable as $data is required for hidden encrypted fields // and not used anywhere else (avoids a warning message) $data = array(); /* $$$ rob - 3.0 for some reason just using $this->data was not right as join data was empty when editing existing record * $$$ hugh - commented this out, as a) running getData() twice is expensive, and b) it blows away any changes onLoad plugins * make to _data, like the juser plugin * Ran this change for a couple of weeks before committing, seems to work without it. *unset($this->data); */ $origData = $this->getData(); foreach ($origData as $key => $val) { if (is_string($val)) { $data[$key] = htmlspecialchars($val, ENT_QUOTES); } else { // Not sure what the htmlspecialchars is for above but if we don't assign here we loose join data $data[$key] = $val; } } $this->tmplData = $data; $this->groupView = array(); $this->readOnlyVals = array(); // $$$ hugh - temp foreach fix $groups = $this->getGroupsHiarachy(); foreach ($groups as $gkey => $groupModel) { $groupTable = $groupModel->getGroup(); $group = $groupModel->getGroupProperties($this); $groupParams = $groupModel->getParams(); $aElements = array(); // Check if group is actually a table join /* if (array_key_exists($groupTable->id, $this->aJoinGroupIds)) { $aElements[] = $this->_makeJoinIdElement($groupTable); } */ $repeatGroup = 1; $foreignKey = null; $startHidden = false; $newGroup = false; if ($groupModel->canRepeat()) { $joinTable = $groupModel->getJoinModel()->getJoin(); $foreignKey = ''; if (is_object($joinTable)) { $repeatGroup = $groupModel->repeatCount(); if ($groupModel->canEdit() && $repeatGroup === 0) { $newGroup = true; $repeatGroup = 1; } if (!$groupModel->fkPublished()) { $startHidden = false; } } } // Test failed validated forms, repeat group counts are in request $repeatGroups = $input->get('fabrik_repeat_group', array(), 'array'); if (!empty($repeatGroups)) { $repeatGroup = FArrayHelper::getValue($repeatGroups, $gkey, $repeatGroup); if ($repeatGroup == 0) { $repeatGroup = 1; $startHidden = true; } $newGroup = false; } $groupModel->repeatTotal = $startHidden ? 0 : $repeatGroup; $aSubGroups = array(); for ($c = 0; $c < $repeatGroup; $c++) { $aSubGroupElements = array(); $elCount = 0; $elementModels = $groupModel->getPublishedElements(); foreach ($elementModels as $elementModel) { /* $$$ rob ensure that the element is associated with the correct form (could occur if n plugins rendering form * and detailed views of the same form. */ $elementModel->setFormModel($this); $elementModel->tmpl = $tmpl; $elementModel->newGroup = $newGroup; /* $$$rob test don't include the element in the form is we can't use and edit it * test for captcha element when user logged in */ if (!$this->isEditable()) { $elementModel->inDetailedView = true; } if (!$this->isEditable() && !$elementModel->canView()) { continue; } $elementModel->_foreignKey = $foreignKey; $elementModel->_repeatGroupTotal = $repeatGroup - 1; $element = $elementModel->preRender($c, $elCount, $tmpl); // $$$ hugh - experimenting with adding non-viewable, non-editable to encrypted vars // if (!$element || ($elementModel->canView() && !$elementModel->canUse())) if (!$element || !$elementModel->canUse()) { /* $$$ hugh - $this->data doesn't seem to always have what we need in it, but $data does. * can't remember exact details, was chasing a nasty issue with encrypted 'user' elements. */ // $$$ rob HTMLName seems not to work for joined data in confirmation plugin $elementModel->getValuesToEncrypt($this->readOnlyVals, $data, $c); /** * $$$ hugh - need to decode it if it's a string, 'cos we encoded $data up there ^^ somewhere, which * then causes read only data to get changed to htmlencoded after submission. See this thread for gory details: * http://fabrikar.com/forums/index.php?threads/how-to-avoid-changes-to-an-element-with-a-read-only-link.37656/#post-192437 */ $elName = $elementModel->getFullName(true, false); if (!is_array($this->readOnlyVals[$elName]['data'])) { $this->readOnlyVals[$elName]['data'] = htmlspecialchars_decode($this->readOnlyVals[$elName]['data']); } $this->readOnlyVals[$elName]['repeatgroup'] = $groupModel->canRepeat(); $this->readOnlyVals[$elName]['join'] = $groupModel->isJoin(); } if ($element) { $elementModel->stockResults($element, $aElements, $this->data, $aSubGroupElements); } if ($element && !$element->hidden) { $elCount++; } } // If its a repeatable group put in subgroup if ($groupModel->canRepeat()) { // Style attribute for group columns (need to occur after randomisation of the elements otherwise clears are not ordered correctly) $rowix = -1; foreach ($aSubGroupElements as $elKey => $element) { $rowix = $groupModel->setColumnCss($element, $rowix); } $aSubGroups[] = $aSubGroupElements; } } $groupModel->randomiseElements($aElements); // Style attribute for group columns (need to occur after randomisation of the elements otherwise clears are not ordered correctly) $rowix = -1; // Don't double setColumnCss otherwise weirdness ensues if (!$groupModel->canRepeat()) { foreach ($aElements as $elKey => $element) { $rowix = $groupModel->setColumnCss($element, $rowix); } } $group->elements = $aElements; $group->subgroups = $aSubGroups; $group->startHidden = $startHidden; $group->repeatIntro = $groupParams->get('repeat_intro', ''); $group->classArray[] = 'fabrikGroup'; $group->class = implode(' ', $group->classArray); $group->newGroup = $newGroup; // Only create the group if there are some element inside it if (count($aElements) != 0 && $groupModel->canView() !== false) { // 28/01/2011 $$$rob and if it is published $showGroup = (int) $groupParams->get('repeat_group_show_first'); if ($showGroup !== 0) { // $$$ - hugh - testing new 'hide if no usable elements' option (4) // Jaanus: if not form view with "details only" option and not details view with "form only" option if (!($showGroup == 2 && $this->isEditable()) && !($showGroup == 3 && $input->get('view', 'form') == 'details') && !($showGroup == 4 && !$groupModel->canView())) { $this->groupView[$group->name] = $group; } } } } return $this->groupView; } /** * Get any fabrik tables that link to the join table * * @param string $table Table name * * @return array */ public function getLinkedFabrikLists($table) { if (!isset($this->linkedFabrikLists)) { $this->linkedFabrikLists = array(); } if (!array_key_exists($table, $this->linkedFabrikLists)) { $db = FabrikWorker::getDbo(true); if (trim($table == '')) { return array(); } else { $query = $db->getQuery(true); $query->select('*')->from('#__fabrik_lists')->where('db_table_name = ' . $db->q($table)); $db->setQuery($query); } $this->linkedFabrikLists[$table] = $db->loadColumn(); } return $this->linkedFabrikLists[$table]; } /** * Used to see if something legitimate in the submission process, like a form plugin, * has modified an RO element value and wants to override the RO/origdata. * * If $value is set, then this method additionally adds the modified value to the updated array. * * @param string $fullname Full element name * @param mixed $value Optional value, states that a plugin update the readonly value of $fullname * * @return bool */ public function updatedByPlugin($fullname = '', $value = null) { if (isset($value)) { $this->pluginUpdatedElements[$fullname] = $value; } return array_key_exists($fullname, $this->pluginUpdatedElements); } /** * Populate the Model state * * @return void */ protected function populateState() { $input = $this->app->getInput(); if (!$this->app->isClient('administrator')) { // Load the menu item / component parameters. $params = $this->app->getParams(); $this->setState('params', $params); // Load state from the request. $pk = $input->getInt('formid', $params->get('formid')); } else { $pk = $input->getInt('formid'); } $this->setState('form.id', $pk); } /** * Is the form editable * * @return bool */ public function isEditable() { return $this->editable; } /** * Set editable state * * @param bool $editable Editable state * * @since 3.0.7 * * @return void */ public function setEditable($editable) { $this->editable = $editable; } /** * Helper method to get the session redirect key. Redirect plugin stores this * other form plugins such as twitter or Paypal may need to query the session to perform the final redirect * once the user has returned from those sites. * * @return string Session key to store redirect information (note: ends in '.') */ public function getRedirectContext() { return 'com_fabrik.form.' . $this->getId() . '.redirect.'; } /** * Resets cached form data. * * @param bool $unset_groups Also reset group and element model cached data * * @return void */ public function unsetData($unset_groups = false) { unset($this->data); unset($this->query); if ($unset_groups) { /* $$$ hugh - unset group published elements list, and clear each * element's default data. Needed from content plugin, otherwise if * we render the same form more than once with different rowids, we end up * rendering the first copy's element data X times. * Not sure if we need to actually unset the group published elements list, * but for the moment I'm just using a Big Hammer to get the content plugin working! */ $groups = $this->getGroupsHiarachy(); foreach ($groups as $groupModel) { $groupModel->resetPublishedElements(); $elementModels = $groupModel->getPublishedElements(); foreach ($elementModels as $elementModel) { $elementModel->reset(); } } unset($this->groups); $pluginManager = FabrikWorker::getPluginManager(); $pluginManager->clearFormPlugins($this); } } /** * Reset form's cached data, i.e. from content plugin, where we may be rendering the same * form twice, with different row data. * * @return void */ public function reset() { $this->unsetData(true); } /** * Get redirect URL * * @param bool $incSession Set url in session? * @param bool $isMambot Is Mambot * * @return array url: string Redirect url, baseRedirect (True: default redirect, False: plugin redirect) * * @since 3.0.6 (was in form controller) */ public function getRedirectURL($incSession = true, $isMambot = false) { $input = $this->app->getInput(); if ($this->app->isClient('administrator')) { // Admin always uses option com_fabrik if (array_key_exists('apply', $this->formData)) { $url = 'index.php?option=com_fabrik&task=form.view&formid=' . $input->getInt('formid') . '&rowid=' . $input->getString('rowid', '', 'string'); } else { $url = 'index.php?option=com_fabrik&task=list.view&listid=' . $this->getListModel()->getId(); } } else { if (array_key_exists('apply', $this->formData)) { $url = 'index.php?option=com_fabrik&view=form&formid=' . $input->getInt('formid') . '&rowid=' . $input->getString('rowid', '', 'string') . '&listid=' . $input->getInt('listid'); $itemId = (int) FabrikWorker::itemId(); if ($itemId !== 0) $url = $url . '&Itemid=' . $itemId; } else { if ($isMambot) { // Return to the same page $url = filter_var(ArrayHelper::getValue($_SERVER, 'HTTP_REFERER', 'index.php'), FILTER_SANITIZE_URL); } else { // Return to the page that called the form $url = urldecode($input->post->get('fabrik_referrer', 'index.php', 'string')); } $itemId = (int) FabrikWorker::itemId(); if ($url == '') { if ($itemId !== 0) { $url = 'index.php?' . http_build_query($this->app->getMenu('site')->getActive()->query) . '&Itemid=' . $itemId; } else { // No menu link so redirect back to list view $url = 'index.php?option=com_fabrik&view=list&listid=' . $input->getInt('listid'); } } } if ($this->config->get('sef')) { $url = Route::_($url); } } // 3.0 need to distinguish between the default redirect and redirect plugin $baseRedirect = true; if (!$incSession) { return array('url' => $url, 'baseRedirect' => $baseRedirect); } $formdata = $this->session->get('com_fabrik.form.data'); $context = $this->getRedirectContext(); // If the redirect plug-in has set a url use that in preference to the default url $sUrl = $this->session->get($context . 'url', array()); if (!empty($sUrl)) { $baseRedirect = false; } if (!is_array($sUrl)) { $sUrl = array($sUrl); } if (empty($sUrl)) { $sUrl[] = $url; } $url = array_shift($sUrl); $this->session->set($context . 'url', $sUrl); // Redirect URL which set prefilters of < were converted to < which then gave mySQL error $url = htmlspecialchars_decode($url); return array('url' => $url, 'baseRedirect' => $baseRedirect); } /** * Should we show success messages * * @since 3.0.7 * * @return boolean */ public function showSuccessMsg() { $mode = $this->getParams()->get('suppress_msgs', '0'); return ($mode == 0 || $mode == 2); } /** * Get the success message * * @return string */ public function getSuccessMsg() { $registry = $this->session->get('registry'); // $$$ rob 30/03/2011 if using as a search form don't show record added message if ($registry && $registry->get('com_fabrik.searchform.fromForm') != $this->get('id')) { if (!$this->showSuccessMsg()) { return ''; } $params = $this->getParams(); return Text::_($params->get('submit-success-msg', 'COM_FABRIK_RECORD_ADDED_UPDATED')); } else { return ''; } } /** * Should we show ACL messages * * @since 3.0.7 * * @return boolean */ public function showACLMsg() { $mode = $this->getParams()->get('suppress_msgs', '0'); return $mode == 0 || $mode == 1; } /** * If trying to add/edit a record when the user doesn't have rights to do so, * what message, if any should we show. * * @param bool $force if true don't check if messages suppressed * * @since 3.0.7 * * @return string */ public function aclMessage($force = false) { if (!$force && !$this->showACLMsg()) { return ''; } $input = $this->app->getInput(); $msg = $input->get('rowid', '', 'string') == 0 ? 'COM_FABRIK_NOTICE_CANT_ADD_RECORDS' : 'COM_FABRIK_NOTICE_CANT_EDIT_RECORDS'; return Text::_($msg); } /** * Say a form is embedded in an article, and is set to redirect on same/new page (so not in popup) * Then we need to grab and re-apply the redirect/thanks message * * @return void */ public function applyMsgOnce() { if (!$this->app->getInput()->get('isMambot')) { // Don't apply if not isMambot return; } // Proceed, isMambot set in PlgFabrik_FormRedirect::buildJumpPage() $context = $this->getRedirectContext(); $msg = $this->session->get($context . 'msg', array()); if (!empty($msg)) { $msg = FArrayHelper::getValue($msg, 0); $this->app->enqueueMessage($msg); } // Ensure its only shown once even if page is refreshed with isMambot in querystring $this->session->clear($context . 'msg'); } /** * Get redirect message * * @return string Redirect message * * @since 3.0.6 (was in form controller) */ public function getRedirectMessage() { if (!$this->showSuccessMsg()) { return ''; } $msg = $this->getSuccessMsg(); $context = $this->getRedirectContext(); $sMsg = $this->session->get($context . 'msg', array($msg)); if (!is_array($sMsg)) { $sMsg = array($sMsg); } if (empty($sMsg)) { $sMsg[] = $msg; } /** * $$$ rob Was using array_shift to set $msg, not to really remove it from $sMsg * without the array_shift the custom message is never attached to the redirect page. * Use-case: redirect plugin with jump page pointing to a J page and thanks message selected. */ $customMsg = array_keys($sMsg); $customMsg = array_shift($customMsg); $customMsg = FArrayHelper::getValue($sMsg, $customMsg); if ($customMsg != '') { $msg = $customMsg; } $q = $this->app->getMessageQueue(); $found = false; foreach ($q as $m) { // Custom message already queued - unset default msg if ($m['type'] == 'message' && trim($m['message']) !== '') { $found = true; break; } } if ($found) { $msg = null; } $showMsg = null; $this->session->set($context . 'msg', $sMsg); $showMsg = (array) $this->session->get($context . 'showsystemmsg', array(true)); if (is_array($showMsg)) { $showMsg = array_shift($showMsg); } $msg = $showMsg == 1 ? $msg : ''; // $$$ hugh - testing allowing placeholders in success msg $w = new FabrikWorker; $msg = $w->parseMessageForPlaceHolder($msg, $this->data); return $msg; } /** * Build the JS key that the model uses in the view. This key is assigned to Fabrik.blocks * * @since 3.1rc1 * * @return string */ public function jsKey() { $key = $this->isEditable() ? 'form_' . $this->getId() : 'details_' . $this->getId(); if ($this->getRowId() != '') { $key .= '_' . $this->getRowId(); } return $key; } /** * Ask all elements to add their js Fabrik.jLayouts to the framework * This has to be done before we call FabrikHelperHTML::framework(); * * @return void; */ public function elementJsJLayouts() { $groups = $this->getGroupsHiarachy(); foreach ($groups as $groupModel) { $elementModels = $groupModel->getPublishedElements(); foreach ($elementModels as $elementModel) { $elementModel->jsJLayout(); } } } /** * Get a subset of the model's data with non accessible values removed * * @param string $view View * * @return array data */ public function accessibleData($view = 'form') { $accessibleData = $this->data; $groups = $this->getGroupsHiarachy(); foreach ($groups as $groupModel) { $elementModels = $groupModel->getPublishedElements(); foreach ($elementModels as $elementModel) { switch ($view) { default: case 'form': $accessible = $elementModel->canUse($view); break; case 'details': $accessible = $elementModel->canView('form'); break; case 'list': $accessible = $elementModel->canView('list'); break; } if (!$accessible) { $name = $elementModel->getFullName(true, false); unset($accessibleData[$name]); unset($accessibleData[$name . '_raw']); } } } return $accessibleData; } /** * Get a form LayoutInterface file * * @param string $name layout name * @param array $paths Optional paths to add as includes * @param array $options Options * * @return LayoutFile */ public function getLayout($name, $paths = array(), $options = array()) { $view = $this->isEditable() ? 'form' : 'details'; $paths[] = COM_FABRIK_FRONTEND . '/views/'. $view . '/tmpl/' . $this->getTmpl() . '/layouts'; $layout = FabrikHelperHTML::getLayout($name, $paths, $options); return $layout; } public function recordInDatabase() { $form = $this->getForm(); return $form->record_in_database === '1'; } }
| ver. 1.4 |
Github
|
.
| PHP 8.1.33 | Генерация страницы: 0.1 |
proxy
|
phpinfo
|
Настройка