validator = JModelLegacy::getInstance('ElementValidator', 'FabrikFEModel'); $this->validator->setElementModel($this); $this->access = new stdClass; } /** * Weed out any non-serializable properties. We only ever get serialized by J!'s cache handler, * to create the cache ID, so we don't really care about __wake() or not saving all state. We just * want to avoid the dreaded "serialization of a closure is not allowed", and provide enough propeties * to guarrantee a unique hash for the cache ID. */ public function __sleep() { $serializable = array(); foreach ($this as $paramName => $paramValue) { //if (!is_string($paramValue) && !is_array($paramValue) && is_callable($paramValue)) if (!is_numeric($paramValue) && !is_string($paramValue) && !is_array($paramValue)) { continue; } $serializable[] = $paramName; } return $serializable; } /** * Method to set the element id * * @param int $id element ID number * * @return void */ public function setId($id) { // Set new element ID $this->id = $id; } /** * Get the element id * * @return int element id */ public function getId() { return $this->id; } /** * Get the element table object * * @param bool $force default false - force load the element * * @return FabrikTableElement element table */ public function &getElement($force = false) { if (!$this->element || $force) { JTable::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_fabrik/tables'); $row = FabTable::getInstance('Element', 'FabrikTable'); $row->load($this->id); $this->element = $row; // 3.1 reset the params at the same time. Seems to be required for ajax autocomplete if ($force) { unset($this->params); $this->getParams(); } } return $this->element; } /** * Get parent element * * @return object element table */ public function getParent() { if (!isset($this->parent)) { $element = $this->getElement(); if ((int) $element->parent_id !== 0) { $this->parent = FabTable::getInstance('element', 'FabrikTable'); $this->parent->load($element->parent_id); } else { $this->parent = $element; } } return $this->parent; } /** * Bind data to the _element variable - if possible we should run one query to get all the forms * element data and then iterate over that, creating an element plugin for each row * and bind each record to that plugins _element. This is instead of using getElement() which * reloads in the element increasing the number of queries run * * @param mixed &$row (object or assoc array) * * @return object element table */ public function bindToElement(&$row) { if (!$this->element) { JTable::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_fabrik/tables'); $this->element = FabTable::getInstance('Element', 'FabrikTable'); } if (is_object($row)) { $row = ArrayHelper::fromObject($row); } $this->element->bind($row); return $this->element; } /** * Set the context in which the element occurs * * @param FabrikFEModelGroup &$groupModel group model * @param FabrikFEModelForm &$formModel form model * @param FabrikFEModelList &$listModel list model * * @return void */ public function setContext(&$groupModel, &$formModel, &$listModel) { // Don't assign these with &= as they already are when passed into the func $this->group = $groupModel; $this->form = $formModel; $this->list = $listModel; } /** * Get the element's fabrik list model * * @return FabrikFEModelList list model */ public function getListModel() { if (is_null($this->list)) { $groupModel = $this->getGroup(); $this->list = $groupModel->getListModel(); } return $this->list; } /** * load in the group model * * @param int $groupId group id * * @return FabrikFEModelGroup group model */ public function &getGroup($groupId = null) { if (is_null($groupId)) { $element = $this->getElement(); $groupId = $element->group_id; } if (is_null($this->group) || $this->group->getId() != $groupId) { $model = JModelLegacy::getInstance('Group', 'FabrikFEModel'); $model->setId($groupId); $model->getGroup(); $this->group = $model; } return $this->group; } /** * Get the elements group model * * @param int $group_id If not set uses elements default group id * * @return FabrikFEModelGroup group model */ public function getGroupModel($group_id = null) { return $this->getGroup($group_id); } /** * Set the group model * * @param FabrikFEModelGroup $group group model * * @since 3.0.6 * * @return null */ public function setGroupModel($group) { $this->group = $group; } /** * get the elements form model * * @deprecated use getFormModel * * @return FabrikFEModelForm Form model */ public function getForm() { return $this->getFormModel(); } /** * get the element's form model * * @return FabrikFEModelForm Form model */ public function getFormModel() { if (is_null($this->form)) { $listModel = $this->getListModel(); $table = $listModel->getTable(); $this->form = JModelLegacy::getInstance('form', 'FabrikFEModel'); $this->form->setId($table->form_id); $this->form->getForm(); } return $this->form; } /** * Set form model * * @param FabrikFEModelForm $model form model * * @return void */ public function setFormModel($model) { $this->form = $model; } /** * Shows the RAW list data - can be overwritten in plugin class * * @param string $data element data * @param object $thisRow all the data in the tables current row * * @return string formatted value */ public function renderRawListData($data, $thisRow) { return $data; } /** * replace labels shown in table view with icons (if found) * * @param string $data data * @param string $view list/details * @param string $tmpl template * * @since 3.0 - icon_folder is a bool - search through template folders for icons * * @deprecated use replaceWithIcons() * @return string data */ protected function _replaceWithIcons($data, $view = 'list', $tmpl = null) { return $this->replaceWithIcons($data, $view, $tmpl); } /** * Replace labels shown in list view with icons (if found) * * @param string $data Data * @param string $view List/details * @param string $tmpl Template * * @since 3.0 - icon_folder is a bool - search through template folders for icons * * @return string data */ protected function replaceWithIcons($data, $view = 'list', $tmpl = null) { if ($data == '') { $this->iconsSet = false; return $data; } $params = $this->getParams(); $listModel = $this->getListModel(); $iconFile = (string) $params->get('icon_file', ''); if ((int) $params->get('icon_folder', 0) === 0 && $iconFile === '') { $this->iconsSet = false; return $data; } if (in_array($listModel->getOutPutFormat(), array('csv', 'rss'))) { $this->iconsSet = false; return $data; } $cleanData = empty($iconFile) ? FabrikString::clean(strip_tags($data)) : $iconFile; $cleanDatas = array($this->getElement()->name . '_' . $cleanData, $cleanData); $opts = array('forceImage' => true); //If subdir is set prepend file name with subdirectory (so first search through [template folders]/subdir for icons, e.g. images/subdir) $iconSubDir = $params->get('icon_subdir', ''); if ($iconSubDir != '') { $iconSubDir = rtrim($iconSubDir, '/') . '/'; $iconSubDir = ltrim($iconSubDir, '/'); array_unshift($cleanDatas, $iconSubDir . $cleanData); //search subdir first } foreach ($cleanDatas as $cleanData) { foreach ($this->imageExtensions as $ex) { $img = FabrikHelperHTML::image($cleanData . '.' . $ex, $view, $tmpl, array(), false, $opts); if ($img !== '') { $this->iconsSet = true; $opts = new stdClass; $opts->position = 'top'; $opts = json_encode($opts); $data = '' . $data . ''; // See if data has an tag if (class_exists('DOMDocument')) { $html = new DOMDocument; /** * The loadXML() chokes if data has & in it. But we can't htmlspecialchar() it, as that removes * the HTML markup we're looking for. So we need to ONLY change &'s which aren't already part of * any HTML entities which may be in the data. So use a negative lookahead regex, which finds & followed * by anything except non-space the ;. Then after doing the loadXML, we have to turn the &s back in * to &, to avoid double encoding 'cos we're going to do an htmpsepecialchars() on $data in a few lines. * * It also chokes if the data already contains any HTML entities which XML doesn't like, like é, * so first we need to do an html_entity_decode() to get rid of those! */ $data = html_entity_decode($data); $data = preg_replace('/&(?!\S+;)/', '&', $data); $html->loadXML($data); $data = str_replace('&', '&', $data); $as = $html->getElementsBytagName('a'); } if ($params->get('icon_hovertext', true)) { $aHref = '#'; $target = ''; if (class_exists('DOMDocument') && $as->length) { // Data already has an lets get that for use in hover text $a = $as->item(0); $aHref = $a->getAttribute('href'); $target = $a->getAttribute('target'); $target = 'target="' . $target . '"'; } $data = htmlspecialchars($data, ENT_QUOTES); $img = '' . $img . ''; } elseif (!empty($iconFile)) { /** * $$$ hugh - kind of a hack, but ... if this is an upload element, it may already be a link, and * we'll need to replace the text in the link with the image * After ages dicking around with a regex to do this, decided to use DOMDocument instead! */ if (class_exists('DOMDocument') && $as->length) { $img = $html->createElement('img'); $src = FabrikHelperHTML::image($cleanData . '.' . $ex, $view, $tmpl, array(), true, array('forceImage' => true)); $img->setAttribute('src', $src); $as->item(0)->nodeValue = ''; $as->item(0)->appendChild($img); return $html->saveHTML(); } } return $img; } } } return $data; } /** * Build the sub query which is used when merging in in repeat element * records from their joined table into the one field. * Overwritten in database join element to allow for building the join * to the table containing the stored values required labels * * @param string $jKey key * @param bool $addAs add 'AS' to select sub query * * @return string sub query */ public function buildQueryElementConcat($jKey, $addAs = true) { $joinTable = $this->getJoinModel()->getJoin()->table_join; $dbTable = $this->actualTableName(); // Jaanus: joined group pk? set in groupConcactJoinKey() $pkField = $this->groupConcactJoinKey(); $fullElName = $this->_db->qn($dbTable . '___' . $this->element->name); $sql = '(SELECT GROUP_CONCAT(' . $jKey . ' SEPARATOR \'' . GROUPSPLITTER . '\') FROM ' . $joinTable . ' WHERE parent_id = ' . $pkField . ')'; if ($addAs) { $sql .= ' AS ' . $fullElName; } return $sql; } /** * Build the sub query which is used when merging in * repeat element records from their joined table into the one field. * Overwritten in database join element to allow for building * the join to the table containing the stored values required ids * * @since 2.1.1 * * @return string sub query */ protected function buildQueryElementConcatRaw() { $joinTable = $this->getJoinModel()->getJoin()->table_join; $dbTable = $this->actualTableName(); $fullElName = $this->_db->qn($dbTable . '___' . $this->element->name . '_raw'); $pkField = $this->groupConcactJoinKey(); return '(SELECT GROUP_CONCAT(id SEPARATOR \'' . GROUPSPLITTER . '\') FROM ' . $joinTable . ' WHERE parent_id = ' . $pkField . ') AS ' . $fullElName; } /** * Build the sub query which is used when merging in * repeat element records from their joined table into the one field. * Overwritten in database join element to allow for building * the join to the table containing the stored values required ids * * @since 2.1.1 * * @return string sub query */ protected function buildQueryElementConcatId() { return ''; } /** * Used in form model setJoinData. * * @since 2.1.1 * * @return array Element names to search data in to create join data array */ public function getJoinDataNames() { $name = $this->getFullName(true, false); $rawName = $name . '_raw'; return array($name, $rawName); } /** * Create the SQL select 'name AS alias' segment for list/form queries * * @param array &$aFields array of element names * @param array &$aAsFields array of 'name AS alias' fields * @param array $opts options : alias - replace the fullelement name in asfields "name AS * tablename___elementname" * * @return void */ public function getAsField_html(&$aFields, &$aAsFields, $opts = array()) { $dbTable = $this->actualTableName(); $db = FabrikWorker::getDbo(); $table = $this->getListModel()->getTable(); $fullElName = FArrayHelper::getValue($opts, 'alias', $db->qn($dbTable . '___' . $this->element->name)); $fName = $dbTable . '.' . $this->element->name; $k = $db->qn($fName); $secret = $this->config->get('secret'); if ($this->encryptMe()) { $k = 'AES_DECRYPT(' . $k . ', ' . $db->q($secret) . ')'; } if ($this->isJoin()) { $jKey = $this->element->name; if ($this->encryptMe()) { $jKey = 'AES_DECRYPT(' . $jKey . ', ' . $db->q($secret) . ')'; } $joinTable = $this->getJoinModel()->getJoin()->table_join; //$fullElName = FArrayHelper::getValue($opts, 'alias', $k); $fullElName = FArrayHelper::getValue($opts, 'alias', $fullElName); $str = $this->buildQueryElementConcat($jKey); } else { if ($this->calcSelectModifier) { $k = $this->calcSelectModifier . '(' . $k . ')'; } $str = $k . ' AS ' . $fullElName; } if ($table->db_primary_key == $fullElName) { array_unshift($aFields, $fullElName); array_unshift($aAsFields, $fullElName); } else { if (!in_array($str, $aFields)) { $aFields[] = $str; $aAsFields[] = $fullElName; } $k = $db->qn($dbTable . '.' . $this->element->name); if ($this->encryptMe()) { $k = 'AES_DECRYPT(' . $k . ', ' . $db->q($secret) . ')'; } if ($this->isJoin()) { $pkField = $this->groupConcactJoinKey(); $str = $this->buildQueryElementConcatRaw(); $aFields[] = $str; $as = $db->qn($dbTable . '___' . $this->element->name . '_raw'); $aAsFields[] = $as; $str = $this->buildQueryElementConcatId(); $aFields[] = $str; $as = $db->qn($dbTable . '___' . $this->element->name . '_id'); $aAsFields[] = $as; $as = $db->qn($dbTable . '___' . $this->element->name . '___params'); $str = '(SELECT GROUP_CONCAT(params SEPARATOR \'' . GROUPSPLITTER . '\') FROM ' . $joinTable . ' WHERE parent_id = ' . $pkField . ') AS ' . $as; // Jaanus: joined group pk set in groupConcactJoinKey() $aFields[] = $str; $aAsFields[] = $as; } else { $fullElName = $db->qn($dbTable . '___' . $this->element->name . '_raw'); if ($this->calcSelectModifier) { $k = $this->calcSelectModifier . '(' . $k . ')'; } $str = $k . ' AS ' . $fullElName; } if (!in_array($str, $aFields)) { $aFields[] = $str; $aAsFields[] = $fullElName; } } } /** * OMG! If repeat element inside a repeat group then the group_concat subquery needs to change the key * it selected on - so it could either be the table pk or the joined groups pk.... :D * * @since 3.1rc1 * * @return string */ protected function groupConcactJoinKey() { $table = $this->getListModel()->getTable(); if ($this->getGroupModel()->isJoin() && $this->isJoin()) { $groupJoin = $this->getGroupModel()->getJoinModel()->getJoin(); $pkField = $groupJoin->params->get('pk'); } else { $pkField = $table->db_primary_key; } return $pkField; } /** * Get raw column name * * @param bool $useStep Use step in name * * @return string */ public function getRawColumn($useStep = true) { $n = $this->getFullName($useStep, false); $n .= '_raw`'; return $n; } /** * Is the element editable - wrapper for _editable property as 3.1 uses editable * * @since 3.0.7 * * @return bool */ public function isEditable() { return $this->editable; } /** * Set the element edit state - wrapper for _editable property as 3.1 uses editable * * @param bool $editable Is the element editable * * @since 3.0.7 * * @return void */ public function setEditable($editable) { $this->editable = $editable; } /** * Check user can view the read only element OR view in list view * * @param string $view View list/form @since 3.0.7 * * @return bool can view or not */ public function canView($view = 'form') { $default = 1; $key = $view == 'form' ? 'view' : 'listview'; $prop = $view == 'form' ? 'view_access' : 'list_view_access'; $params = $this->getParams(); if (!is_object($this->access) || !array_key_exists($key, $this->access)) { $groups = $this->user->getAuthorisedViewLevels(); $this->access->$key = in_array($params->get($prop, $default), $groups); } // Override with check on lookup element's value = logged in user id. if ($params->get('view_access_user', '') !== '' && $view == 'form') { $formModel = $this->getFormModel(); $data = $formModel->getData(); if (!empty($data) && $this->user->get('id') !== 0) { $lookUpId = $params->get('view_access_user', ''); $lookUp = $formModel->getElement($lookUpId, true); // Could be a linked parent element in which case the form doesn't contain the element whose id is $lookUpId if (!$lookUp) { $lookUp = FabrikWorker::getPluginManager()->getElementPlugin($lookUpId); } if ($lookUp) { $fullName = $lookUp->getFullName(false, true); $value = $formModel->getElementData($fullName, true); $this->access->$key = ($this->user->get('id') == $value) ? true : false; } else { FabrikWorker::logError('Did not load element ' . $lookUpId . ' for element::canView()', 'error'); } } } return $this->access->$key; } /** * Check if the user can use the active element * If location is 'list' then we don't check the group canEdit() option - causes inline edit plugin not to work * when followed by a update_col plugin. * * @param string $location To trigger plugin on form/list for elements * @param string $event To trigger plugin on * * @return bool can use or not */ public function canUse($location = 'form', $event = null) { // Odd! even though defined in initialize() for confirmation plugin access was not set. if (!isset($this->access)) { $this->access = new stdClass; } if (!is_object($this->access) || !array_key_exists('use', $this->access)) { /** * $$$ hugh - testing new "Option 5" for group show, "Always show read only" * So if element's group show is type 5, then element is de-facto read only. */ if ($location !== 'list' && !$this->getGroupModel()->canEdit()) { $this->access->use = false; } else { $viewLevel = $this->getElement()->access; if (!$this->getFormModel()->isNewRecord()) { $editViewLevel = $this->getParams()->get('edit_access'); if ($editViewLevel) { $viewLevel = $editViewLevel; } } $groups = $this->user->getAuthorisedViewLevels(); $this->access->use = in_array($viewLevel, $groups); // Override with check on lookup element's value = logged in user id. $params = $this->getParams(); if (!$this->access->use && $params->get('edit_access_user', '') !== '' && $location == 'form') { $formModel = $this->getFormModel(); $data = $formModel->getData(); if (!empty($data) && $this->user->get('id') !== 0) { $lookUpId = $params->get('edit_access_user', ''); $lookUp = $formModel->getElement($lookUpId, true); // Could be a linked parent element in which case the form doesn't contain the element whose id is $lookUpId if (!$lookUp) { $lookUp = FabrikWorker::getPluginManager()->getElementPlugin($lookUpId); } if ($lookUp) { $fullName = $lookUp->getFullName(true, true); $value = (array) $formModel->getElementData($fullName, true); $this->access->use = in_array($this->user->get('id'), $value); } else { FabrikWorker::logError('Did not load element ' . $lookUpId . ' for element::canUse()', 'error'); } } } } } return $this->access->use; } /** * Defines if the user can use the filter related to the element * * @return bool true if you can use */ public function canUseFilter() { if (!is_object($this->access) || !array_key_exists('filter', $this->access)) { $groups = $this->user->getAuthorisedViewLevels(); // $$$ hugh - fix for where certain elements got created with 0 as the // the default for filter_access, which isn't a legal value, should be 1 $filterAccess = $this->getParams()->get('filter_access'); $filterAccess = $filterAccess == '0' ? '1' : $filterAccess; $this->access->filter = in_array($filterAccess, $groups); } return $this->access->filter; } /** * Set/get if element should record its data in the database * * @deprecated - not used * * @return bool */ public function setIsRecordedInDatabase() { return true; } /** * Internal element validation * * @param array $data Form data * @param int $repeatCounter Repeat group counter * * @return bool */ public function validate($data, $repeatCounter = 0) { return true; } /** * Get validation error - run through JText * * @return string */ public function getValidationErr() { return FText::_($this->validationError); } /** * Is the element consider to be empty for purposes of rendering on the form, * i.e. for assigning classes, etc. Can be overridden by individual elements. * * NOTE - this was originally intended for validation, but wound up being used for both validation * AND rendering. Which doesn't really work, because the $data can be entirely different. Tried * adding dataConsideredEmptyForValidation() below, but that causes issues where elements don't have * one, we'd need to go through in one swoop and split them out in every element. So for now, leave this * as the default which is called in both contexts, BUT the notempty validation checks to see if an * element model has a dataCOnsideredEmptyForValidation() method and calls that in preference to this * if it does. We can come back and revisit this issue, as we gradually split out the funcitonality in each * element type. * * @param array $data Data to test against * @param int $repeatCounter Repeat group # * * @return bool */ public function dataConsideredEmpty($data, $repeatCounter) { return ($data == '') ? true : false; } /** * is the element consider to be empty for validation purposes, on form submit * Used in isempty validation rule. Split out from dataConsideredEmpty in 3.2 * * NOTE - see comments on dataConsideredEmpty(), have to hold off on putting this in the main model. * * @param array $data Data to test against * @param int $repeatCounter Repeat group # * * @return bool * * @since 3.2 */ /* public function dataConsideredEmptyForValidation($data, $repeatCounter) { return ($data == '') ? true : false; } */ /** * Get an array of element html ids and their corresponding * js events which trigger a validation. * Examples of where this would be overwritten include timedate element with time field enabled * * @param int $repeatCounter Repeat group counter * * @return array html ids to watch for validation */ public function getValidationWatchElements($repeatCounter) { $id = $this->getHTMLId($repeatCounter); $ar = array('id' => $id, 'triggerEvent' => 'blur'); return array($ar); } /** * Manipulates posted form data for insertion into database * * @param mixed $val This elements posted form data * @param array $data Posted form data * * @return mixed */ public function storeDatabaseFormat($val, $data) { if (is_array($val) && count($val) === 1) { $val = array_shift($val); } if (is_array($val) || is_object($val)) { return json_encode($val); } else { return $val; } } /** * When importing csv data you can run this function on all the data to * format it into the format that the form would have submitted the date * * @param array &$data To prepare * @param string $key List column heading * @param bool $isRaw Data is raw * * @return array data */ public function prepareCSVData(&$data, $key, $isRaw = false) { return $data; } /** * Determines if the data in the form element is used when updating a record * * @param mixed $val Element form data * * @return bool true if ignored on update, default = false */ public function ignoreOnUpdate($val) { return false; } /** * can be overwritten in add-on class * * checks the posted form data against elements INTERNAL validation rule - e.g. file upload size / type * * @param array $aErrors Existing errors * @param object &$groupModel Group model * @param object &$formModel Form model * @param array $data Posted data * * @deprecated - not used * * @return array updated errors */ public function validateData($aErrors, &$groupModel, &$formModel, $data) { return $aErrors; } /** * Determines the label used for the browser title * in the form/detail views * * @param array $data Form data * @param int $repeatCounter When repeating joined groups we need to know what part of the array to access * @param array $opts Options * * @return string Text to add to the browser's title */ public function getTitlePart($data, $repeatCounter = 0, $opts = array()) { $titlePart = $this->getValue($data, $repeatCounter, $opts); return is_array($titlePart) ? implode(', ', $titlePart) : $titlePart; } /** * This really does get just the default value (as defined in the element's settings) * * @param array $data Form data * * @return mixed */ public function getDefaultValue($data = array()) { if (!isset($this->default)) { $w = new FabrikWorker; $element = $this->getElement(); $default = $w->parseMessageForPlaceHolder($element->default, $data); if ($element->eval == "1" && is_string($default)) { /** * Inline edit with a default eval'd "return FabrikHelperElement::filterValue(290);" * was causing the default to be eval'd twice (no idea y) - add in check for 'return' into eval string * see http://fabrikar.com/forums/showthread.php?t=30859 */ if (!stristr($default, 'return')) { $this->_default = $default; } else { FabrikHelperHTML::debug($default, 'element eval default:' . $element->label); $default = stripslashes($default); $default = @eval($default); FabrikWorker::logEval($default, 'Caught exception on eval of ' . $element->name . ': %s'); // Test this does stop error $this->_default = $default === false ? '' : $default; } } if (is_array($default)) { foreach ($default as &$d) { $d = FText::_($d); } $this->default = $default; } else { $this->default = FText::_($default); } } return $this->default; } /** * Called by form model to build an array of values to encrypt * * @param array &$values Previously encrypted values * @param array $data Form data * @param int $c Repeat group counter * * @return void */ public function getValuesToEncrypt(&$values, $data, $c) { $name = $this->getFullName(true, false); $opts = array('raw' => true); $group = $this->getGroup(); if ($group->canRepeat()) { if (!array_key_exists($name, $values)) { $values[$name]['data'] = array(); } $values[$name]['data'][$c] = $this->getValue($data, $c, $opts); } else { $values[$name]['data'] = $this->getValue($data, $c, $opts); } } /** * Element plugin specific method for setting unencrypted values back into post data * * @param array &$post Data passed by ref * @param string $key Key * @param string $data Elements unencrypted data * * @return void */ public function setValuesFromEncryt(&$post, $key, $data) { FArrayHelper::setValue($post, $key, $data); FArrayHelper::setValue($_REQUEST, $key, $data); // $$$rob even though $post is passed by reference - by adding in the value // we aren't actually modifying the $_POST var that post was created from $this->app->input->set($key, $data); } /** * Determines the value for the element in the form view * * @param array $data Form data * @param int $repeatCounter When repeating joined groups we need to know what part of the array to access * * @return string value */ public function getROValue($data, $repeatCounter = 0) { return $this->getValue($data, $repeatCounter); } /** * Helper method to get the default value used in getValue() * For readonly elements: * If the form is new we need to get the default value * If the form is being edited we don't want to get the default value * Otherwise use the 'use_default' value in $opts, defaulting to true * * @param array $data Form data * @param array $opts Options * * @since 3.0.7 * * @return mixed value */ protected function getDefaultOnACL($data, $opts) { // Rob - 31/10/2012 - if readonly and editing an existing record we don't want to show the default label if (!$this->isEditable() && FArrayHelper::getValue($data, 'rowid') != 0) { $opts['use_default'] = false; } /** * $$$rob - if no search form data submitted for the search element then the default * selection was being applied instead * otherwise get the default value so if we don't find the element's value in $data we fall back on this value */ return FArrayHelper::getValue($opts, 'use_default', true) == false ? '' : $this->getDefaultValue($data); } /** * Use in list model storeRow() to determine if data should be stored. * Currently only supported for db join elements whose values are default values * avoids casing '' into 0 for int fields * * @param array $data Data being inserted * @param mixed $val Element value to insert into table * * @since 3.0.7 * * @return boolean */ public function dataIsNull($data, $val) { return false; } /** * Determines the value for the element in the form view * * @param array $data Form data * @param int $repeatCounter When repeating joined groups we need to know what part of the array to access * @param array $opts Options, 'raw' = 1/0 use raw value * * @return string value */ public function getValue($data, $repeatCounter = 0, $opts = array()) { $input = $this->app->input; if (!isset($this->defaults)) { $this->defaults = array(); } $key = $repeatCounter . '.' . serialize($opts); if (!array_key_exists($key, $this->defaults)) { $groupRepeat = $this->getGroupModel()->canRepeat(); $default = $this->getDefaultOnACL($data, $opts); $name = $this->getFullName(true, false); if (FArrayHelper::getValue($opts, 'raw', 0) == 1) { $name .= '_raw'; } /** * @FIXME - if an element is NULL in the table, we will be applying the default even if this * isn't a new form. Probaby needs to be a global option, although not entirely sure what * we would set it to ... */ $values = FArrayHelper::getValue($data, $name, $default); // Querystring override (seems on http://fabrikar.com/subscribe/form/22 querystring var was not being set into $data) if (FArrayHelper::getValue($opts, 'use_querystring', false)) { if ((is_array($values) && empty($values)) || $values === '') { // Trying to avoid errors if value is an array $values = $input->get($name, null, 'array'); if (is_null($values) || (count($values) === 1 && $values[0] == '')) { $values = $input->get($name, '', 'string'); } } } if ($groupRepeat) { // Weird bug where stdClass with key 0, when cast to (array) you couldn't access values[0] if (is_object($values)) { $values = ArrayHelper::fromObject($values); } if (!is_array($values)) { $values = (array) $values; } $values = FArrayHelper::getValue($values, $repeatCounter, ''); } if (FArrayHelper::getValue($opts, 'runplugins', false)) { $formModel = $this->getFormModel(); FabrikWorker::getPluginManager()->runPlugins('onGetElementDefault', $formModel, 'form', $this); } $this->defaults[$key] = $values; } return $this->defaults[$key]; } /** * Is the element hidden or not - if not set then return false * * @return bool */ public function isHidden() { $element = $this->getElement(); return ($element->hidden == true) ? true : false; } /** * Used in things like date when its id is suffixed with _cal * called from getLabel(); * * @param string &$id Initial id * * @return void */ protected function modHTMLId(&$id) { } /** * Should the element be tipped? * * @param string $mode Form/list render context * * @since 3.0.6 * * @return bool */ private function isTipped($mode = 'form') { $formModel = $this->getFormModel(); if ($formModel->getParams()->get('tiplocation', 'tip') !== 'tip' && $mode === 'form') { return false; } $params = $this->getParams(); if ($params->get('rollover', '') === '') { return false; } if ($mode == 'form' && (!$formModel->isEditable() && $params->get('labelindetails', true) == false)) { return false; } if ($mode === 'list' && $params->get('labelinlist', false) == false) { return false; } return true; } /** * Get list heading label * * @return string */ public function getListHeading() { $params = $this->getParams(); $element = $this->getElement(); $label = $params->get('alt_list_heading') == '' ? $element->label : $params->get('alt_list_heading'); return FText::_($label); } /** * Get the element's HTML label * * @param int $repeatCounter Group repeat counter * @param string $tmpl Form template * * @return string label */ public function getLabel($repeatCounter, $tmpl = '') { $element = $this->getElement(); $this->modHTMLId($elementHTMLId); $model = $this->getFormModel(); $displayData = new stdClass; $displayData->canView = $this->canView(); $displayData->id = $this->getHTMLId($repeatCounter); $displayData->canUse = $this->canUse(); $displayData->j3 = FabrikWorker::j3(); $displayData->hidden = $this->isHidden(); $displayData->label = FText::_($element->label); $displayData->hasLabel = $this->get('hasLabel'); $displayData->view = $this->app->input->get('view', 'form'); $displayData->tip = $this->tipHtml($model->data); $displayData->tipText = $this->tipTextAndValidations('form', $model->data); $displayData->rollOver = $this->isTipped(); $displayData->isEditable = $this->isEditable(); $displayData->tipOpts = $this->tipOpts(); $labelClass = ''; if ($displayData->canView || $displayData->canUse) { $labelClass = 'fabrikLabel control-label'; if (empty($displayData->label)) { $labelClass .= ' fabrikEmptyLabel'; } if ($displayData->rollOver) { $labelClass .= ' fabrikHover'; } if ($displayData->hasLabel && !$displayData->hidden) { if ($displayData->tip !== '') { $labelClass .= ' fabrikTip'; } } } $displayData->icons = ''; $iconOpts = array('icon-class' => 'small'); if ($displayData->rollOver) { $displayData->icons .= FabrikHelperHTML::image('question-sign', 'form', $tmpl, $iconOpts) . ' '; } if ($displayData->isEditable) { $displayData->icons .= $this->validator->labelIcons(); } $displayData->labelClass = $labelClass; $layout = FabrikHelperHTML::getLayout('fabrik-element-label', $this->labelPaths()); $str = $layout->render($displayData); return $str; } /** * Get an array of paths to look for the element template. * * @return array */ protected function labelPaths() { $basePath = COM_FABRIK_BASE . 'components/com_fabrik/layouts/element'; $pluginPath = COM_FABRIK_BASE . '/plugins/fabrik_element/' . $this->getPluginName() . '/layouts'; $perThemePath = JPATH_THEMES . '/' . $this->app->getTemplate() . '/html/layouts/com_fabrik/element'; $perElementPath = JPATH_THEMES . '/' . $this->app->getTemplate() . '/html/layouts/com_fabrik/element/' . $this->getFullName(true, false); return array($basePath, $pluginPath, $perThemePath, $perElementPath); } /** * Set fabrikErrorMessage div with potential error messages * * @param int $repeatCounter repeat counter * @param string $tmpl template * * @return string */ protected function addErrorHTML($repeatCounter, $tmpl = '') { $err = $this->getErrorMsg($repeatCounter); $err = htmlspecialchars($err, ENT_QUOTES); $layout = FabrikHelperHTML::getLayout('element.fabrik-element-error'); $displayData = new stdClass; $displayData->err = $err; $displayData->tmpl = $tmpl; return $layout->render($displayData); } /** * Add tips on element labels * does ACL check on element's label in details setting * * @param string $txt Label * @param array $data Row data * @param string $mode Form/list render context * * @return string Label with tip */ protected function rollover_old($txt, $data = array(), $mode = 'form') { if (is_object($data)) { $data = ArrayHelper::fromObject($data); } $rollOver = $this->tipHtml($data, $mode); return $rollOver !== '' ? '' . $txt . '' : $txt; } /** * Add tips on element labels * does ACL check on element's label in details setting * * @param string $txt Label * @param array $data Row data * @param string $mode Form/list render context * * @return string Label with tip */ protected function rollover($txt, $data = array(), $mode = 'form') { if (is_object($data)) { $data = ArrayHelper::fromObject($data); } //$title = $this->tipTextAndValidations($mode, $data); //$opts = $this->tipOpts(); //$opts = json_encode($opts); //return $title !== '' ? 'title="' . $title . '" opts=\'' . $opts . '\'' : ''; $layout = FabrikHelperHTML::getLayout('element.fabrik-element-tip'); $displayData = new stdClass; $displayData->tipTitle = $this->tipTextAndValidations($mode, $data); $displayData->tipText = $txt; $displayData->rollOver = $this->isTipped(); $displayData->isEditable = $this->isEditable(); $displayData->tipOpts = $this->tipOpts(); $rollOver = $layout->render($displayData); return $rollOver; } /** * Get the hover tip options * * @return stdClass */ protected function tipOpts() { $params = $this->getParams(); $opts = new stdClass; $pos = $params->get('tiplocation', 'top'); $opts->formTip = true; $opts->position = $pos; $opts->trigger = 'hover'; $opts->notice = true; if ($this->editable) { if ($this->validator->hasValidations()) { $opts->heading = FText::_('COM_FABRIK_VALIDATION'); } } return $opts; } /** * Get Hover tip text and validation text * * @param string $mode View mode form/list * @param array $data Model data * * @return string */ protected function tipTextAndValidations($mode, $data = array()) { $lines = array(); $tmpl = $this->getFormModel()->getTmpl(); if (($mode === 'list' || !$this->validator->hasValidations()) && !$this->isTipped($mode)) { return ''; } $lines[] = ''; } $lines = array_unique($lines); $rollOver = implode('', $lines); // $$$ rob - looks like htmlspecialchars is needed otherwise invalid markup created and pdf output issues. $rollOver = htmlspecialchars($rollOver, ENT_QUOTES); //$rollOver = str_replace('"', '"', $rollOver); return $rollOver; } /** * Get the element tip HTML * * @param array $data to use in parse holders - defaults to form's data * * @return string tip HTML */ protected function getTipText($data = null) { if (is_null($data)) { $data = $this->getFormModel()->data; } $model = $this->getFormModel(); $params = $this->getParams(); if (!$model->isEditable() && !$params->get('labelindetails')) { return ''; } $w = new FabrikWorker; $tip = $w->parseMessageForPlaceHolder($params->get('rollover'), $data); if ($params->get('tipseval')) { if (FabrikHelperHTML::isDebug()) { $res = eval($tip); } else { $res = @eval($tip); } FabrikWorker::logEval($res, 'Caught exception (%s) on eval of ' . $this->getElement()->name . ' tip: ' . $tip); $tip = $res; } $tip = FText::_($tip); return $tip; } /** * Used for the name of the filter fields * For element this is an alias of getFullName() * Overridden currently only in databasejoin class * * @return string element filter name */ public function getFilterFullName() { return FabrikString::safeColName($this->getFullName(true, false)); } /** * Get the field name to use in the list's slug url * * @param bool $raw raw * * @since 3.0.6 * * @return string element slug name */ public function getSlugName($raw = false) { return $this->getFilterFullName(); } /** * Set and override element full name (used in pw element) * * @param string $name Element name * @param bool $useStep Concat name with form's step element (true) or with '.' (false) default true * @param bool $incRepeatGroup Include '[]' at the end of the name (used for repeat group elements) default true * * @return void */ public function setFullName($name = '', $useStep = true, $incRepeatGroup = true) { $groupModel = $this->getGroup(); $formModel = $this->getFormModel(); $element = $this->getElement(); $key = $element->id . '.' . $groupModel->get('id') . '_' . $formModel->getId() . '_' . $useStep . '_' . $incRepeatGroup; $this->fullNames[$key] = $name; } /** * If already run then stored value returned * * @param bool $useStep Concat name with form's step element (true) or with '.' (false) default true * @param bool $incRepeatGroup Include '[]' at the end of the name (used for repeat group elements) default true * * @return string element full name */ public function getFullName($useStep = true, $incRepeatGroup = true) { $groupModel = $this->getGroup(); $formModel = $this->getFormModel(); $listModel = $this->getListModel(); $element = $this->getElement(); $key = $element->id . '.' . $groupModel->get('id') . '_' . $formModel->getId() . '_' . $useStep . '_' . $incRepeatGroup; if (isset($this->fullNames[$key])) { return $this->fullNames[$key]; } $table = $listModel->getTable(); $db_table_name = $table->db_table_name; $thisStep = ($useStep) ? $formModel->joinTableElementStep : '.'; if ($groupModel->isJoin()) { $joinModel = $groupModel->getJoinModel(); $join = $joinModel->getJoin(); $fullName = $join->table_join . $thisStep . $element->name; } else { $fullName = $db_table_name . $thisStep . $element->name; } if ($groupModel->canRepeat() == 1 && $incRepeatGroup) { $fullName .= '[]'; } $this->fullNames[$key] = $fullName; return $fullName; } /** * Get order by full name * * @param bool $useStep Concat name with form's step element (true) or with '.' (false) default true * * @return string Order by full name */ public function getOrderbyFullName($useStep = true) { return $this->getFullName($useStep); } /** * When copying elements from an existing table * once a copy of all elements has been made run them through this method * to ensure that things like watched element id's are updated * * @param array $newElements copied element ids (keyed on original element id) * * @return void */ public function finalCopyCheck($newElements) { // Overwritten in element class } /** * Copy an element table row * * @param int $id Element id to copy * @param string $copyText Feedback msg * @param int $groupId Group model id * @param string $name New element name * * @return mixed Error or new row */ public function copyRow($id, $copyText = 'Copy of %s', $groupId = null, $name = null) { /** @var FabrikTableElement $rule */ $rule = FabTable::getInstance('Element', 'FabrikTable'); $rule->load((int) $id); $rule->id = null; $rule->label = sprintf($copyText, $rule->label); if (!is_null($groupId)) { $rule->group_id = $groupId; } if (!is_null($name)) { $rule->name = $name; } $groupModel = JModelLegacy::getInstance('Group', 'FabrikFEModel'); $groupModel->setId($groupId); $groupListModel = $groupModel->getListModel(); // $$$ rob - if its a joined group then it can have the same element names if ((int) $groupModel->getGroup()->is_join === 0) { if ($groupListModel->fieldExists($rule->name, array(), $groupModel)) { $this->app->enqueueMessage(FText::_('COM_FABRIK_ELEMENT_NAME_IN_USE'), 'error'); return false; } } $date = $this->date; $tz = new DateTimeZone($this->app->get('offset')); $date->setTimezone($tz); $rule->created = $date->toSql(); $params = $rule->params == '' ? new stdClass : json_decode($rule->params); $params->parent_linked = 1; $rule->params = json_encode($params); $rule->parent_id = $id; $config = JComponentHelper::getParams('com_fabrik'); if ($config->get('unpublish_clones', false)) { $rule->published = 0; } $rule->store(); /** * I thought we did this in an overridden element model method, like onCopy? * if its a database join then add in a new join record */ if (is_a($this, 'PlgFabrik_ElementDatabasejoin')) { $join = FabTable::getInstance('Join', 'FabrikTable'); $join->load(array('element_id' => $id)); $join->id = null; $join->element_id = $rule->id; $join->group_id = $rule->group_id; $join->store(); } // Copy js events $db = FabrikWorker::getDbo(true); $query = $db->getQuery(true); $query->select('id')->from('#__{package}_jsactions')->where('element_id = ' . (int) $id); $db->setQuery($query); $actions = $db->loadColumn(); foreach ($actions as $id) { $jsCode = FabTable::getInstance('Jsaction', 'FabrikTable'); $jsCode->load($id); $jsCode->id = 0; $jsCode->element_id = $rule->id; $jsCode->store(); } return $rule; } /** * Get the element's raw label (used for details view, not wrapped in