Spade
Mini Shell
| Directory:~$ /proc/self/root/home/lmsyaran/public_html/joomla5/components/com_fabrik/models/ |
| [Home] [System Details] [Kill Me] |
<?php
/**
* Fabrik Element Model
*
* @package Joomla
* @subpackage Fabrik
* @copyright Copyright (C) 2005-2023 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\Application\ApplicationHelper;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Layout\FileLayout;
use Joomla\CMS\Layout\LayoutInterface;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Filter\InputFilter;
use Joomla\CMS\Profiler\Profiler;
use Joomla\CMS\Session\Session;
use Joomla\CMS\Form\FormHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Object\CMSObject;
use Joomla\CMS\Filesystem\File;
use Joomla\CMS\Filesystem\Folder;
use Fabrik\Helpers\LayoutFile;
use Joomla\Registry\Registry;
use Joomla\Utilities\ArrayHelper;
use Joomla\String\StringHelper;
use Joomla\CMS\Date\Date;
use Joomla\CMS\HTML\HTMLHelper;
use Fabrik\Helpers\Php;
jimport('joomla.application.component.model');
jimport('joomla.filesystem.file');
/**
* Fabrik Element Model
*
* @package Fabrik
* @since 3.0
*/
#[\AllowDynamicProperties]
class PlgFabrik_Element extends FabrikPlugin
{
/**
* Element id
*
* @var int
*/
protected $id = null;
/**
* Javascript actions to attach to element
*
* @var array
*/
protected $jsActions = null;
/**
* Editable
*
* @var bool
*/
public $editable = null;
/**
* Is an upload element
*
* @var bool
*/
protected $is_upload = false;
/**
* Does the element's data get recorded in the db
*
* @var bool
*/
protected $recordInDatabase = true;
/**
* Contain access rights
*
* @var object
*/
protected $access = null;
/**
* Validation error
*
* @var string
*/
protected $validationError = null;
/**
* Stores possible element names to avoid repeat db queries
*
* @var array
*/
public $fullNames = array();
/**
* Group model
*
* @var object
*/
protected $group = null;
/**
* Form model
*
* @var object
*/
protected $form = null;
/**
* List model
*
* @var object
*/
protected $list = null;
/**
* Element object
*
* @var FabrikTableElement
*/
public $element = null;
/**
* If the element 'Include in search all' option is set to
'default' then this states if the
* element should be ignored from search all.
*
* @var bool True, ignore in extended search all.
*/
protected $ignoreSearchAllDefault = false;
/**
* Does the element have a label
*
* @var bool
*/
private $hasLabel = true;
/**
* Does the element contain sub elements e.g checkboxes radiobuttons
*
* @var bool
*/
public $hasSubElements = false;
/**
* Valid image extensions
*
* @var array
*/
protected $imageExtensions = array('jpg', 'jpeg',
'gif', 'bmp', 'png');
/**
* Is the element in a detailed view?
*
* @var bool
*/
public $inDetailedView = false;
/**
* Default values
*
* @var array
*/
public $defaults = array();
/**
* The element's HTML ids based on $repeatCounter
*
* @var array
*/
public $HTMLids = array();
/**
* Is the element in a repeat group
*
* @var bool
*/
public $inRepeatGroup = null;
/**
* Is the element in a new group.
*
* Used by pre rendering / getGroupView so elements can set a default on
new repeat groups,
* even if it isn't a new record.
*
* @var bool
*/
public $newGroup = null;
/**
* Default value
*
* @var string
*/
protected $default = null;
/**
* Join model
*
* @var object
*/
protected $joinModel = null;
/**
* Has the icon been set
*
* @var bool
*/
protected $iconsSet = false;
/**
* Parent element row - if no parent returns element
*
* @var object
*/
protected $parent = null;
/**
* Actual table name (table or joined tables db table name)
*
* @var string
*/
protected $actualTable = null;
/**
* Ensures the query values are only escaped once
*
* @var bool
*/
protected $escapedQueryValue = false;
/**
* Db table field type
*
* @var string
*/
protected $fieldDesc = 'VARCHAR(%s)';
/**
* Db table field size
*
* @var string
*/
protected $fieldSize = '255';
/**
* Element error msg
*
* @var string
*/
protected $elementError = '';
/**
* Multi-db join option - can we add duplicate options (set to false in
tags element)
*
* @var bool
*/
protected $allowDuplicates = true;
/**
* Wraps 'foo' AS 'bar' as statements with
SUM('foo') = 'bar'
* Used in nv3d viz to alter query statements
*
* @var string
*/
public $calcSelectModifier = null;
public static $fxAdded = array();
/**
* @var FabrikFEModelElementValidator
*/
public $validator;
/**
* Selected filter value labels
*
* @var array
*/
public $filterDisplayValues = array();
/**
* Cache for eval'ed options for dropdowns
*
* @var array
*/
protected $phpOptions = array();
/**
*
* Cache for suboptions
*
* @var null|array
*
* @since 3.7
*/
protected $subOptionValues = null;
/**
* Cache for subpoption labels
*
* @var null|array
*/
protected $subOptionLabels = null;
/* dynamic properties to make php8.2 happy */
public $tmpl = null;
public $_foreignKey = null;
public $_repeatGroupTotal = null;
public $_inJoin = null;
public $elementHTMLName = null;
public $_default = null;
/**
* Constructor
*
* @param object &$subject The object to observe
* @param array $config An array that holds the plugin configuration
*
* @since 1.5
*/
public function __construct(&$subject, $config = array())
{
parent::__construct($subject, $config);
$this->validator =
Factory::getApplication()->bootComponent('com_fabrik')->getMVCFactory()->createModel('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)
{
Table::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)
{
Table::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 =
Factory::getApplication()->bootComponent('com_fabrik')->getMVCFactory()->createModel('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 =
Factory::getApplication()->bootComponent('com_fabrik')->getMVCFactory()->createModel('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.9 - icon_folder is a three way - search through template
folders for icons or use class
*
* @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.9 - icon_folder is a three way - search through template
folders for icons or use class
*
* @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',
'');
$iconFolder = (int) $params->get('icon_folder',
'');
if ($iconFile === '{extension}')
{
$iconFileInfo = pathinfo(trim(strip_tags($data)));
$iconFile = FArrayHelper::getValue($iconFileInfo, 'extension',
'');
}
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 (!empty($img))
{
break;
}
}
if (empty($img) && $iconFolder === 2)
{
$opts = array('forceImage' => false);
$img = FabrikHelperHTML::image($cleanData, $view, $tmpl, array(),
false, $opts);
}
if (!empty($img))
{
$this->iconsSet = true;
$opts = new stdClass;
$opts->position = 'top';
$opts = json_encode($opts);
$data = '<span>' . $data .
'</span>';
// See if data has an <a> tag
$html = FabrikHelperHTML::loadDOMDocument($data);
$as = $html->getElementsBytagName('a');
if ($params->get('icon_hovertext', true))
{
//$aHref = 'javascript:void(0)';
$aHref = '#';
$target = '';
if ($as->length)
{
// Data already has an <a href="foo"> 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);
$layout =
FabrikHelperHTML::getLayout('element.fabrik-element-listicon-tip');
$displayData = new stdClass;
$displayData->img = $img;
$displayData->title = $data;
$displayData->href = $aHref;
$displayData->target = $target;
$displayData->opts = $opts;
$img = $layout->render($displayData);
//$img = '<a class="fabrikTip" ' . $target .
' href="' . $aHref . '" opts=\'' . $opts
. '\' title="' . $data . '">' . $img
. '</a>';
}
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 ($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) || !isset($this->access->{$key}))
{
$groups = $this->user->getAuthorisedViewLevels();
$this->access->$key = in_array($params->get($prop, $default),
$groups);
}
// If no group access, can override with check on lookup element's
value = logged in user id.
if (!$this->access->$key &&
$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(true, 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');
}
}
}
else if ($this->access->$key && $view == 'form')
{
$formModel = $this->getFormModel();
$pluginManager = FabrikWorker::getPluginManager();
if (in_array(false,
$pluginManager->runPlugins('onElementCanView', $formModel,
'form', $this)))
{
$this->access->$key = false;
}
}
else if ($this->access->$key && $view == 'list')
{
$listModel = $this->getListModel();
$pluginManager = FabrikWorker::getPluginManager();
if (in_array(false,
$pluginManager->runPlugins('onElementCanViewList', $listModel,
'list', $this)))
{
$this->access->$key = false;
}
}
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) || !isset($this->access->use))
{
/**
* $$$ 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');
}
}
}
else if ($this->access->use && $location ==
'form')
{
$formModel = $this->getFormModel();
$pluginManager = FabrikWorker::getPluginManager();
if (in_array(false,
$pluginManager->runPlugins('onElementCanUse', $formModel,
'form', $this)))
{
$this->access->use = false;
}
}
}
}
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) || !isset($this->access->filter))
{
$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 Text
*
* @return string
*/
public function getValidationErr()
{
return Text::_($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' =>
$this->getChangeEvent());
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;
}
/**
* Should the element ignore a form or list row copy, and use the default
regardless
*
* @return bool
*/
public function defaultOnCopy()
{
$params = $this->getParams();
return $params->get('default_on_copy', '0') ===
'1';
}
/**
* 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);
FabrikWorker::clearEval();
$default = Php::Eval(['code' => $default,
'vars'=>['data'=>$data]]);
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 = Text::_($d);
}
$this->default = $default;
}
else
{
$this->default = Text::_($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->getInput()->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() && (int)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->getInput();
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 ...
*/
//Fix: don't apply the default if a rowid is set and >=1 (=
existing record)
if (array_key_exists('rowid',$data) &&
!empty($data['rowid'])) {
$values = FArrayHelper::getValue($data, $name);
}
else {
$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
* @param array $data data array
*
* @since 3.0.6
*
* @return bool
*/
private function isTipped($mode = 'form', $data = array())
{
$formModel = $this->getFormModel();
if ($formModel->getParams()->get('tiplocation',
'tip') !== 'tip' && $mode === 'form')
{
return false;
}
$params = $this->getParams();
if (empty($this->getTipText($data)))
{
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 Text::_($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->j3 = true;
$displayData->hidden = $this->isHidden();
$displayData->label = Text::_($element->label);
$displayData->altLabel = $this->getListHeading();
$displayData->hasLabel = $this->hasLabel;
$displayData->view =
$this->app->getInput()->get('view', 'form');
$displayData->tip = $this->tipHtml($model->data);
$displayData->tipText =
$this->tipTextAndValidations('form', $model->data);
$displayData->rollOver = $this->isTipped('form',
$model->data);
$displayData->isEditable = $this->isEditable();
$displayData->tipOpts = $this->tipOpts();
$displayData->labelPosition = $this->group->labelPosition();
$labelClass = 'form-label';
if ($displayData->canView || $displayData->canUse)
{
$labelClass .= ' fabrikLabel ';
if (empty($displayData->label))
{
$labelClass .= ' fabrikEmptyLabel';
}
if ($displayData->rollOver)
{
$labelClass .= ' fabrikHover';
}
if ($displayData->hasLabel && !$displayData->hidden)
{
if ($displayData->tip !== '')
{
//trob: this triggers FabrikJS, use layout
components\com_fabrik\layouts\element\fabrik-element-label.php
//$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()
{
// base built-in layouts
$basePath = COM_FABRIK_BASE .
'components/com_fabrik/layouts/element';
$pluginPath = COM_FABRIK_BASE . '/plugins/fabrik_element/'
. $this->getPluginName() . '/layouts';
// Custom per template layouts
$view = $this->getFormModel()->isEditable() ? 'form' :
'details';
$tmplPath = COM_FABRIK_FRONTEND . '/views/'. $view .
'/tmpl/' . $this->getFormModel()->getTmpl() .
'/layouts/element/';
$tmplElPath = COM_FABRIK_FRONTEND . '/views/'. $view .
'/tmpl/' . $this->getFormModel()->getTmpl() .
'/layouts/element/' . $this->getFullName(true, false);
// Custom per theme 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, $tmplPath, $tmplElPath,
$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 !== '' ? '<span
class="fabrikTip" ' . $rollOver . '>' . $txt .
'</span>' : $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);
// $$$ hugh - only run the layout if there's a title, save row
rendering time
if (!empty($title))
{
$layout =
FabrikHelperHTML::getLayout('element.fabrik-element-tip');
$displayData = new stdClass;
$displayData->tipTitle = $title;
$displayData->tipText = $txt;
$displayData->rollOver = $this->isTipped('form',
$data);
$displayData->isEditable = $this->isEditable();
$displayData->tipOpts = $this->tipOpts();
$rollOver = $layout->render($displayData);
}
else
{
// defensive coding for corner case of calcs with JSON data
$rollOver = is_scalar($txt) ? (string)$txt : '';
}
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 = Text::_('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, $data))
{
return '';
}
if ($this->isTipped($mode, $data))
{
$lines[] = '<li>' .
FabrikHelperHTML::image('question-sign', 'form', $tmpl)
. ' ' . $this->getTipText($data) . '</li>';
}
if ($mode === 'form')
{
$validationTexts = $this->validator->hoverTexts();
foreach ($validationTexts as $validationText)
{
$lines[] = '<li>' . $validationText .
'</li>';
}
}
if (count($lines) > 0)
{
array_unshift($lines,'<ul class="validation-notices"
style="list-style:none">');
$lines[] = '</ul>';
$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);
}
else
{
$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'))
{
FabrikWorker::clearEval();
$res = Php::Eval(['code' => $tip, 'vars' =>
['data'=>$data]]);
FabrikWorker::logEval($res, 'Caught exception (%s) on eval of
' . $this->getElement()->name . ' tip: ' .
str_replace('%','%',$tip));
$tip = $res;
}
$tip = Text::_($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 =
Factory::getApplication()->bootComponent('com_fabrik')->getMVCFactory()->createModel('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(Text::_('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 =
ComponentHelper::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('#__fabrik_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
<label> tags
*
* @return string Label
*/
protected function getRawLabel()
{
return $this->element->label;
}
/**
* This was in the views display and _getElement code but seeing as its
used
* by multiple views its safer to have it here
*
* @param int $c Repeat group counter
* @param int $elCount Order in which the element is shown in the
form
* @param string $tmpl Template
*
* @return mixed - false if you shouldn't continue to render the
element
*/
public function preRender($c, $elCount, $tmpl)
{
$model = $this->getFormModel();
$groupModel = $this->getGroup();
$group = $groupModel->getGroupProperties($model);
if (!$this->canUse() && !$this->canView())
{
return false;
}
if (!$this->canUse())
{
$this->setEditable(false);
}
else
{
$editable = $model->isEditable() ? true : false;
$this->setEditable($editable);
}
// Force reload?
$this->HTMLids = null;
$elementTable = $this->getElement();
$element = new stdClass;
$element->startRow = 0;
$element->endRow = 0;
$elHTMLName = $this->getFullName();
// If the element is in a join AND is the join's foreign key then we
don't show the element
if ($elementTable->name == $this->_foreignKey)
{
$element->label = '';
$element->error = '';
$this->element->hidden = true;
}
else
{
$element->error = $this->getErrorMsg($c);
}
$element->plugin = $elementTable->plugin;
$element->hidden = $this->isHidden();
$element->id = $this->getHTMLId($c);
$element->className = 'fb_el_' . $element->id;
//$element->containerClass = $this->containerClass($element);
$element->element = $this->preRenderElement($model->data,
$c);
$element->bsClass = $this->getBsClass();
// Ensure that view data property contains the same html as the
group's element
$model->tmplData[$elHTMLName] = $element->element;
$element->label_raw = Text::_($this->getRawLabel());
// GetLabel needs to know if the element is editable
if ($elementTable->name != $this->_foreignKey)
{
$l = $this->getLabel($c, $tmpl);
$w = new FabrikWorker;
$element->label = $w->parseMessageForPlaceHolder($l,
$model->data);
}
$element->errorTag = $this->addErrorHTML($c, $tmpl);
$element->element_ro = $this->getROElement($model->data, $c);
$element->value = $this->getValue($model->data, $c);
$element->containerClass = $this->containerClass($element);
$elName = $this->getFullName(true, false);
if (array_key_exists($elName . '_raw', $model->data))
{
$element->element_raw = $model->data[$elName . '_raw'];
}
else
{
$element->element_raw = array_key_exists($elName, $model->data) ?
$model->data[$elName] : $element->value;
}
if ($this->dataConsideredEmpty($element->element_ro, $c))
{
$element->containerClass .= ' fabrikDataEmpty';
$element->dataEmpty = true;
}
else
{
$element->dataEmpty = false;
}
// Tips (if not rendered as hovers)
$tip = $this->getTipText();
if ($tip !== '')
{
$tip = FabrikHelperHTML::image('question-sign',
'form', $tmpl) . ' ' . $tip;
}
$element->labels = $groupModel->labelPosition('form');
$element->dlabels =
$groupModel->labelPosition('details');
switch ($model->getParams()->get('tiplocation'))
{
default:
case 'tip':
$element->tipAbove = '';
$element->tipBelow = '';
$element->tipSide = '';
break;
case 'above':
$element->tipAbove = $tip;
$element->tipBelow = '';
$element->tipSide = '';
break;
case 'below':
$element->tipAbove = '';
$element->tipBelow = $tip;
$element->tipSide = '';
break;
case 'side':
$element->tipAbove = '';
$element->tipBelow = '';
$element->tipSide = $tip;
break;
}
return $element;
}
/**
* Build the tip HTML
*
* @param array $data Data
* @param string $mode Mode Form/List
*
* @return string
*/
protected function tipHtml($data = array(), $mode = 'form')
{
$title = $this->tipTextAndValidations($mode, $data);
$opts = $this->tipOpts();
$opts = json_encode($opts);
return $title !== '' ? 'title="' . $title .
'" opts=\'' . $opts . '\'' :
'';
}
/**
* Get the class name for the element wrapping dom object
*
* @param object $element element row
*
* @since 3.0
*
* @return string class names
*/
protected function containerClass($element)
{
$item = $this->getElement();
$c = array('fabrikElementContainer', 'plg-' .
$item->plugin, $element->className);
if ($element->hidden)
{
$c[] = 'fabrikHide';
}
else
{
/**
* $$$ hugh - adding a class name for repeat groups, as per:
* http://fabrikar.com/forums/showthread.php?p=165128#post165128
* But as per my response on that thread, if this turns out to be a
performance
* hit, may take it out. That said, I think having this class will make
things
* easier for custom styling when the element ID isn't constant.
*/
$groupModel = $this->getGroupModel();
if ($groupModel->canRepeat())
{
$c[] = 'fabrikRepeatGroup___' . $this->getFullName(true,
false);
}
}
if ($element->error != '')
{
$c[] = 'fabrikError';
}
$c[] = $this->getParams()->get('containerclass',
'');
$c[] = $this->getRowClassRO($element);
// first run plugins, which may set row class
$formModel = $this->getFormModel();
$args = new stdClass;
$args->rowClass = [];
$args->data = $formModel->data;
$pluginResults =
\Fabrik\Helpers\Worker::getPluginManager()->runPlugins(
'onElementContainerClass',
$formModel,
'form',
$this,
$args
);
$c = array_merge($c, $args->rowClass);
return implode(' ', $c);
}
/**
* Merge the rendered element into the views element storage arrays
*
* @param object $element to merge
* @param array &$aElements element array
* @param array &$namedData Form data
* @param array &$aSubGroupElements sub group element array
*
* @return void
*/
public function stockResults($element, &$aElements, &$namedData,
&$aSubGroupElements)
{
$elHTMLName = $this->getFullName();
$aElements[$this->getElement()->name] = $element;
/**
* $$$ rob 12/10/2012 - $namedData is the formModels data - commenting
out as the form data needs to be consistent
* as we loop over elements - this was setting from a string to an object
?!!!???!!
* $namedData[$elHTMLName] = $element;
*/
if ($elHTMLName)
{
// $$$ rob was keyed on int but that's not very useful for
templating
$aSubGroupElements[$this->getElement()->name] = $element;
}
}
/**
* Pre-render just the element (no labels etc.)
* Was _getElement but this was ambiguous with getElement() and method is
public
*
* @param array $data data
* @param int $repeatCounter repeat group counter
*
* @return string
*/
public function preRenderElement($data, $repeatCounter = 0)
{
$groupModel = $this->getGroupModel();
if (!$this->canView() && !$this->canUse())
{
return '';
}
// Used for working out if the element should behave as if it was in a
new form (joined grouped) even when editing a record
$this->inRepeatGroup = $groupModel->canRepeat();
$this->_inJoin = $groupModel->isJoin();
$opts = array('runplugins' => 1);
$this->getValue($data, $repeatCounter, $opts);
if ($this->isEditable())
{
return $this->render($data, $repeatCounter);
}
else
{
$htmlId = $this->getHTMLId($repeatCounter);
// $$$ rob even when not in ajax mode the element update() method may be
called in which case we need the span
// $$$ rob changed from span wrapper to div wrapper as element's
content may contain divs which give html error
// Placeholder to be updated by ajax code
// @TODO the entity decode causes problems on RO with tooltips
$v = $this->getROElement($data, $repeatCounter);
$v = html_entity_decode($v);
//$v = $v == '' ? ' ' : $v;
return '<div class="fabrikElementReadOnly"
id="' . $htmlId . '">' . $v .
'</div>';
}
}
/**
* Get read-only element
* Was _getROElement() but is a public method
*
* @param array $data data
* @param int $repeatCounter repeat group counter
*
* @return string
*/
public function getROElement($data, $repeatCounter = 0)
{
if (!$this->canView() && !$this->canUse())
{
return '';
}
$editable = $this->isEditable();
$this->setEditable(false);
$v = $this->render($data, $repeatCounter);
$this->addCustomLink($v, $data, $repeatCounter);
$this->setEditable($editable);
return $v;
}
/**
* Add custom link to element - must be uneditable for link to be added
*
* @param string &$v value
* @param array $data row data
* @param int $repeatCounter repeat counter
*
* @return string
*/
protected function addCustomLink(&$v, $data, $repeatCounter = 0)
{
if ($this->isEditable())
{
return $v;
}
$params = $this->getParams();
$customLink = $params->get('custom_link', '');
if ($customLink !== '' &&
$this->getElement()->link_to_detail == '1' &&
$params->get('custom_link_indetails', true))
{
$w = new FabrikWorker;
/**
* $$$ hugh - this should really happen elsewhere, but I needed a quick
fix for handling
* {slug} in detail view links, which for some reason are not
'stringURLSafe' at this point,
* so they are like "4:A Page Title" instead of
4-a-page-title.
*/
if (strstr($customLink, '{slug}') &&
array_key_exists('slug', $data))
{
$slug = str_replace(':', '-',
$data['slug']);
$slug = ApplicationHelper::stringURLSafe($slug);
$customLink = str_replace('{slug}', $slug, $customLink);
}
/**
* Testing new parseMessageForRepeats(), see comments on the function
itself.
*/
$customLink = $w->parseMessageForRepeats($customLink, $data, $this,
$repeatCounter);
$customLink = $w->parseMessageForPlaceHolder($customLink, $data);
$customLink =
$this->getListModel()->parseMessageForRowHolder($customLink, $data);
if (trim($customLink) !== '')
{
$v = '<a href="' . $customLink . '"
data-iscustom="1">' . $v . '</a>';
}
}
return $v;
}
/**
* Get any html error messages
*
* @param int $repeatCount group repeat count
*
* @return string error messages
*/
protected function getErrorMsg($repeatCount = 0)
{
$arErrors = $this->getFormModel()->errors;
$parsed_name = $this->getFullName();
$err_msg = '';
$parsed_name = FabrikString::rtrimword($parsed_name, '[]');
if (isset($arErrors[$parsed_name]))
{
if (array_key_exists($repeatCount, $arErrors[$parsed_name]))
{
if (is_array($arErrors[$parsed_name][$repeatCount]))
{
$err_msg = implode('<br />',
$arErrors[$parsed_name][$repeatCount]);
}
else
{
$err_msg .= $arErrors[$parsed_name][$repeatCount];
}
}
}
return $err_msg;
}
/**
* Draws the html form element
*
* @param array $data To pre-populate element with
* @param int $repeatCounter Repeat group counter
*
* @return string elements html
*/
public function render($data, $repeatCounter = 0)
{
return 'need to overwrite in element plugin class';
}
/**
* Format the read only output for the page
*
* @param string $value Initial value
* @param string $label Label
*
* @return string Read only value
*/
protected function getReadOnlyOutput($value, $label)
{
$params = $this->getParams();
if ($params->get('icon_folder') != -1 &&
$params->get('icon_folder') != '')
{
$icon = $this->replaceWithIcons($value);
if ($this->iconsSet)
{
$label = $icon;
}
}
return $label;
}
/**
* Get hidden field
*
* @param string $name Element name
* @param string $value Element value
* @param string $id Element id
* @param string $class Class name
*
* @return string
*/
protected function getHiddenField($name, $value, $id = '',
$class = 'fabrikinput inputbox hidden')
{
$value = htmlspecialchars($value, ENT_COMPAT, 'UTF-8');
$opts = array('class' => $class, 'type' =>
'hidden', 'name' => $name, 'value' =>
$value, 'id' => $id);
return $this->buildInput('input', $opts);
}
/**
* Helper method to build an input field
*
* @deprecated use LayoutInterfaces instead
*
* @param string $node Input type default 'input'
* @param array $bits Input property => value
* @param bool $shortTag Is $node a <node/> or
<node></node> tag
*
* @return string input
*/
protected function buildInput($node = 'input', $bits = array(),
$shortTag = true)
{
$str = '<' . $node . ' ';
$value = '';
foreach ($bits as $key => $val)
{
if ($node === 'textarea' && $key ===
'value')
{
$value = $val;
continue;
}
$str .= $key . '="' . $val . '" ';
}
$str .= $shortTag ? '' : '>';
if (!$shortTag && $value !== '')
{
$str .= $value;
}
$str .= $shortTag ? '/>' : '</' . $node .
'>';
return $str;
}
/**
* Helper function to build the property array used in buildInput()
*
* @param int $repeatCounter Repeat group counter
* @param mixed $type Null/string $type property (if null then
password/text applied as default)
*
* @return array input properties key/value
*/
protected function inputProperties($repeatCounter, $type = null)
{
$bits = array();
$element = $this->getElement();
$params = $this->getParams();
$size = (int) $element->width < 0 ? 1 : (int)
$element->width;
if (!isset($type))
{
// Changes by JF Questiaux - info@betterliving.be
switch ($params->get('password')) // Kept the name
'password' for backward compatibility
{
case '1' :
$type = 'password';
break;
case '2' :
$type = 'tel';
break;
case '3' :
$type = 'email';
break;
case '4' :
$type = 'search';
break;
case '5' :
$type = 'url';
break;
case '6' :
$type = 'number';
break;
default :
$type = 'text';
}
// End of changes
}
$maxLength = $params->get('maxlength');
if ($maxLength == '0' or $maxLength == '')
{
$maxLength = $size;
}
$class = array();
// Bootstrap 3
$class[] = 'form-control';
if ($this->elementError != '')
{
$class[] = ' elementErrorHighlight';
}
if ($element->hidden == '1')
{
$class[] = ' hidden';
$type = 'hidden';
}
$bits['type'] = $type;
$bits['id'] = $this->getHTMLId($repeatCounter);
$bits['name'] = $this->getHTMLName($repeatCounter);
if (!$element->hidden)
{
$bits['size'] = $size;
$bits['maxlength'] = $maxLength;
}
$class[] = 'fabrikinput inputbox';
$bits['class'] = implode(' ', $class);
if ($params->get('placeholder', '') !==
'')
{
$bits['placeholder'] =
Text::_($params->get('placeholder'));
}
if ($params->get('autocomplete', 1) == 0)
{
$bits['autocomplete'] = 'off';
}
// Cant be used with hidden element types
if ($element->hidden != '1')
{
if ($params->get('readonly'))
{
$bits['readonly'] = "readonly";
$bits['class'] .= " readonly";
}
if ($params->get('disable'))
{
$bits['class'] .= " disabled";
$bits['disabled'] = 'disabled';
}
}
return $bits;
}
/**
* get the id used in the html element
*
* @param int $repeatCounter group counter
*
* @return string
*/
public function getHTMLId($repeatCounter = 0)
{
if (!is_array($this->HTMLids))
{
$this->HTMLids = array();
}
if (!array_key_exists((int) $repeatCounter, $this->HTMLids))
{
$groupModel = $this->getGroup();
$listModel = $this->getListModel();
$table = $listModel->getTable();
$element = $this->getElement();
if ($groupModel->isJoin())
{
$joinModel = $groupModel->getJoinModel();
$joinTable = $joinModel->getJoin();
$fullName = $joinTable->table_join . '___' .
$element->name;
}
else
{
$fullName = $table->db_table_name . '___' .
$element->name;
}
// Change the id for detailed view elements
if ($this->inDetailedView)
{
$fullName .= '_ro';
}
if ($groupModel->canRepeat())
{
$fullName .= '_' . $repeatCounter;
}
$this->HTMLids[$repeatCounter] = $fullName;
}
return $this->HTMLids[$repeatCounter];
}
/**
* get the element html name
*
* @param int $repeatCounter group counter
*
* @return string
*/
public function getHTMLName($repeatCounter = 0)
{
$groupModel = $this->getGroup();
$table = $this->getListModel()->getTable();
$element = $this->getElement();
if ($groupModel->isJoin())
{
$joinModel = $groupModel->getJoinModel();
$joinTable = $joinModel->getJoin();
$fullName = $joinTable->table_join . '___' .
$element->name;
}
else
{
$fullName = $table->db_table_name . '___' .
$element->name;
}
if ($groupModel->canRepeat())
{
// $$$ rob - always use repeatCounter in html names - avoids ajax post
issues with mootools1.1
$fullName .= '[' . $repeatCounter . ']';
}
if ($this->hasSubElements)
{
$fullName .= '[]';
}
// @TODO: check this - repeated elements do need to have something
applied to their id based on their order in the repeated groups
$this->elementHTMLName = $fullName;
return $this->elementHTMLName;
}
/**
* Load element params
*
* @return Registry default element params
*/
public function getParams()
{
if (!isset($this->params))
{
$this->params = new Registry($this->getElement()->params);
}
return $this->params;
}
/**
* Not used
*
* @deprecated
*
* @return mixed
*/
protected function loadPluginParams()
{
if (isset($this->xmlPath))
{
$element = $this->getElement();
$pluginParams = new Registry($element->params);
return $pluginParams;
}
return false;
}
/**
* Loads in elements validation objects
*
* @deprecated use $this->validator->findAll()
*
* @return array validation objects
*/
public function getValidations()
{
return $this->validator->findAll();
}
/**
* get javascript actions
*
* @deprecated ?
*
* @return array js actions
*/
public function getJSActions()
{
if (!isset($this->jsActions))
{
$query = $this->_db->getQuery();
$query->select('*')->from('#__fabrik_jsactions')->where('element_id
= ' . (int) $this->id);
$this->_db->setQuery($query);
$this->jsActions = $this->_db->loadObjectList();
}
return $this->jsActions;
}
/**
*Create the js code to observe the elements js actions
*
* @param string $jsControllerKey Either form_ or _details
* @param int $repeatCount Counter
*
* @return string js events
*/
public function getFormattedJSActions($jsControllerKey, $repeatCount)
{
$jsStr = '';
$allJsActions = $this->getFormModel()->getJsActions();
/**
* 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.
* $element = $this->getParent();
*/
$element = $this->getElement();
$w = new FabrikWorker;
if (array_key_exists($element->id, $allJsActions))
{
$elId = $this->getHTMLId($repeatCount);
foreach ($allJsActions[$element->id] as $jsAct)
{
$js = $jsAct->code;
$js = str_replace(array("\n", "\r"), "",
$js);
// Don't think we need to do this any more, although removing it
will break bc
/*
if ($jsAct->action == 'load')
{
// JS code is already stored in the db as htmlspecialchars()
09/08/2013
$quote = ''';
$js = preg_replace('#\bthis\b#', 'document.id('
. $quote . $elId . $quote . ')', $js);
}
*/
if ($jsAct->action != '' && $js !== '')
{
$jsSlashes = addslashes($js);
$jsStr .= $jsControllerKey .
".dispatchEvent('$element->plugin', '$elId',
'$jsAct->action', '$jsSlashes');\n";
}
else
{
// Build wysiwyg code
if (isset($jsAct->js_e_event) && $jsAct->js_e_event !=
'')
{
// $$$ rob get the correct element id based on the repeat counter
$triggerEl =
$this->getFormModel()->getElement(str_replace('fabrik_trigger_element_',
'', $jsAct->js_e_trigger));
$triggerid = is_object($triggerEl) ? 'element_' .
$triggerEl->getHTMLId($repeatCount) : $jsAct->js_e_trigger;
$key = $elId . serialize($jsAct);
if (array_key_exists($key, self::$fxAdded))
{
// Avoid duplicate events
continue;
}
$jsStr .= $jsControllerKey .
".addElementFX('$triggerid',
'$jsAct->js_e_event');\n";
self::$fxAdded[$key] = true;
$f = InputFilter::getInstance();
$post = $f->clean($_POST, 'array');
$jsAct->js_e_value =
$w->parseMessageForPlaceHolder(htmlspecialchars_decode($jsAct->js_e_value),
$post);
if ($jsAct->js_e_condition == 'hidden')
{
$js = "if (this.getContainer().getStyle('display')
=== 'none') {";
}
elseif ($jsAct->js_e_condition == 'shown')
{
$js = "if (this.getContainer().getStyle('display')
!== 'none') {";
}
elseif ($jsAct->js_e_condition == 'CONTAINS')
{
$js = "if (this.get('value') !== null ";
$js .= " &&
(Array.from(this.get('value')).contains('$jsAct->js_e_value')";
$js .= " ||
this.get('value').contains('$jsAct->js_e_value'))";
$js .= ") {";
}
elseif ($jsAct->js_e_condition == '!CONTAINS')
{
$js = "if (this.get('value') === null ";
$js .= " ||
(!Array.from(this.get('value')).contains('$jsAct->js_e_value')";
$js .= " ||
!this.get('value').contains('$jsAct->js_e_value'))";
$js .= ") {";
}
// $$$ hugh if we always quote the js_e_value, numeric comparison
doesn't work, as '100' < '3'.
// So let's assume if they use <, <=, > or >= they
mean numbers.
elseif (in_array($jsAct->js_e_condition, array('<',
'<=', '>', '>=')))
{
$js .= "if(this.get('value').toFloat()
$jsAct->js_e_condition '$jsAct->js_e_value'.toFloat())
{";
}
elseif ($jsAct->js_e_condition == 'regex')
{
if (preg_match('#^/.+/\w*#', $jsAct->js_e_value))
{
$js .= "if
(this.get('value').toString().test(%%REGEX%%)) {";
}
else
{
$js .= "if
(this.get('value').toString().test(/%%REGEX%%/)) {";
}
}
elseif ($jsAct->js_e_condition == '!regex')
{
if (preg_match('#^/.+/\w*#', $jsAct->js_e_value))
{
$js .= "if
(this.get('value').toString().test(%%REGEX%%)) {";
}
else
{
$js .= "if
(!this.get('value').toString().test(/%%REGEX%%/)) {";
}
}
else
{
$js = "if (this.get('value')
$jsAct->js_e_condition '$jsAct->js_e_value') {";
}
// Need to use corrected triggerid here as well
if (preg_match('#^fabrik_trigger#', $triggerid))
{
$js .= "Fabrik.getBlock('" . $jsControllerKey .
"').doElementFX('" . $triggerid . "',
'$jsAct->js_e_event', this)";
}
else
{
$js .= "Fabrik.getBlock('" . $jsControllerKey .
"').doElementFX('fabrik_trigger_" . $triggerid .
"', '$jsAct->js_e_event', this)";
}
$js .= "}";
$js = addslashes($js);
$js = str_replace('%%REGEX%%', $jsAct->js_e_value, $js);
$js = str_replace(array("\n", "\r"),
"", $js);
$jsStr .= $jsControllerKey .
".dispatchEvent('$element->plugin', '$elId',
'$jsAct->action', '$js');\n";
}
}
}
}
return $jsStr;
}
/**
* Get the default value for the list filter
*
* @param bool $normal is the filter a normal or advanced filter
* @param int $counter filter order
*
* @return string
*/
protected function getDefaultFilterVal($normal = true, $counter = 0)
{
$input = $this->app->getInput();
// Used for update col list plugin - we don't want a default value
filled
if ($input->get('fabrikIngoreDefaultFilterVal', false))
{
return '';
}
$listModel = $this->getListModel();
$filters = $listModel->getFilterArray();
// $$$ rob test for db join fields
//$elName = $this->getFilterFullName();
$elName = $this->getFullName(true, false);
$elid = $this->getElement()->id;
$f = InputFilter::getInstance();
$data = $f->clean($_REQUEST, 'array');
// See if the data is in the request array - can use
tablename___elementname=filterval in query string
$default = '';
if (array_key_exists($elName, $data))
{
if (is_array($data[$elName]))
{
$default = @$data[$elName]['value'];
}
}
$context = 'com_' . $this->package . '.list' .
$listModel->getRenderContext() . '.filter.' . $elid;
$context .= $normal ? '.normal' : '.advanced';
// We didn't find anything - lets check the filters
if ($default == '')
{
if (empty($filters))
{
return '';
}
if (array_key_exists('elementid', $filters))
{
/**
* $$$ hugh - if we have one or more pre-filters on the same element
that has a normal filter,
* the following line doesn't work. So in 'normal' mode
we need to get all the keys,
* and find the 'normal' one.
* $k = $normal == true ? array_search($elid,
$filters['elementid']) : $counter;
*/
$k = false;
if ($normal)
{
$keys = array_keys($filters['elementid'], $elid);
foreach ($keys as $key)
{
/**
* $$$ rob 05/09/2011 - just testing for 'normal' is not
enough as there are several search_types
* i.e. I've added a test for Querystring filters as without
that the search values were
* not being shown in ranged filter fields
*/
if (in_array($filters['search_type'][$key],
array('normal', 'querystring',
'jpluginfilters')))
{
$k = $key;
continue;
}
}
}
else
{
$k = $counter;
}
// Is there a filter with this elements name
if ($k !== false)
{
$searchType =
FArrayHelper::getValue($filters['search_type'], $k);
// Check element name is the same as the filter (could occur in
advanced search when swapping element type)
if ($searchType <> 'advanced' ||
$filters['key'][$k] ===
$input->getString('element'))
{
/**
* if its a search all filter don't use its value.
* if we did the next time the filter form is submitted its value is
turned
* from a search all filter into an element filter
*/
if (!is_null($searchType) && $searchType !=
'searchall')
{
if ($searchType != 'prefilter')
{
$default = FArrayHelper::getValue($filters['origvalue'],
$k);
}
}
}
}
}
}
$default = $this->app->getUserStateFromRequest($context, $elid,
$default);
$fType = $this->getElement()->filter_type;
if ($this->multiOptionFilter())
{
$default = (is_array($default) &&
array_key_exists('value', $default)) ?
$default['value'] : $default;
if (is_array($default))
{
// Hidden querystring filters can be using ranged valued though
if (!in_array($fType, array('hidden', 'checkbox',
'multiselect', 'range')))
{
// Weird thing on meow where when you first load the task list the id
element had a date range filter applied to it????
$default = '';
}
}
else
{
$default = stripslashes($default);
}
}
return $default;
}
/**
* Is the element filter type a multi-select
*
* @return boolean
*/
protected function multiOptionFilter()
{
$fType = $this->getElement()->filter_type;
return in_array($fType, array('range', 'checkbox',
'multiselect'));
}
/**
* If the search value isn't what is stored in the database, but
rather what the user
* sees then switch from the search string to the db value here
* overwritten in things like checkbox and radio plugins
*
* @param string $value FilterVal
*
* @return string
*/
protected function prepareFilterVal($value)
{
return $value;
}
/**
* Get the filter name
*
* @param int $counter Filter order
* @param bool $normal Do we render as a normal filter or as an
advanced search filter
*
* @return string
*/
protected function filterName($counter = 0, $normal = true)
{
$listModel = $this->getListModel();
$v = 'fabrik___filter[list_' .
$listModel->getRenderContext() . '][value]';
$v .= $normal ? '[' . $counter . ']' :
'[]';
return $v;
}
/**
* Get the list filter for the element
*
* @param int $counter Filter order
* @param bool $normal Do we render as a normal filter or as an
advanced search filter
* if normal include the hidden fields as well
(default true, use false for advanced filter
* rendering)
*
* @return string Filter html
*/
public function getFilter($counter = 0, $normal = true, $container =
'')
{
$listModel = $this->getListModel();
$formModel = $listModel->getFormModel();
$dbElName = $this->getFullName(false, false);
if (!$formModel->hasElement($dbElName))
{
return '';
}
$element = $this->getElement();
$elName = $this->getFullName(true, false);
$v = $this->filterName($counter, $normal);
// Correct default got
$default = $this->getDefaultFilterVal($normal,
$counter);
$this->filterDisplayValues = array($default);
$return = array();
if (in_array($element->filter_type, array('range',
'dropdown', 'checkbox', 'multiselect')))
{
$rows = $this->filterValueList($normal);
$this->unmergeFilterSplits($rows);
if (!in_array($element->filter_type, array('checkbox',
'multiselect')))
{
array_unshift($rows, HTMLHelper::_('select.option',
'', $this->filterSelectLabel()));
}
$this->getFilterDisplayValues($default, $rows);
foreach ($rows as &$r)
{
// translate
$r->text = Text::_($r->text);
// decode first, to decode all hex entities (like ')
$r->text = html_entity_decode($r->text, ENT_QUOTES | ENT_XML1,
'UTF-8');
// Encode if necessary
if (!in_array($element->get('filter_type'),
array('checkbox')))
{
/**
* Special case, pick out alt="foo" string if the text is an
img tag in a dropdown
*/
$m = [];
if ($element->get('filter_type') === 'dropdown'
&&
preg_match('/<img\s+.*?alt="(.*?)".*>/',
$r->text, $m))
{
$r->text = $m[1];
}
else
{
$r->text = strip_tags($r->text);
}
$r->text = htmlspecialchars($r->text, ENT_NOQUOTES,
'UTF-8', false);
}
}
}
switch ($element->filter_type)
{
case 'range':
$this->rangedFilterFields($default, $return, $rows, $v,
'list');
break;
case 'checkbox':
$return[] = $this->checkboxFilter($rows, $default, $v);
break;
case 'dropdown':
case 'multiselect':
$return[] = $this->selectFilter($rows, $default, $v);
break;
case 'field':
default:
$return[] = $this->singleFilter($default, $v);
break;
case 'hidden':
if (is_array($default))
{
$this->rangedFilterFields($default, $return, null, $v,
'hidden');
}
else
{
$return[] = $this->singleFilter($default, $v, 'hidden');
}
break;
case 'auto-complete':
$autoComplete = $this->autoCompleteFilter($default, $v, null,
$normal, $container);
$return = array_merge($return, $autoComplete);
break;
}
$return[] = $normal ? $this->getFilterHiddenFields($counter, $elName,
false, $normal) : $this->getAdvancedFilterHiddenFields();
return implode("\n", $return);
}
/**
* Build a select list filter
*
* @param $rows
* @param $default
* @param $v
*
* @return mixed
*/
protected function selectFilter($rows, $default, $v)
{
$class = $this->filterClass();
$element = $this->getElement();
$id = $this->getHTMLId() . 'value';
if ($element->filter_type === 'dropdown' ||
$element->filter_type === 'multiselect')
{
$advancedClass = $this->getAdvancedSelectClass();
$class .= !empty($advancedClass) ? ' ' . $advancedClass :
'';
}
$max = count($rows) < 7 ? count($rows) : 7;
$size = $element->filter_type === 'multiselect' ?
'multiple="multiple" size="' . $max .
'"' : 'size="1"';
$v = $element->filter_type === 'multiselect' ? $v .
'[]' : $v;
$data = 'data-filter-name="' . $this->getFullName(true,
false) . '"';
return HTMLHelper::_('select.genericlist', $rows, $v,
'class="form-select-sm ' . $class . '" ' .
$size . ' ' . $data, 'value', 'text',
$default, $id);
}
/**
* Get the labels for the filter values
*
* @param array|string $default
* @param array $rows
*
* @return array
*/
protected function getFilterDisplayValues($default, $rows)
{
$default = (array) $default;
$this->filterDisplayValues = array();
foreach ($rows as $row)
{
if (in_array($row->value, $default) && $row->value !=
'')
{
$this->filterDisplayValues[] = $row->text;
}
}
return $this->filterDisplayValues;
}
/**
* Get filter classes
*
* @since 3.1b
*
* @return string
*/
protected function filterClass()
{
$params = $this->getParams();
$classes = array('fabrik_filter');
$bootstrapClass =
trim($this->getBsClass($params->get('filter_class',
'col-sm-4')));
$classes[] = $bootstrapClass;
$classes[] = $params->get('filter_responsive_class',
'');
return implode(' ', $classes);
}
/**
* Checkbox filter
*
* @param array $rows Filter list options
* @param array $default Selected filter values
* @param string $v Filter name
*
* @since 3.0.7
*
* @return string Checkbox filter LayoutInterface HTML
*/
protected function checkboxFilter($rows, $default, $v)
{
$values = array();
$labels = array();
foreach ($rows as $row)
{
$values[] = $row->value;
$labels[] = $row->text;
}
$default = (array) $default;
$layout =
$this->getLayout('list-filter-checkbox');
$displayData = new stdClass;
$displayData->values = $values;
$displayData->labels = $labels;
$displayData->default = $default;
$displayData->elementName = $this->getFullName(true, false);
$displayData->name = $v;
$res = $layout->render($displayData);
// If no custom list layout found revert to the default
list.filter.fabrik-filter-checkbox renderer
if ($res === '')
{
//$basePath = COM_FABRIK_FRONTEND . '/layouts/';
//$layout = new
FileLayout('list.filter.fabrik-filter-checkbox', $basePath,
array('debug' => false, 'component' =>
'com_fabrik', 'client' => 'site'));
//$layout =
$this->getLayout('list.filter.fabrik-filter-checkbox');
$layout =
$this->getListModel()->getLayout('list.filter.fabrik-filter-checkbox');
$res = $layout->render($displayData);
}
return $res;
}
/**
* Build ranged filter fields either as two drop-downs or two hidden
fields
*
* @param array $default Filter values
* @param array &$return HTML to return
* @param array $rows Filter list options
* @param string $v Filter name
* @param string $type Show ranged values as a list or hidden
*
* @since 3.0.7
*
* @return void
*/
protected function rangedFilterFields($default, &$return, $rows, $v,
$type = 'list')
{
$element = $this->getElement();
$class = $this->filterClass();
$attributes = 'class="' . $class . '"
size="1" ';
$attributes .= 'data-filter-name="' .
$this->getFullName(true, false) . '"';
$default = (array) $default;
if (count($default) === 1)
{
$default[1] = '';
}
$def0 = array_key_exists('value', $default) ?
$default['value'][0] : $default[0];
$def1 = array_key_exists('value', $default) ?
$default['value'][1] : $default[1];
if ($type === 'list')
{
$return[] = '<span
class="fabrikFilterRangeLabel">' .
Text::_('COM_FABRIK_BETWEEN') . '</span>';
$return[] = HTMLHelper::_('select.genericlist', $rows, $v .
'[0]', $attributes, 'value', 'text', $def0,
$element->name . '_filter_range_0');
$return[] = '<br />';
$return[] = '<span
class="fabrikFilterRangeLabel">' .
Text::_('COM_FABRIK_AND') . '</span>';
$return[] = HTMLHelper::_('select.genericlist', $rows, $v .
'[1]', $attributes, 'value', 'text', $def1,
$element->name . '_filter_range_1');
}
else
{
$return[] = '<input type="hidden"
data-filter-name="' . $this->getFullName(true, false) .
'" class="' .
$class . '" name="' . $v . '[0]"
value="' . $def0 . '" id="' .
$element->name . '_filter_range_0" />';
$return[] = '<input type="hidden"
data-filter-name="' . $this->getFullName(true, false) .
'" class="' .
$class . '" name="' . $v . '[1]"
value="' . $def1 . '" id="' .
$element->name . '_filter_range_1" />';
}
}
/**
* Create a input type text/hidden filter
*
* @param string $default Value
* @param string $v Filter name
* @param string $type Type: 'text' or 'hidden'
*
* @return string filter
*/
protected function singleFilter($default, $v, $type = 'text')
{
// $$$ hugh - for "reasons", sometimes it's an array with
one value. No clue why. Sod it.
if (is_array($default))
{
$default = array_shift($default);
}
// $$$ rob - if searching on "O'Fallon" from querystring
filter the string has slashes added regardless
$default = (string) $default;
$default = stripslashes($default);
$default = htmlspecialchars($default);
$size = (int) $this->getParams()->get('filter_length',
20);
$class = $this->filterClass();
$id = $this->getHTMLId() . 'value';
return '<input type="' . $type . '"
data-filter-name="' . $this->getFullName(true, false) .
'" name="' . $v . '" class="' .
$class . '" size="' . $size . '"
value="' . $default . '" id="'
. $id . '" />';
}
/**
* Build the HTML ////for the auto-complete filter
*
* @param string $default Label
* @param string $v Field name
* @param string $labelValue Label value
* @param bool $normal Do we render as a normal filter or as an
advanced search filter
* if normal include the hidden fields as well
(default true, use false for advanced
* filter rendering)
*
* @return array HTML bits
*/
protected function autoCompleteFilter($default, $v, $labelValue = null,
$normal = true, $container = null)
{
if (is_null($labelValue))
{
$labelValue = $default;
}
$listModel = $this->getListModel();
$default = stripslashes($default);
$default = htmlspecialchars($default);
$id = $this->getHTMLId() . 'value';
$class = $this->filterClass();
$size = (int)
$this->getParams()->get('filter_length', 20);
/**
* $$$ rob 28/10/2011 using selector rather than element id so we can
have n modules with the same filters
* showing and not produce invalid html & duplicate js calls
*/
$return = array();
$return[] = '<input type="hidden" "
data-filter-name="' . $this->getFullName(true, false) .
'" name="' . $v . '" class="' .
$class . ' ' . $id . '" value="' . $default .
'" />';
$return[] = '<input type="text"
name="auto-complete' . $this->getElement()->id .
'" class="' . $class . ' autocomplete-trigger
'
. $id . '-auto-complete" size="' . $size .
'" value="' . $labelValue . '" />';
$opts = array();
if ($normal)
{
$opts['menuclass'] = 'auto-complete-container';
if (empty($container))
{
$container = 'listform_' . $listModel->getRenderContext();
}
$selector = '#' . $container . ' .' . $id;
}
else
{
$selector = '.advancedSearch_' .
$listModel->getRenderContext() . ' .' . $id;
$opts['menuclass'] = 'auto-complete-container
advanced';
}
$element = $this->getElement();
$formId = $this->getFormModel()->getId();
FabrikHelperHTML::autoComplete($selector, $element->id, $formId,
$element->plugin, $opts);
return $return;
}
/**
* Get drop-down filter select label
*
* @return string
*/
protected function filterSelectLabel()
{
$params = $this->getParams();
return $params->get('filter_required') == 1 ?
Text::_('COM_FABRIK_PLEASE_SELECT') :
Text::_('COM_FABRIK_FILTER_PLEASE_SELECT');
}
/**
* Checks if filter option values are in json format
* if so explode those values into new options
*
* @param array &$rows Filter options
*
* @return null
*/
protected function unmergeFilterSplits(&$rows)
{
/*
* takes rows which may be in format :
*
* [0] => stdClass Object
(
[text] => ["1"]
[value] => ["1"]
)
and converts them into
[0] => CMSObject Object
(
[_errors:protected] => Array
(
)
[value] => 1
[text] => 1
[disable] =>
)
*/
$allValues = array();
foreach ($rows as $row)
{
$allValues[] = $row->value;
}
$c = count($rows) - 1;
for ($j = $c; $j >= 0; $j--)
{
$vals = FabrikWorker::JSONtoData($rows[$j]->value, true);
$txt = FabrikWorker::JSONtoData($rows[$j]->text, true);
if (is_array($vals))
{
for ($i = 0; $i < count($vals); $i++)
{
$vals2 = FabrikWorker::JSONtoData($vals[$i], true);
$txt2 = FabrikWorker::JSONtoData(FArrayHelper::getValue($txt, $i),
true);
for ($jj = 0; $jj < count($vals2); $jj++)
{
if (!in_array($vals2[$jj], $allValues))
{
$allValues[] = $vals2[$jj];
$rows[] = HTMLHelper::_('select.option', $vals2[$jj],
$txt2[$jj]);
}
}
}
if (FabrikWorker::isJSON($rows[$j]->value, false))
{
// $$$ rob 01/10/2012 - if not unset then you could get json values in
standard dd filter (checkbox)
unset($rows[$j]);
}
}
if (count($vals) > 1)
{
unset($rows[$j]);
}
}
}
/**
* Run after unmergeFilterSplits to ensure filter dropdown labels are
correct
*
* @param array &$rows filter options
*
* @return null
*/
protected function reapplyFilterLabels(&$rows)
{
$values = $this->getSubOptionValues();
$labels = $this->getSubOptionLabels();
foreach ($rows as &$row)
{
$k = array_search($row->value, $values);
if ($k !== false)
{
$row->text = $labels[$k];
}
}
$rows = array_values($rows);
}
/**
* Get sub option values
*
* @param array $data Form data. If submitting a form, we want to use
that form's data and not
* re-query the form Model for its data as with
multiple plugins of the same type
* this was getting the plugin params out of sync.
*
* @return array
*/
protected function getSubOptionValues($data = array())
{
$phpOpts = $this->getPhpOptions($data);
if (!$phpOpts)
{
// cache, as even just fetching the params can eat up time in list with
lots of rows
if (!isset($this->subOptionValues))
{
$params = $this->getParams();
$opts = $params->get('sub_options', '');
$opts = $opts == '' ? array() : (array)
@$opts->sub_values;
$this->subOptionValues = $opts;
}
return $this->subOptionValues;
}
else
{
/**
* Paul - According to tooltip, $phpOpts should be of form
"array(JHTML: :_('select.option', '1',
'one'))"
* This is an array of objects with properties text and value.
* If user has mis-specified this we should tell them.
*
* @FIXME - $$$ hugh - seems like an empty array should be valid as
well?
**/
if (!is_array($phpOpts) || !$phpOpts[0] || !is_object($phpOpts[0]) ||
!isset($phpOpts[0]->value) || !isset($phpOpts[0]->text))
{
FabrikWorker::logError(sprintf(Text::_('COM_FABRIK_ELEMENT_SUBOPTION_ERROR'),
$this->element->name, var_export($phpOpts, true)),
'error');
return array();
}
$opts = array();
foreach ($phpOpts as $phpOpt)
{
$opts[] = $phpOpt->value;
}
}
return $opts;
}
/**
* Get sub option labels
*
* @param array $data Form data. If submitting a form, we want to use
that form's data and not
* re-query the form Model for its data as with
multiple plugins of the same type
* this was getting the plugin params out of sync.
*
* @return array
*/
protected function getSubOptionLabels($data = array())
{
$phpOpts = $this->getPhpOptions($data);
if (!$phpOpts)
{
// cache, as running Text::() can eat up time with lots of options
if (!isset($this->subOptionLabels))
{
$params = $this->getParams();
$opts = $params->get('sub_options', '');
$opts = $opts == '' ? array() : (array)
@$opts->sub_labels;
foreach ($opts as &$opt)
{
$opt = Text::_($opt);
}
$this->subOptionLabels = $opts;
}
return $this->subOptionLabels;
}
else
{
/**
* Paul - According to tooltip, $phpOpts should be of form
"array(HTMLHelper::_('select.option', '1',
'one'))"
* This is an array of objects with properties text and value.
* If user has mis-specified this we should tell them.
*
* @FIXME - $$$ hugh - seems like an empty array should be valid as
well?
**/
if (!is_array($phpOpts) || !$phpOpts[0] || !is_object($phpOpts[0]) ||
!isset($phpOpts[0]->value) || !isset($phpOpts[0]->text))
{
FabrikWorker::logError(sprintf(Text::_('COM_FABRIK_ELEMENT_SUBOPTION_ERROR'),
$this->element->name, var_export($phpOpts, true)),
'error');
return array();
}
$opts = array();
foreach ($phpOpts as $phpOpt)
{
$opts[] = $phpOpt->text;
}
}
foreach ($opts as &$opt)
{
$opt = Text::_($opt);
}
return $opts;
}
/**
* Get sub option enabled/disabled state
*
* @return array
*/
protected function getSubOptionEnDis()
{
$opts = array();
$phpOpts = $this->getPhpOptions();
if ($phpOpts)
{
foreach ($phpOpts as $phpOpt)
{
$opts[] = isset($phpOpt->disable) ? $phpOpt->disable : false;
}
}
return $opts;
}
/**
* Should we get the elements sub options via the use of eval'd
parameter setting
*
* @param array $data Form data. If submitting a form, we want to use
that form's data and not
* re-query the form Model for its data as with
multiple plugins of the same type
* this was getting the plugin params out of sync.
*
* @since 3.0.7
*
* @return mixed false if no, otherwise needs to return array of
HTMLHelperoptions
*/
protected function getPhpOptions($data = array())
{
$params = $this->getParams();
$pop = $params->get('dropdown_populate', '');
if ($pop !== '')
{
$w = new FabrikWorker;
//$data = empty($data) ? $this->getFormModel()->getData() : $data;
$pop = $w->parseMessageForPlaceHolder($pop, $data, false);
$key = md5($pop) . '-' . md5(serialize($data));
if (isset($this->phpOptions[$key]))
{
return $this->phpOptions[$key];
}
FabrikWorker::clearEval();
$res = Php::Eval(['code' => $pop, 'vars' =>
["data" => $data]]);
FabrikWorker::logEval($res, 'Eval exception : ' .
$this->element->name . '::getPhpOptions() : ' . $pop .
' : %s');
$this->phpOptions[$key] = $res;
return $res;
}
return false;
}
/**
* Get the radio buttons possible values
* needed for inline edit list plugin
*
* @return array of radio button values
*/
public function getOptionValues()
{
return $this->getSubOptionValues();
}
/**
* get the radio buttons possible labels
* needed for inline edit list plugin
*
* @return array of radio button labels
*/
protected function getOptionLabels()
{
return $this->getSubOptionLabels();
}
/**
* Get the filter build method - all (2) or recorded data (1)
*
* @since 3.0.7
*
* @return int
*/
protected function getFilterBuildMethod()
{
$usersConfig = ComponentHelper::getParams('com_fabrik');
$params = $this->getParams();
$filterBuild = $params->get('filter_build_method', 0);
if ($filterBuild == 0)
{
$filterBuild = $usersConfig->get('filter_build_method');
}
return $filterBuild;
}
/**
* Used by radio and drop-down elements to get a drop-down list of their
unique
* unique values OR all options - based on filter_build_method
*
* @param bool $normal do we render as a normal filter or as an
advanced search filter
* @param string $tableName table name to use - defaults to
element's current table
* @param string $label field to use, defaults to element name
* @param string $id field to use, defaults to element name
* @param bool $incJoin include join
*
* @return array text/value objects
*/
public function filterValueList($normal, $tableName = '', $label
= '', $id = '', $incJoin = true)
{
$filterBuild = $this->getFilterBuildMethod();
if ($filterBuild == 2 && $this->hasSubElements)
{
return $this->filterValueList_All($normal, $tableName, $label, $id,
$incJoin);
}
else
{
return $this->filterValueList_Exact($normal, $tableName, $label, $id,
$incJoin);
}
}
/**
* If filterValueList_Exact incjoin value = false, then this method is
called
* to ensure that the query produced in filterValueList_Exact contains at
least the database join element's
* join
*
* @return string required join text to ensure exact filter list code
produces a valid query.
*/
protected function buildFilterJoin()
{
return '';
}
/**
* Create an array of label/values which will be used to populate the
elements filter dropdown
* returns only data found in the table you are filtering on
*
* @param bool $normal do we render as a normal filter or as an
advanced search filter
* @param string $tableName table name to use - defaults to
element's current table
* @param string $label field to use, defaults to element name
* @param string $id field to use, defaults to element name
* @param bool $incJoin include join
*
* @throws ErrorException
*
* @return array filter value and labels
*/
protected function filterValueList_Exact($normal, $tableName =
'', $label = '', $id = '', $incJoin = true)
{
$listModel = $this->getListModel();
$fbConfig = ComponentHelper::getParams('com_fabrik');
$fabrikDb = $listModel->getDb();
$table = $listModel->getTable();
$element = $this->getElement();
$origTable = $table->db_table_name;
$elName = $this->getFullName(true, false);
$elName2 = $this->getFullName(false, false);
if (!$this->isJoin())
{
$ids = $listModel->getColumnData($elName2);
// For ids that are text with apostrophes in
for ($x = count($ids) - 1; $x >= 0; $x--)
{
if ($ids[$x] == '')
{
unset($ids[$x]);
}
else
{
$ids[$x] = addslashes($ids[$x]);
}
}
}
$incJoin = $this->isJoin() ? false : $incJoin;
/**
* filter the drop downs lists if the table_view_own_details option is on
* other wise the lists contain data the user should not be able to see
* note, this should now use the prefilter data to filter the list
*/
// Check if the elements group id is on of the table join groups if it is
then we swap over the table name
$fromTable = $this->isJoin() ?
$this->getJoinModel()->getJoin()->table_join : $origTable;
$joinStr = $incJoin ? $listModel->buildQueryJoin() :
$this->buildFilterJoin();
// New option not to order elements - required if you want to use db
joins 'Joins where and/or order by statement'
$groupBy = $this->getOrderBy('filter');
foreach ($listModel->getJoins() as $aJoin)
{
// Not sure why the group id key wasn't found - but put here to
remove error
if (property_exists($aJoin, 'group_id'))
{
if ($aJoin->group_id == $element->group_id &&
$aJoin->element_id == 0)
{
$fromTable = $aJoin->table_join;
$elName = preg_replace('/^' . $origTable .
'\./', $fromTable . '.', $elName2);
}
}
}
$elName = FabrikString::safeColName($elName);
if ($label == '')
{
$label = $this->isJoin() ? $this->getElement()->name : $elName;
}
if ($id == '')
{
$id = $this->isJoin() ? 'id' : $elName;
}
if ($this->encryptMe())
{
$secret = $this->config->getValue('secret');
$label = 'AES_DECRYPT(' . $label . ', ' .
$fabrikDb->quote($secret) . ')';
$id = 'AES_DECRYPT(' . $id . ', ' .
$fabrikDb->quote($secret) . ')';
}
$origTable = $tableName == '' ? $origTable : $tableName;
/**
* $$$ rob - 2nd sql was blowing up for me on my test table - why did we
change to it?
*
http://localhost/fabrik2.0.x/index.php?option=com_fabrik&view=table&listid=12&calculations=0&resetfilters=0&Itemid=255&lang=en
* so added test for initial fromtable in join str and if found use
origtable
*/
if (strstr($joinStr, 'JOIN ' . $fabrikDb->qn($fromTable)))
{
$sql = 'SELECT DISTINCT(' . $label . ') AS ' .
$fabrikDb->qn('text') . ', ' . $id . ' AS
' . $fabrikDb->qn('value')
. ' FROM ' . $fabrikDb->qn($origTable) . ' ' .
$joinStr . "\n";
}
else
{
$sql = 'SELECT DISTINCT(' . $label . ') AS ' .
$fabrikDb->qn('text') . ', ' . $id . ' AS
' . $fabrikDb->qn('value')
. ' FROM ' . $fabrikDb->qn($fromTable) . ' ' .
$joinStr . "\n";
}
if (!$this->isJoin())
{
$sql .= 'WHERE ' . $id . ' IN (\'' .
implode("','", $ids) . '\')';
}
// Apply element where/order by statements to the filter (e.g. dbjoins
'Joins where and/or order by statement')
$elementWhere = $this->buildQueryWhere(array(), true, null,
array('mode' => 'filter'));
if (StringHelper::stristr($sql, 'WHERE ') &&
StringHelper::stristr($elementWhere??'', 'WHERE '))
{
// $$$ hugh - only replace the WHERE with AND if it's the first
word, so we don't munge sub-queries
// $elementWhere = StringHelper::str_ireplace('WHERE ',
'AND ', $elementWhere);
$elementWhere = preg_replace("#^(\s*)(WHERE)(.*)#i",
"$1AND$3", $elementWhere);
}
else if (StringHelper::stristr($sql, 'WHERE ') &&
!empty($elementWhere) && !StringHelper::stristr($elementWhere,
'WHERE '))
{
// if we have a WHERE in the main query, and the element clause
isn't empty but doesn't start with WHERE ...
$elementWhere = 'AND ' . $elementWhere;
}
$sql .= ' ' . $elementWhere;
$sql .= "\n" . $groupBy;
$sql = $listModel->pluginQuery($sql);
$fabrikDb->setQuery($sql, 0,
$fbConfig->get('filter_list_max', 100));
FabrikHelperHTML::debug((string) $fabrikDb->getQuery(), 'element
filterValueList_Exact:');
try
{
$rows = $fabrikDb->loadObjectList();
} catch (RuntimeException $e)
{
throw new ErrorException('filter query error: ' .
$this->getElement()->name . ' ' .
$fabrikDb->getErrorMsg(), 500);
}
return $rows;
}
/**
* Get a readonly value for a filter, uses getROElement() to ascertain
value, adds between x & y if ranged values
*
* @param mixed $data String or array of filter value(s)
*
* @since 3.0.7
*
* @return string
*/
public function getFilterRO($data)
{
if (in_array($this->getFilterType(), array('range',
'range-hidden')))
{
$return = array();
foreach ($data as $d)
{
$return[] = $this->getROElement($d);
}
return Text::_('COM_FABRIK_BETWEEN') . '<br
/>' . implode('<br />' .
Text::_('COM_FABRIK_AND') . "<br />", $return);
}
return $this->getROElement($data);
}
/**
* Get options order by
*
* @param string $view View mode '' or
'filter'
* @param JDatabaseQuery|bool $query Set to false to return a string
*
* @return string order by statement
*/
protected function getOrderBy($view = '', $query = false)
{
if (isset($this->orderBy))
{
return $this->orderBy;
}
else
{
return "ORDER BY text ASC ";
}
}
/**
* Create an array of label/values which will be used to populate the
elements filter dropdown
* returns all possible options
*
* @param bool $normal do we render as a normal filter or as an
advanced search filter
* @param string $tableName table name to use - defaults to
element's current table
* @param string $label field to use, defaults to element name
* @param string $id field to use, defaults to element name
* @param bool $incJoin include join
*
* @return array filter value and labels
*/
protected function filterValueList_All($normal, $tableName = '',
$label = '', $id = '', $incJoin = true)
{
$values = $this->getSubOptionValues();
$labels = $this->getSubOptionLabels();
$return = array();
for ($i = 0; $i < count($values); $i++)
{
$return[] = HTMLHelper::_('select.option', $values[$i],
$labels[$i]);
}
return $return;
}
/**
* Get the hidden fields for a normal filter
*
* @param int $counter Filter counter
* @param string $elName Full element name will be converted to
tablename.elementname format
* @param bool $hidden Has the filter been added due to a search form
value with no corresponding filter set
* up in the table if it has we need to know so
that when we do a search from a
* 'fabrik_list_filter_all' field that
search term takes precedence
* @param bool $normal do we render as a normal filter or as an
advanced search filter
*
*
* @return string html Hidden fields
*/
protected function getFilterHiddenFields($counter, $elName, $hidden =
false, $normal = true)
{
$params = $this->getParams();
$element = $this->getElement();
$class = $this->filterClass();
$filters = $this->getListModel()->getFilterArray();
// $$$ needs to apply to CDD's as well, so just making this an
override-able method.
if ($this->quoteLabel())
{
$elName = FabrikString::safeColName($elName);
}
// If querying via the querystring - then the condition and eval should
be looked up against that key
$elementIds = FArrayHelper::getValue($filters, 'elementid',
array());
// Check that there is an element filter for this element in the element
ids.
$filterIndex = array_search($this->getId(), $elementIds);
$hidden = $hidden ? 1 : 0;
$match = $this->isExactMatch(array('match' =>
$element->filter_exact_match));
$return = array();
$eval = FArrayHelper::getValue($filters, 'eval', array());
$joins = FArrayHelper::getValue($filters, 'join', array());
$condition = FArrayHelper::getValue($filters, 'condition',
array());
$groupedTo = FArrayHelper::getValue($filters,
'grouped_to_previous', array());
/*
* Element filter not found (could be a prefilter instead) so use element
default options
* see
http://fabrikar.com/forums/index.php?threads/major-filter-issues.37360/
*/
if ($filterIndex === false || $normal)
{
$condition = $this->getFilterCondition();
$eval = FABRIKFILTER_TEXT;
}
else
{
$condition = FArrayHelper::getValue($condition, $filterIndex,
$this->getFilterCondition());
$eval = FArrayHelper::getValue($eval, $filterIndex,
FABRIKFILTER_TEXT);
}
$searchTypes = FArrayHelper::getValue($filters, 'search_type',
array());
$searchType = FArrayHelper::getValue($searchTypes, $filterIndex,
'normal');
// If our previous filter was a pre-filter we never want to use its join
value as it could result in the pre-filter being ignored
// Thus in this instance we should always use 'AND'
$join = $searchType === 'prefilter' ?
'AND' : FArrayHelper::getValue($joins, $filterIndex,
'AND');
$groupedToPrevious = FArrayHelper::getValue($groupedTo, $filterIndex,
'0');
// Need to include class other wise csv export produces incorrect results
when exporting
$prefix = '<input type="hidden" class="' .
$class . '" name="fabrik___filter[list_' .
$this->getListModel()->getRenderContext() . ']';
$return[] = $prefix . '[condition][' . $counter . ']"
value="' . $condition . '" />';
$return[] = $prefix . '[join][' . $counter . ']"
value="' . $join . '" />';
$return[] = $prefix . '[key][' . $counter . ']"
value="' . $elName . '" />';
$return[] = $prefix . '[search_type][' . $counter .
']" value="normal" />';
$return[] = $prefix . '[match][' . $counter . ']"
value="' . $match . '" />';
$return[] = $prefix . '[full_words_only][' . $counter .
']" value="' .
$params->get('full_words_only', '0') . '"
/>';
$return[] = $prefix . '[eval][' . $counter . ']"
value="' . $eval . '" />';
$return[] = $prefix . '[grouped_to_previous][' . $counter .
']" value="' . $groupedToPrevious . '"
/>';
$return[] = $prefix . '[hidden][' . $counter . ']"
value="' . $hidden . '" />';
$return[] = $prefix . '[elementid][' . $counter . ']"
value="' . $element->id . '" />';
return implode("\n", $return);
}
/**
* Get the condition statement to use in the filters hidden field
*
* @return string =, begins or contains
*/
protected function getFilterCondition()
{
if ($this->getElement()->filter_type == 'auto-complete')
{
$cond = 'contains';
}
else
{
$match = $this->isExactMatch(array('match' =>
$this->getElement()->filter_exact_match));
$cond = ($match == 1) ? '=' : 'contains';
}
return $cond;
}
/**
* Get the filter type: the element filter_type property unless a ranged
querystring is used
*
* @since 3.0.7
*
* @return string
*/
protected function getFilterType()
{
$element = $this->getElement();
$type = $element->filter_type;
$name = $this->getFullName(true, false);
$qsFilter = $this->app->getInput()->get($name, array(),
'array');
$qsValues = FArrayHelper::getValue($qsFilter, 'value',
array());
if (is_array($qsValues) && count($qsValues) > 1)
{
$type = $type === 'hidden' ? 'range-hidden' :
'range';
}
return $type;
}
/**
* Get the hidden fields for an advanced filter
*
* @return string html hidden fields
*/
protected function getAdvancedFilterHiddenFields()
{
$element = $this->getElement();
$return = array();
$prefix = '<input type="hidden"
name="fabrik___filter[list_' .
$this->getListModel()->getRenderContext() . ']';
$return[] = $prefix . '[elementid][]" value="' .
$element->id . '" />';
return implode("\n", $return);
}
/**
* This builds an array containing the filters value and condition
* when using a ranged search
*
* @param array $value Initial values
* @param string $condition Filter condition e.g. BETWEEN
*
* @return array (value condition)
*/
protected function getRangedFilterValue($value, $condition = '')
{
$db = FabrikWorker::getDbo();
$element = $this->getElement();
if ($element->filter_type === 'range' ||
strtoupper($condition) === 'BETWEEN')
{
if (is_numeric($value[0]) && is_numeric($value[1]))
{
$value = $value[0] . ' AND ' . $value[1];
}
else
{
$value = $db->q($value[0]) . ' AND ' .
$db->q($value[1]);
}
$condition = 'BETWEEN';
}
else
{
if (is_array($value) && !empty($value))
{
foreach ($value as &$v)
{
$v = $db->q($v);
}
$value = ' (' . implode(',', $value) .
')';
}
$condition = 'IN';
}
return array($value, $condition);
}
/**
* Escapes a SINGLE query search string
*
* @param string $condition filter condition
* @param value &$value value to escape
*
* @since 3.0.7
*
* @return null
*/
private function escapeOneQueryValue($condition, &$value)
{
if ($condition == 'REGEXP')
{
$value = preg_quote($value);
}
/**
* If doing a search via a querystring for O'Fallon then the '
is backslashed
* in FabrikModelListfilter::getQuerystringFilters()
* but the MySQL regexp needs it to be back-quoted three times
*/
// If searching on '\' then don't double up \'s
if (strlen(str_replace('\\', '', $value)) > 0)
{
$value = str_replace("\\", "\\\\\\", $value);
// $$$rob check things haven't been double quoted twice (occurs now
that we are doing preg_quote() above to fix searches on '*'
$value = str_replace("\\\\\\\\\\\\", "\\\\\\",
$value);
}
}
/**
* Escapes the a query search string
*
* @param string $condition filter condition
* @param value &$value value to escape
*
* @return null
*/
private function escapeQueryValue($condition, &$value)
{
// $$$ rob 30/06/2011 only escape once !
if ($this->escapedQueryValue)
{
return;
}
$this->escapedQueryValue = true;
if (is_array($value))
{
foreach ($value as &$val)
{
$this->escapeOneQueryValue($condition, $val);
}
}
else
{
$this->escapeOneQueryValue($condition, $value);
}
}
/**
* Builds an array containing the filters value and condition
*
* @param string $value Initial value
* @param string $condition Initial condition e.g. LIKE, =
* @param string $eval How the value should be handled
*
* @return array (value condition)
*/
public function getFilterValue($value, $condition, $eval)
{
$condition = StringHelper::strtolower($condition);
$this->escapeQueryValue($condition, $value);
$db = FabrikWorker::getDbo();
if (is_array($value))
{
// Ranged search
list($value, $condition) = $this->getRangedFilterValue($value,
$condition);
}
else
{
switch ($condition)
{
case 'notequals':
case '<>':
$condition = "<>";
// 2 = sub-query so don't quote
$value = ($eval == FABRIKFILTER_QUERY) ? '(' . $value .
')' : $db->q($value);
break;
case 'equals':
case '=':
$condition = "=";
$value = ($eval == FABRIKFILTER_QUERY) ? '(' . $value .
')' : $db->q($value);
break;
case 'begins':
case 'begins with':
$condition = "LIKE";
$value = $eval == FABRIKFILTER_QUERY ? '(' . $value .
')' : $db->q($value . '%');
break;
case 'ends':
case 'ends with':
// @TODO test this with subquery
$condition = "LIKE";
$value = $eval == FABRIKFILTER_QUERY ? '(' . $value .
')' : $db->q('%' . $value);
break;
case 'contains':
case 'like':
// @TODO test this with subquery
$condition = "LIKE";
//$value = $eval == FABRIKFILTER_QUERY ? '(' . $value .
')' : $db->q('%' . $value . '%');
// if they want NOQUOTES on a LIKE, assume they are building their own
CONCAT or whatever with %'s
switch ($eval)
{
case FABRIKFILTER_QUERY:
$value = '(' . $value . ')';
break;
case FABRIKFILTER_NOQUOTES:
$value = $value;
break;
default:
$value = $db->q('%' . $value . '%');
break;
}
break;
case '>':
case '>':
case 'greaterthan':
$condition = '>';
break;
case '<':
case '<':
case 'lessthan':
$condition = '<';
break;
case '>=':
case '>=':
case 'greaterthanequals':
$condition = '>=';
break;
case '<=':
case '<=':
case 'lessthanequals':
$condition = '<=';
break;
case 'in':
$condition = 'IN';
if ($eval != FABRIKFILTER_QUERY)
{
$value = FabrikString::safeQuote($value, true);
}
$value = '(' . $value . ')';
break;
case 'not_in':
$condition = 'NOT IN';
if ($eval != FABRIKFILTER_QUERY)
{
$value = FabrikString::safeQuote($value, true);
}
$value = '(' . $value . ')';
break;
}
switch ($condition)
{
case '>':
case '<':
case '>=':
case '<=':
if ($eval == FABRIKFILTER_QUERY)
{
$value = '(' . $value . ')';
}
else
{
if (!is_numeric($value))
{
$value = $db->q($value);
}
}
break;
}
// $$$ hugh - if 'noquotes' (3) selected, strip off the quotes
again!
if ($eval == FABRIKFILTER_NOQUOTES)
{
// $$$ hugh - darn, this is stripping the ' of the end of things
like "select & from foo where bar = '123'"
$value = StringHelper::ltrim($value, "'");
$value = StringHelper::rtrim($value, "'");
}
if ($condition == '=' && $value ==
"'_null_'")
{
$condition = " IS NULL ";
$value = '';
}
}
return array($value, $condition);
}
/**
* Build the filter query for the given element.
* Can be overwritten in plugin - e.g. see checkbox element which checks
for partial matches
*
* @param string $key element name in format
`tablename`.`elementname`
* @param string $condition =/like etc.
* @param string $value search string - already quoted if
specified in filter array options
* @param string $originalValue original filter value without quotes or
%'s applied
* @param string $type filter type
advanced/normal/prefilter/search/querystring/sea* @
* @param string $filterEval eval the filter value
* @return string sql query part e,g, "key = value"
*/
public function getFilterQuery($key, $condition, $value, $originalValue,
$type = 'normal', $filterEval = '0')
{
$this->encryptFieldName($key);
switch ($condition)
{
case 'thisyear':
$query = ' YEAR(' . $key . ') = YEAR(NOW()) ';
break;
case 'earlierthisyear':
$query = ' (DAYOFYEAR(' . $key . ') <=
DAYOFYEAR(NOW()) AND YEAR(' . $key . ') = YEAR(NOW())) ';
break;
case 'laterthisyear':
$query = ' (DAYOFYEAR(' . $key . ') >=
DAYOFYEAR(NOW()) AND YEAR(' . $key . ') = YEAR(NOW())) ';
break;
case 'today':
$query = ' (' . $key . ' >= CURDATE() AND ' .
$key . ' < CURDATE() + INTERVAL 1 DAY) ';
break;
case 'yesterday':
$query = ' (' . $key . ' >= CURDATE() - INTERVAL 1
DAY AND ' . $key . ' < CURDATE()) ';
break;
case 'tomorrow':
$query = ' (' . $key . ' >= CURDATE() + INTERVAL 1
DAY AND ' . $key . ' < CURDATE() + INTERVAL 2 DAY ) ';
break;
case 'thismonth':
$query = ' (' . $key . ' >=
DATE_ADD(LAST_DAY(DATE_SUB(now(), INTERVAL 1 MONTH)), INTERVAL 1 DAY) AND
' . $key
. ' <= LAST_DAY(NOW()) ) ';
break;
case 'lastmonth':
$query = ' (' . $key . ' >=
DATE_ADD(LAST_DAY(DATE_SUB(now(), INTERVAL 2 MONTH)), INTERVAL 1 DAY) AND
' . $key
. ' <= LAST_DAY(DATE_SUB(NOW(), INTERVAL 1 MONTH)) ) ';
break;
case 'nextmonth':
$query = ' (' . $key . ' >= DATE_ADD(LAST_DAY(now()),
INTERVAL 1 DAY) AND ' . $key
. ' <= DATE_ADD(LAST_DAY(NOW()), INTERVAL 1 MONTH) ) ';
break;
case 'nextweek1':
$query = ' (YEARWEEK(' . $key . ',1) =
YEARWEEK(DATE_ADD(NOW(), INTERVAL 1 WEEK), 1))';
break;
case 'birthday':
$query = '(MONTH(' . $key . ') = MONTH(CURDATE()) AND
DAY(' . $key . ') = DAY(CURDATE())) ';
break;
default:
if ($this->isJoin())
{
// Query the joined table concatenating into one field
$joinTable = $this->getJoinModel()->getJoin()->table_join;
// Jaanus: joined group pk set in groupConcactJoinKey()
$pk = $this->groupConcactJoinKey();
$key = "(SELECT GROUP_CONCAT(id SEPARATOR '" .
GROUPSPLITTER . "') FROM $joinTable WHERE parent_id = $pk)";
$value = str_replace("'", '', $value);
$query = "($key = '$value' OR $key LIKE
'$value" . GROUPSPLITTER . "%' OR
$key LIKE '" . GROUPSPLITTER . "$value" .
GROUPSPLITTER . "%' OR
$key LIKE '%" . GROUPSPLITTER . "$value')";
}
else
{
$query = " $key $condition $value ";
}
break;
}
return $query;
}
/**
* Get the AES decrypt sql segment for the element
*
* @param string &$key field name
*
* @return void
*/
public function encryptFieldName(&$key)
{
if ($this->encryptMe())
{
$db = FabrikWorker::getDbo();
$secret = $this->config->get('secret');
$matches = array();
if (preg_match('/LOWER\((.*)\)/', $key, $matches))
{
$key = 'LOWER(CONVERT(AES_DECRYPT(' . $matches[1] . ',
' . $db->q($secret) . ') USING utf8))';
}
else
{
$key = 'AES_DECRYPT(' . $key . ', ' .
$db->q($secret) . ')';
}
}
}
/**
* If no filter condition supplied (either via querystring or in posted
filter data
* return the most appropriate filter option for the element.
*
* @return string default filter condition ('=',
'REGEXP' etc.)
*/
public function getDefaultFilterCondition()
{
$fieldDesc = $this->getFieldDescription();
if (StringHelper::stristr($fieldDesc, 'INT') ||
$this->getElement()->filter_exact_match == 1)
{
return '=';
}
return 'REGEXP';
}
/**
* $$$ rob testing not using this as elements can only be in one group
* $$$ hugh - still called from import.php
* when adding a new element this will ensure its added to all tables that
the
* elements group is associated with
*
* @param string $origColName original column name leave null to ignore
*
* @TODO Fabrik 3 - loadFromFormId() might need to pass in a package id
*
* @deprecated
*
* @return null
*/
public function addToDBTable($origColName = null)
{
}
/**
* called from admin element controller when element saved
*
* @param array $data posted element save data
*
* @return bool save ok or not
*/
public function onSave($data)
{
$params = $this->getParams();
if (!$this->canEncrypt() &&
$params->get('encrypt'))
{
throw new RuntimeException('The encryption option is only available
for field and text area plugins');
}
// Overridden in element plugin if needed
return true;
}
/**
* Called from admin element controller when element is removed
*
* @return bool save ok or not
*/
public function onRemove()
{
// Delete js actions
$db = FabrikWorker::getDbo(true);
$query = $db->getQuery(true);
$id = (int) $this->getElement()->id;
$query->delete()->from('#__fabrik_jsactions')->where('element_id
=' . $id);
$db->setQuery($query);
try
{
$db->execute();
} catch (Exception $e)
{
throw new RuntimeException('Didn\'t delete js actions for
element ' . $id);
}
return true;
}
/**
* States if the element contains data which is recorded in the database
* some elements (e.g. buttons) don't
*
* @param array $data posted data
*
* @return bool
*/
public function recordInDatabase($data = null)
{
return $this->getParams()->get('store_in_db',
$this->recordInDatabase);
}
/**
* Used by elements with sub-options, given a value, return its label
*
* @param string $v Value
* @param string $defaultLabel Default label
* @param bool $forceCheck Force check even if $v === $defaultLabel
*
* @return string Label
*/
public function getLabelForValue($v, $defaultLabel = null, $forceCheck =
false)
{
/**
* $$$ 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.
* $element = $this->getParent();
*/
$params = $this->getParams();
$values = $this->getSubOptionValues();
$labels = $this->getSubOptionLabels();
//F4: Don't use strict search, in F4 values may be integer or string
$key = array_search($v, $values, false);
/**
* $$$ rob if we allow adding to the dropdown but not recording
* then there will be no $key set to revert to the $val instead
*/
if ($v === $params->get('sub_default_value'))
{
$v = $params->get('sub_default_label');
}
return ($key === false) ? $v : FArrayHelper::getValue($labels, $key,
$defaultLabel);
}
/**
* Build the query for the avg calculation
*
* @param FabrikFEModelList &$listModel list model
* @param array $labels Labels
*
* @return string sql statement
*/
protected function getAvgQuery(&$listModel, $labels = array())
{
$label = count($labels) == 0 ? "'calc' AS label"
: 'CONCAT(' . implode(', " & " , ',
$labels) . ') AS label';
$item = $listModel->getTable();
$joinSQL = $listModel->buildQueryJoin();
$whereSQL = $listModel->buildQueryWhere();
$name = $this->getFullName(false, false);
$groupModel = $this->getGroup();
$roundTo = (int) $this->getParams()->get('avg_round');
if ($groupModel->isJoin())
{
// Element is in a joined column - lets presume the user wants to sum
all cols, rather than reducing down to the main cols totals
$sql = "SELECT ROUND(AVG($name), $roundTo) AS value, $label FROM
" . FabrikString::safeColName($item->db_table_name)
. " $joinSQL $whereSQL " .
$this->additionalElementCalcJoin('avg_split');
}
else
{
/*
* Need to do first query to get distinct records as if we are doing
left joins the sum is too large
* However, views may not have a primary key which is unique so set to
'' if no join
*/
$distinct = $this->getListModel()->isView() &&
trim($joinSQL) == '' ? '' : 'DISTINCT';
$sql = "SELECT ROUND(AVG(value), $roundTo) AS value, label
FROM (SELECT " . $distinct . " $item->db_primary_key, $name
AS value, $label FROM " .
FabrikString::safeColName($item->db_table_name)
. " $joinSQL $whereSQL " .
$this->additionalElementCalcJoin('avg_split') . ") AS
t";
}
return $sql;
}
/**
* Get sum query
*
* @param FabrikFEModelList &$listModel List model
* @param array $labels Label
*
* @return string
*/
protected function getSumQuery(&$listModel, $labels = array())
{
$label = count($labels) == 0 ? "'calc' AS label"
: 'CONCAT(' . implode(', " & " , ',
$labels) . ') AS label';
$item = $listModel->getTable();
$joinSQL = $listModel->buildQueryJoin();
$whereSQL = $listModel->buildQueryWhere();
$name = $this->getFullName(false, false);
$groupModel = $this->getGroup();
if ($groupModel->isJoin())
{
// Element is in a joined column - lets presume the user wants to sum
all cols, rather than reducing down to the main cols totals
$sql = "SELECT SUM($name) AS value, $label FROM " .
FabrikString::safeColName($item->db_table_name) . " $joinSQL
$whereSQL " .
$this->additionalElementCalcJoin('sum_split');
}
else
{
/*
* Need to do first query to get distinct records as if we are doing
left joins the sum is too large
* However, views may not have a primary key which is unique so set to
'' if no join
*/
$distinct = $this->getListModel()->isView() &&
trim($joinSQL) == '' ? '' : 'DISTINCT';
$sql = "SELECT SUM(value) AS value, label
FROM (SELECT " . $distinct . " $item->db_primary_key, $name
AS value, $label FROM " .
FabrikString::safeColName($item->db_table_name)
. " $joinSQL $whereSQL " .
$this->additionalElementCalcJoin('sum_split') . ") AS
t";
}
return $sql;
}
/**
* If split then the split element could require an additional join to get
the sum query to work
*
* @param string $splitParam Name of calculation split param. Loads up
split calculation element
*
* @return string Sql
*/
private function additionalElementCalcJoin($splitParam)
{
$sql = '';
$elementId = $this->getParams()->get($splitParam, null);
if (!is_null($elementId))
{
$pluginManager = FabrikWorker::getPluginManager();
$plugin = $pluginManager->getElementPlugin($elementId);
/**
* If the join table_join_alias is set, it has already been joined in
the buildQueryJoin
* so we don't need to add it (it'll blow up with a "Not
unique table/alias" if we do)
*/
$join = $plugin->getJoin();
if (!(isset($join->table_join_alias) &&
!empty($join->table_join_alias)))
{
$sql = ' ' . $plugin->buildFilterJoin();
}
}
return $sql;
}
/**
* Get a custom query
*
* @param FabrikFEModelList &$listModel list
* @param string $label label
*
* @return string
*/
protected function getCustomQuery(&$listModel, $label =
"'calc'")
{
$params = $this->getParams();
$custom_query = $params->get('custom_calc_query',
'');
$item = $listModel->getTable();
$joinSQL = $listModel->buildQueryJoin();
$whereSQL = $listModel->buildQueryWhere();
$name = $this->getFullName(false, false);
$groupModel = $this->getGroup();
if ($groupModel->isJoin())
{
// Element is in a joined column - lets presume the user wants to sum
all cols, rather than reducing down to the main cols totals
// $custom_query = sprintf($custom_query, $name);
$custom_query = str_replace('%s', $name, $custom_query);
return "SELECT $custom_query AS value, $label AS label FROM "
. FabrikString::safeColName($item->db_table_name) . " $joinSQL
$whereSQL";
}
else
{
// Need to do first query to get distinct records as if we are doing
left joins the sum is too large
// $custom_query = sprintf($custom_query, 'value');
$custom_query = str_replace('%s', 'value',
$custom_query);
return 'SELECT ' . $custom_query . ' AS value, label FROM
(SELECT DISTINCT ' .
FabrikString::safeColName($item->db_table_name)
. '.*, ' . $name . ' AS value, ' . $label . '
AS label FROM ' . FabrikString::safeColName($item->db_table_name)
. ' ' . $joinSQL . ' ' . $whereSQL . ') AS
t';
}
}
/**
* Get a query for our median query
*
* @param FabrikFEModelList &$listModel List
* @param array $labels Label
*
* @return string
*/
protected function getMedianQuery(&$listModel, $labels = array())
{
$label = count($labels) == 0 ? "'calc' AS label" :
'CONCAT(' . implode(', " & " , ',
$labels) . ') AS label';
$item = $listModel->getTable();
$joinSQL = $listModel->buildQueryJoin();
$whereSQL = $listModel->buildQueryWhere();
$sql = 'SELECT ' . $this->getFullName(false, false) . '
AS value, ' . $label . ' FROM ' .
FabrikString::safeColName($item->db_table_name)
. ' ' . $joinSQL . ' ' . $whereSQL . ' ' .
$this->additionalElementCalcJoin('median_split');
return $sql;
}
/**
* Get a query for our count method
*
* @param FabrikFEModelList &$listModel List
* @param array $labels Labels
*
* @return string
*/
protected function getCountQuery(&$listModel, $labels = array())
{
$label = count($labels) == 0 ? "'calc' AS label" :
'CONCAT(' . implode(', " & " , ',
$labels) . ') AS label';
$db = FabrikWorker::getDbo();
$item = $listModel->getTable();
$joinSQL = $listModel->buildQueryJoin();
$whereSQL = $listModel->buildQueryWhere();
$name = $this->getFullName(false, false);
// $$$ hugh - need to account for 'count value' here!
$params = $this->getParams();
$count_condition = $params->get('count_condition',
'');
if (!empty($count_condition))
{
if (!empty($whereSQL))
{
$whereSQL .= " AND $name = " . $db->q($count_condition);
}
else
{
$whereSQL = "WHERE $name = " . $db->q($count_condition);
}
}
$groupModel = $this->getGroup();
if ($groupModel->isJoin())
{
// Element is in a joined column - lets presume the user wants to sum
all cols, rather than reducing down to the main cols totals
$sql = "SELECT COUNT($name) AS value, $label FROM " .
FabrikString::safeColName($item->db_table_name) . " $joinSQL
$whereSQL " .
$this->additionalElementCalcJoin('count_split');
}
else
{
// Need to do first query to get distinct records as if we are doing
left joins the sum is too large
$sql = "SELECT COUNT(value) AS value, label
FROM (SELECT DISTINCT $item->db_primary_key, $name AS value, $label
FROM " . FabrikString::safeColName($item->db_table_name)
. " $joinSQL $whereSQL " .
$this->additionalElementCalcJoin('count_split') . ") AS
t";
}
return $sql;
}
/**
* Work out the calculation group-bys to apply:
*
* - If group_by is assigned in the app input
* - If no group_by request then check the list models group by and add
that
*
* @param string $splitParam Element parameter name containing the
calculation split option
* @param object $listModel List model
*
* @since 3.0.8
*
* @return array (Group by element names, group by element labels)
*/
protected function calcGroupBys($splitParam, $listModel)
{
$pluginManager = FabrikWorker::getPluginManager();
$requestGroupBy =
$this->app->getInput()->get('group_by', '');
$groupByLabels = array();
if ($requestGroupBy == '0')
{
$requestGroupBy = '';
}
$groupBys = array();
if ($requestGroupBy !== '')
{
$formModel = $this->getFormModel();
$requestGroupBy =
$formModel->getElement($requestGroupBy)->getElement()->id;
$groupBys[] = $requestGroupBy;
}
else
{
$listGroupBy = $listModel->getTable()->group_by;
if ($listGroupBy !== '')
{
$groupBys[] = $listGroupBy;
}
}
$params = $this->getParams();
$splitSum = $params->get($splitParam, null);
if (!is_null($splitSum))
{
$groupBys[] = $splitSum;
}
foreach ($groupBys as &$gById)
{
$plugin = $pluginManager->getElementPlugin($gById);
/**
* In the calculation rendering, in default.php, we are keying by values
not labels for join elements,
* so there's no point keying by label for joins. So I think we
need to change this chunk so it just tests to see if
* there is a getJoinValueColumn method, and if so, use that. I'm
leaving the original code intact though,
* while I wait to see if this causes Other Horrible Things To Happen.
*/
/*
$sName = method_exists($plugin, 'getJoinLabelColumn') ?
$plugin->getJoinLabelColumn() : $plugin->getFullName(false, false,
false);
if (!stristr($sName, 'CONCAT'))
{
$gById = FabrikString::safeColName($sName);
}
else
{
if (method_exists($plugin, 'getJoinValueColumn'))
{
$sName = $plugin->getJoinValueColumn();
$gById = FabrikString::safeColName($sName);
}
}
*/
if (method_exists($plugin, 'getFullLabelOrConcat'))
{
$sName = $plugin->getJoinValueColumn();
$sLabel = $plugin->getFullLabelOrConcat();
}
else
{
$sName = $sLabel = $plugin->getFullName(false, false, false);
}
$gById = FabrikString::safeColName($sName);
$groupByLabels[] = $sLabel;
}
return array($groupBys, $groupByLabels);
}
/**
* If the calculation query has had to convert the data to a machine
format, use
* this function to convert back to human readable format. E.g. time
element
* calcs in seconds but we'd want to convert back into h:m:s
*
* @param array &$rows Calculation values
*
* @return void
*/
protected function formatCalValues(&$rows)
{
}
/**
* Calculation: sum
* can be overridden in element class
*
* @param FabrikFEModelList &$listModel List model
*
* @return array
*/
public function sum(&$listModel)
{
$db = $listModel->getDb();
$params = $this->getParams();
$splitSum = $params->get('sum_split', '');
list($groupBys, $groupByLabels) =
$this->calcGroupBys('sum_split', $listModel);
$split = empty($groupBys) ? false : true;
$calcLabel = $params->get('sum_label',
Text::_('COM_FABRIK_SUM'));
if ($split)
{
$pluginManager = FabrikWorker::getPluginManager();
$plugin = $pluginManager->getElementPlugin($splitSum);
$sql = $this->getSumQuery($listModel, $groupBys) . '
GROUP BY label';
$sql = $listModel->pluginQuery($sql);
$db->setQuery($sql);
$results2 = $db->loadObjectList('label');
$this->formatCalValues($results2);
$uberTotal = 0;
foreach ($results2 as $pair)
{
$uberTotal += $pair->value;
}
$uberObject = new stdClass;
$uberObject->value = $uberTotal;
$uberObject->label = Text::_('COM_FABRIK_TOTAL');
$uberObject->class = 'splittotal';
$uberObject->special = true;
$results2[] = $uberObject;
$results = $this->formatCalcSplitLabels($results2,
$plugin, 'sum');
}
else
{
// Need to add a group by here as well as if the ONLY_FULL_GROUP_BY SQL
mode is enabled an error is produced
$sql = $this->getSumQuery($listModel) . ' GROUP BY label';
$sql = $listModel->pluginQuery($sql);
$db->setQuery($sql);
$results = $db->loadObjectList('label');
$this->formatCalValues($results);
}
$res = $this->formatCalcs($results, $calcLabel, $split);
return array($res, $results);
}
/**
* calculation: average
* can be overridden in element class
*
* @param FabrikFEModelList &$listModel list model
*
* @return string result
*/
public function avg(&$listModel)
{
$db = $listModel->getDb();
$params = $this->getParams();
$splitAvg = $params->get('avg_split', '');
$item = $listModel->getTable();
$calcLabel = $params->get('avg_label',
Text::_('COM_FABRIK_AVERAGE'));
list($groupBys, $groupByLabels) =
$this->calcGroupBys('avg_split', $listModel);
$split = empty($groupBys) ? false : true;
if ($split)
{
$pluginManager = FabrikWorker::getPluginManager();
$plugin = $pluginManager->getElementPlugin($splitAvg);
$sql = $this->getAvgQuery($listModel, $groupBys) . "
GROUP BY label";
$sql = $listModel->pluginQuery($sql);
$db->setQuery($sql);
$results2 = $db->loadObjectList('label');
$this->formatCalValues($results2);
$uberTotal = 0;
foreach ($results2 as $pair)
{
$uberTotal += $pair->value;
}
$uberObject = new stdClass;
$uberObject->value = $uberTotal / count($results2);
$uberObject->label = Text::_('COM_FABRIK_AVERAGE');
$uberObject->special = true;
$uberObject->class = 'splittotal';
$results2[] = $uberObject;
$results = $this->formatCalcSplitLabels($results2, $plugin,
'avg');
}
else
{
// Need to add a group by here as well as if the ONLY_FULL_GROUP_BY SQL
mode is enabled an error is produced
$sql = $this->getAvgQuery($listModel) . " GROUP BY label";
$sql = $listModel->pluginQuery($sql);
$db->setQuery($sql);
$results = $db->loadObjectList('label');
$this->formatCalValues($results);
}
$res = $this->formatCalcs($results, $calcLabel, $split);
return array($res, $results);
}
/**
* Get the sprintf format string
*
* @since 3.0.4
*
* @return string
*/
public function getFormatString()
{
$params = $this->getParams();
return $params->get('text_format_string');
}
/**
* calculation: median
* can be overridden in element class
*
* @param FabrikDEModelList &$listModel list model
*
* @return string result
*/
public function median(&$listModel)
{
$db = $listModel->getDb();
$item = $listModel->getTable();
$element = $this->getElement();
$joinSQL = $listModel->buildQueryJoin();
$whereSQL = $listModel->buildQueryWhere();
$params = $this->getParams();
$splitMedian = $params->get('median_split', '');
list($groupBys, $groupByLabels) =
$this->calcGroupBys('sum_split', $listModel);
$split = empty($groupBys) ? false : true;
$format = $this->getFormatString();
$res = '';
$calcLabel = $params->get('median_label',
Text::_('COM_FABRIK_MEDIAN'));
$results = array();
if ($split)
{
$pluginManager = FabrikWorker::getPluginManager();
$plugin = $pluginManager->getElementPlugin($splitMedian);
$sql = $this->getMedianQuery($listModel, $groupBys) .
' GROUP BY label ';
$sql = $listModel->pluginQuery($sql);
$db->setQuery($sql);
$results2 = $db->loadObjectList();
$results = $this->formatCalcSplitLabels($results2, $plugin,
'median');
}
else
{
$sql = $this->getMedianQuery($listModel);
$sql = $listModel->pluginQuery($sql);
$db->setQuery($sql);
$res = $this->_median($db->loadColumn());
$o = new stdClass;
if ($format != '')
{
$res = sprintf($format, $res);
}
$o->value = $res;
$label = $this->getListHeading();
$o->elLabel = $label;
$o->calLabel = $calcLabel;
$o->label = 'calc';
$results = array('calc' => $o);
}
$res = $this->formatCalcs($results, $calcLabel, $split, true, false);
return array($res, $results);
}
/**
* calculation: count
* can be overridden in element class
*
* @param FabrikFEModelList &$listModel List model
*
* @return string result
*/
public function count(&$listModel)
{
$db = $listModel->getDb();
$listModel->clearTable();
$item = $listModel->getTable();
$element = $this->getElement();
$params = $this->getParams();
$calcLabel = $params->get('count_label',
Text::_('COM_FABRIK_COUNT'));
$splitCount = $params->get('count_split', '');
list($groupBys, $groupByLabels) =
$this->calcGroupBys('count_split', $listModel);
$split = empty($groupBys) ? false : true;
if ($split)
{
$pluginManager = FabrikWorker::getPluginManager();
$plugin = $pluginManager->getElementPlugin($splitCount);
$sql = $this->getCountQuery($listModel, $groupBys) . "
GROUP BY label ";
$sql = $listModel->pluginQuery($sql);
$db->setQuery($sql);
$results2 = $db->loadObjectList('label');
$uberTotal = 0;
/*
* Removes values from display when split on used:
* see
http://www.fabrikar.com/forums/index.php?threads/calculation-split-on-problem.32035/
foreach ($results2 as $k => &$r)
{
if ($k == '')
{
unset($results2[$k]);
}
}
*/
foreach ($results2 as $pair)
{
$uberTotal += $pair->value;
}
$uberObject = new stdClass;
$uberObject->value = count($results2) == 0 ? 0 :
$uberTotal;
$uberObject->label =
Text::_('COM_FABRIK_TOTAL');
$uberObject->class = 'splittotal';
$uberObject->special = true;
$results =
$this->formatCalcSplitLabels($results2, $plugin, 'count');
$results[Text::_('COM_FABRIK_TOTAL')] = $uberObject;
}
else
{
// Need to add a group by here as well as if the ONLY_FULL_GROUP_BY SQL
mode is enabled an error is produced
$sql = $this->getCountQuery($listModel) . ' GROUP BY label
';
$sql = $listModel->pluginQuery($sql);
$db->setQuery($sql);
$results = $db->loadObjectList('label');
}
$res = $this->formatCalcs($results, $calcLabel, $split, false, false);
return array($res, $results);
}
/**
* calculation: custom_calc
* can be overridden in element class
*
* @param FabrikFEModelList &$listModel list model
*
* @return array
*/
public function custom_calc(&$listModel)
{
$db = $listModel->getDb();
$params = $this->getParams();
$item = $listModel->getTable();
$splitCustom = $params->get('custom_calc_split',
'');
$split = $splitCustom == '' ? false : true;
$calcLabel = $params->get('custom_calc_label',
Text::_('COM_FABRIK_CUSTOM'));
if ($split)
{
$pluginManager = FabrikWorker::getPluginManager();
$plugin = $pluginManager->getElementPlugin($splitCustom);
$splitName = method_exists($plugin, 'getJoinLabelColumn')
? $plugin->getJoinLabelColumn() : $plugin->getFullName(false, false);
$splitName = FabrikString::safeColName($splitName);
$sql = $this->getCustomQuery($listModel, $splitName) .
' GROUP BY label';
$sql = $listModel->pluginQuery($sql);
$db->setQuery($sql);
$results2 = $db->loadObjectList('label');
$results = $this->formatCalcSplitLabels($results2, $plugin,
'custom_calc');
}
else
{
// Need to add a group by here as well as if the ONLY_FULL_GROUP_BY SQL
mode is enabled an error is produced
$sql = $this->getCustomQuery($listModel) . ' GROUP BY
label';
$sql = $listModel->pluginQuery($sql);
$db->setQuery($sql);
$results = $db->loadObjectList('label');
}
$res = $this->formatCalcs($results, $calcLabel, $split);
return array($res, $results);
}
/**
* Format the labels for calculations when they are split
*
* @param array &$results2 calculation results
* @param object &$plugin element that the data is SPLIT on
* @param string $type of calculation
*
* @return array
*/
protected function formatCalcSplitLabels(&$results2, &$plugin,
$type = '')
{
$results = array();
$toMerge = array();
$name = $plugin->getFullName(true, false);
// $$$ hugh - avoid PHP warning if $results2 is NULL
if (empty($results2))
{
return $results;
}
foreach ($results2 as $key => $val)
{
if (isset($val->special) && $val->special)
{
// Don't include special values (ubers) in $toMerge, otherwise
total sum added to first value
$results[$val->label] = $val;
continue;
}
if ($plugin->hasSubElements)
{
//
http://fabrikar.com/forums/index.php?threads/calculation-split-on-problem.40122/
$labelParts = explode(' & ',
$val->label??'');
if (count($labelParts) > 1)
{
$label = $labelParts[1];
}
else
{
$label = $labelParts[0];
}
$label = FabrikWorker::JSONtoData($label);
$ls = array();
if (is_array($label))
{
foreach ($label as $l)
{
$ls[] = ($type != 'median') ?
$plugin->getLabelForValue($l) : $plugin->getLabelForValue($key,
$key);
}
}
else
{
$ls[] = ($type != 'median') ?
$plugin->getLabelForValue($label) : $plugin->getLabelForValue($key,
$key);
}
if (count($labelParts) > 1)
{
$val->label = $labelParts[0] . ' & ' .
implode(',', $ls);
}
else
{
$val->label = implode(',', $ls);
}
}
else
{
$d = new stdClass;
$d->$name = $val->label;
$opts = array('rollover' => false, 'link'
=> false);
$val->label = $plugin->renderListData($val->label, $d, $opts);
}
if (array_key_exists($val->label, $results))
{
/** $$$ rob the $result data is keyed on the raw database result -
however, we are interested in
* keying on the formatted table result (e.g. allows us to group date
entries by year)
*/
if ($results[$val->label] !== '')
{
$toMerge[$val->label][] = $results[$val->label]->value;
}
$results[$val->label] = '';
$toMerge[$val->label][] = $val->value;
}
else
{
$results[$val->label] = $val;
}
}
foreach ($toMerge as $label => $data)
{
$o = new stdClass;
switch ($type)
{
case 'avg':
$o->value = $this->simpleAvg($data);
break;
case 'sum':
$o->value = $this->simpleSum($data);
break;
case 'median':
$o->value = $this->_median($data);
break;
case 'count':
$o->value = count($data);
break;
case 'custom_calc':
$params = $this->getParams();
$custom_calc_php = $params->get('custom_calc_php',
'');
if (!empty($custom_calc_php))
{
FabrikWorker::clearEval();
$o->value = Php::Eval(['code' => $custom_calc_php]);
FabrikWorker::logEval($custom_calc_php, 'Caught exception on
eval of ' . $name . ': %s');
}
else
{
$o->value = $data;
}
break;
default:
$o->value = $data;
break;
}
$o->label = $label;
$results[$label] = $o;
}
return $results;
}
/**
* find an average from a set of data
* can be overwritten in plugin - see date for example of averaging dates
*
* @param array $data to average
*
* @return string average result
*/
public function simpleAvg($data)
{
return $this->simpleSum($data) / count($data);
}
/**
* find the sum from a set of data
* can be overwritten in plugin - see date for example of averaging dates
*
* @param array $data to sum
*
* @return string sum result
*/
public function simpleSum($data)
{
return array_sum($data);
}
/**
* Take the results form a calc and create the string that can be used to
summarize them
*
* @param array &$results calculation results
* @param string $calcLabel calc label
* @param bool $split is the data split
* @param bool $numberFormat should we apply any number formatting
* @param bool $sprintFFormat should we apply the text_format_string ?
*
* @return string
*/
protected function formatCalcs(&$results, $calcLabel, $split = false,
$numberFormat = true, $sprintFFormat = true)
{
settype($results, 'array');
$res = array();
$res[] = $split ? '<dl>' : '<ul
class="fabrikRepeatData">';
$l = '<span class="calclabel">' .
$calcLabel . '</span>';
$res[] = $split ? '<dt>' . $l . '</dt>'
: '<li>' . $l;
$format = $this->getFormatString();
$label = $this->getListHeading();
foreach ($results as $key => $o)
{
$o->label = ($o->label == 'calc') ? '' :
$o->label;
$o->elLabel = $label . ' ' . $o->label;
if ($numberFormat)
{
$o->value = $this->numberFormat($o->value);
}
if ($format != '' && $sprintFFormat)
{
$o->value = sprintf($format, $o->value);
}
$o->calLabel = $calcLabel;
$class = isset($o->class) ? ' class="' .
$o->class . '"' : '';
if ($split)
{
$res[] = '<dd' . $class . '><span
class="calclabel">' . $o->label . ':</span>
' . $o->value . '</dd>';
}
else
{
$res[] = $o->value . '</li>';
}
}
ksort($results);
$res[] = $split ? '</dl>' : '</ul>';
return implode("\n", $res);
}
/**
* Get median
*
* @param array $results set of results to get median from
*
* @return string median value
*/
private function _median($results)
{
$results = (array) $results;
sort($results);
if ((count($results) % 2) == 1)
{
/* odd */
$midKey = floor(count($results) / 2);
return $results[$midKey];
}
else
{
$midKey = floor(count($results) / 2) - 1;
$midKey2 = floor(count($results) / 2);
return $this->simpleAvg(array(FArrayHelper::getValue($results,
$midKey), FArrayHelper::getValue($results, $midKey2)));
}
}
/**
* Returns javascript which creates an instance of the class defined in
formJavascriptClass()
*
* @param int $repeatCounter Repeat group counter
*
* @return array
*/
public function elementJavascript($repeatCounter)
{
return array();
}
/**
* Get JS code for ini element list js
* Overwritten in plugin classes
*
* @return string
*/
public function elementListJavascript()
{
return '';
}
/**
* Create a class for the elements default javascript options
*
* @param int $repeatCounter repeat group counter
*
* @return object options
*/
public function getElementJSOptions($repeatCounter)
{
$element = $this->getElement();
$opts = new stdClass;
$data = $this->getFormModel()->data;
$opts->repeatCounter = $repeatCounter;
$opts->editable = ($this->canView() &&
!$this->canUse()) ? false : $this->isEditable();
$opts->value = $this->getValue($data, $repeatCounter);
$opts->label = $element->label;
$opts->defaultVal = $this->getDefaultValue($data);
$opts->inRepeatGroup = $this->getGroup()->canRepeat() == 1;
$opts->fullName = $this->getFullName(true, false);
$opts->watchElements =
$this->validator->jsWatchElements($repeatCounter);
$groupModel = $this->getGroup();
$opts->canRepeat = (bool) $groupModel->canRepeat();
$opts->isGroupJoin = (bool) $groupModel->isJoin();
$validations = $this->validator->findAll();
$opts->mustValidate = false;
foreach ($validations as $validation)
{
$validationParams = $validation->getParams();
if ($validationParams->get('must_validate', '0')
=== '1')
{
if ($validation->canValidate())
{
$opts->mustValidate = true;
break;
}
}
}
$opts->validations = empty($validations) ? false : true;
if ($this->isJoin())
{
$opts->joinid = (int) $this->getJoinModel()->getJoin()->id;
}
else
{
$opts->joinid = (int) $groupModel->getGroup()->join_id;
}
return $opts;
}
/**
* Does the element use the WYSIWYG editor
*
* @return bool use wysiwyg editor
*/
public function useEditor()
{
return false;
}
/**
* Processes uploaded data
*
* @return void
*/
public function processUpload()
{
}
/**
* Get the class to manage the form element
* to ensure that the file is loaded only once
*
* @param array &$srcs Scripts previously loaded
* @param string $script Script to load once class has loaded
* @param array &$shim Dependant class names to load before
loading the class - put in requirejs.config shim
*
* @return void
*/
public function formJavascriptClass(&$srcs, $script = '',
&$shim = array())
{
$name = $this->getElement()->plugin;
$ext = FabrikHelperHTML::isDebug() ? '.js' :
'-min.js';
$formId = $this->getFormModel()->getId();
static $elementClasses;
if (!isset($elementClasses))
{
$elementClasses = array();
}
if (!array_key_exists($formId, $elementClasses))
{
$elementClasses[$formId] = array();
}
// Load up the default script
if ($script == '')
{
$script = 'plugins/fabrik_element/' . $name . '/' .
$name . $ext;
}
if (empty($elementClasses[$formId][$script]))
{
$srcs['Element' . ucfirst($name)] = $script;
$elementClasses[$formId][$script] = 1;
}
}
/**
* load js file for element when in list view
*
* @param array &$srcs JS scripts to load
*
* @return null
*/
public function tableJavascriptClass(&$srcs)
{
$p = $this->getElement()->plugin;
$src = 'plugins/fabrik_element/' . $p . '/list-' . $p
. '.js';
if (File::exists(JPATH_SITE . '/' . $src))
{
$className = 'Fb' . ucfirst($p) .'List';
$srcs[$className] = $src;
}
}
/**
* Can be overwritten in plugin classes
* e.g. if changing from db join to field we need to remove the join
* entry from the #__fabrik_joins table
*
* @param object &$row that is going to be updated
*
* @return null
*/
public function beforeSave(&$row)
{
// $safeHtmlFilter = InputFilter::getInstance(null, null, 1, 1);
$safeHtmlFilter = InputFilter::getInstance(array(), array(), 1, 1);
$post = $safeHtmlFilter->clean($_POST, 'array');
$post = $post['jform'];
$dbjoinEl = (is_subclass_of($this,
'PlgFabrik_ElementDatabasejoin') || get_class($this) ==
'PlgFabrik_ElementDatabasejoin');
/**
* $$$ hugh - added test for empty id, i.e. new element, otherwise we try
and delete a crap load of join table rows
* we shouldn't be deleting! Also adding defensive code to
deleteJoins() to test for empty ID.
*/
if (!empty($post['id']) && !$this->isJoin()
&& !$dbjoinEl)
{
$this->deleteJoins((int) $post['id']);
}
}
/**
* Delete joins
*
* @param int $id element id
*
* @return null
*/
protected function deleteJoins($id)
{
// $$$ hugh - bail if no $id specified
if (empty($id))
{
return;
}
$db = FabrikWorker::getDbo(true);
$query = $db->getQuery(true);
$query->delete('#__fabrik_joins')->where('element_id
= ' . $id);
$db->setQuery($query);
$db->execute();
$query->clear();
$query->select('j.id AS
jid')->from('#__fabrik_elements AS
e')->join('INNER', ' #__fabrik_joins AS j ON
j.element_id = e.id')
->where('e.parent_id = ' . $id);
$db->setQuery($query);
$join_ids = $db->loadColumn();
if (!empty($join_ids))
{
$query->clear();
$query->delete('#__fabrik_joins')->where('id IN
(' . implode(',', $join_ids) . ')');
$db->setQuery($query);
$db->execute();
}
}
/**
* If your element risks not to post anything in the form (e.g. check
boxes with none checked)
* the this function will insert a default value into the database
*
* @param array &$data form data
*
* @return array form data
*/
public function getEmptyDataValue(&$data)
{
}
/**
* Used to format the data when shown in the form's email
*
* @param mixed $value element's data
* @param array $data form records data
* @param int $repeatCounter repeat group counter
*
* @return string formatted value
*/
public function getEmailValue($value, $data = array(), $repeatCounter = 0)
{
if ($this->inRepeatGroup && is_array($value))
{
$val = array();
foreach ($value as $v2)
{
$val[] = $this->getIndEmailValue($v2, $data, $repeatCounter);
}
}
else
{
$val = $this->getIndEmailValue($value, $data, $repeatCounter);
}
return $val;
}
/**
* Turn form value into email formatted value
*
* @param mixed $value Element value
* @param array $data Form data
* @param int $repeatCounter Group repeat counter
*
* @return string email formatted value
*/
protected function getIndEmailValue($value, $data = array(),
$repeatCounter = 0)
{
return $value;
}
/**
* Is the element an upload element
*
* @return boolean
*/
public function isUpload()
{
return $this->is_upload;
}
/**
* If a database join element's value field points to the same db
field as this element
* then this element can, within modifyJoinQuery, update the query.
* E.g. if the database join element points to a file upload element then
you can replace
* the file path that is the standard $val with the html to create the
image
*
* @param string $val value
* @param string $view form or list
*
* @deprecated - doesn't seem to be used
*
* @return string modified val
*/
protected function modifyJoinQuery($val, $view = 'form')
{
return $val;
}
/**
* not used
*
* @deprecated
*
* @return void
*/
public function ajax_loadTableFields()
{
$db = FabrikWorker::getDbo();
$listModel =
Factory::getApplication()->bootComponent('com_fabrik')->getMVCFactory()->createModel('List',
'FabrikFEModel');
$input = $this->app->getInput();
$this->_cnnId = $input->getInt('cid', 0);
$tbl = $db->qn($input->get('table'));
$fieldDropDown = $listModel->getFieldsDropDown($this->_cnnId,
$tbl, '-', false, 'params[join_val_column]');
$fieldDropDown2 = $listModel->getFieldsDropDown($this->_cnnId,
$tbl, '-', false, 'params[join_key_column]');
echo "$('addJoinVal').innerHTML =
'$fieldDropDown';";
echo "$('addJoinKey').innerHTML =
'$fieldDropDown2';";
}
/**
* Get join row
*
* @return FabrikTableJoin join table or false if not loaded
*/
protected function getJoin()
{
if ($this->isJoin())
{
return $this->getJoinModel()->getJoin();
}
return false;
}
/**
* Get database field description
*
* @return string db field type
*/
public function getFieldDescription()
{
$element = $this->getPluginName();
$plugin = PluginHelper::getPlugin('fabrik_element', $element);
$fParams = new Registry($plugin->params);
$p = $this->getParams();
if ($this->encryptMe())
{
return 'BLOB';
}
$group = $this->getGroup();
if ($group->isJoin() == 0 && $group->canRepeat())
{
return "TEXT";
}
else
{
$size = $p->get('maxlength', $this->fieldSize);
$objType = sprintf($this->fieldDesc, $size);
}
$objType = $fParams->get('defaultFieldType', $objType);
return $objType;
}
/**
* Trigger called when a row is deleted, can be used to delete images
previously uploaded
*
* @param array $groups grouped data of rows to delete
*
* @return void
*/
public function onDeleteRows($groups)
{
}
/**
* Get a value to use as an empty filter value
*
* @return string
*/
public function emptyFilterValue()
{
return '';
}
/**
* Trigger called when a form or group row is stored.
* Ignores the element if it is a join chx/multi select
*
* @param array &$data Data to store
* @param int $repeatCounter Repeat group index
*
* @return bool If false, data should not be added.
*/
public function onStoreRow(&$data, $repeatCounter = 0)
{
if ($this->isJoin())
{
return false;
}
$element = $this->getElement();
// We should not process this element if it is unpublished
// Unpublished elements may not be in a valid state and may cause an
error (white-screen)
if (!$element->published)
{
return false;
}
$shortName = $element->name;
$listModel = $this->getListModel();
if ($this->encryptMe())
{
$listModel->encrypt[] = $shortName;
}
$formModel = $this->getFormModel();
$name = $this->getFullName(true, false);
/**
* @TODO - fix this to use formData instead of formDataWithTableName,
* which we need to deprecate.
*/
if (!array_key_exists($name, $formModel->formDataWithTableName))
//if
($this->dataConsideredEmpty(FArrayHelper::getValue($formModel->formDataWithTableName,
$name, '')))
{
$this->getEmptyDataValue($data);
}
$v = $this->getValue($formModel->formDataWithTableName,
$repeatCounter);
if (!$this->ignoreOnUpdate($v))
{
$data[$shortName] = $v;
}
return true;
}
/**
* Shows the data formatted for the list view
*
* @param string $data Elements data
* @param stdClass &$thisRow All the data in the lists current row
* @param array $opts Rendering options
*
* @return string formatted value
*/
public function renderListData($data, stdClass &$thisRow, $opts =
array())
{
$profiler = Profiler::getInstance('Application');
JDEBUG ? $profiler->mark("renderListData: parent: start:
{$this->element->name}") : null;
$params = $this->getParams();
$listModel = $this->getListModel();
if (!ArrayHelper::getValue($opts, 'json', false))
{
$data = FabrikWorker::JSONtoData($data, true);
}
else
{
$data = (array) $data;
}
foreach ($data as $i => &$d)
{
/**
* At this point we should have scalar data, but if something (like a
textarea) had JSON as its value,
* it will have gotten decoded by the JSONtoData, so if not scalar,
re-encode it.
*/
if (!is_scalar($d))
{
$d = json_encode($d);
}
if ((int)$params->get('icon_folder', 0) > 0 &&
ArrayHelper::getValue($opts, 'icon', 1))
{
// $$$ rob was returning here but that stopped us being able to use
links and icons together
$d = $this->replaceWithIcons($d, 'list',
$listModel->getTmpl());
}
if (ArrayHelper::getValue($opts, 'rollover', 1))
{
$d = $this->rollover($d, $thisRow, 'list');
}
if (ArrayHelper::getValue($opts, 'link', 1))
{
$d = $listModel->_addLink($d, $this, $thisRow, $i);
}
}
$final = $this->renderListDataFinal($data, $opts);
JDEBUG ? $profiler->mark("renderListData: parent: end:
{$this->element->name}") : null;
return $final;
}
/**
* Final prepare data function called from renderListData(), converts data
to string and if needed
* encases in <ul> (for repeating data)
*
* @param array $data list cell data
* $param array $opts list options
*
* @return string cell data
*/
protected function renderListDataFinal($data, $opts)
{
$profiler = Profiler::getInstance('Application');
JDEBUG ? $profiler->mark("renderListDataFinal: parent:
start: {$this->element->name}") : null;
if (is_array($data))
{
if (count($data) > 1)
{
if (!array_key_exists(0, $data))
{
// Occurs if we have created a list from an existing table whose data
contains json objects (e.g. #__users.params)
$obj = ArrayHelper::toObject($data);
$data = array();
$data[0] = $obj;
}
// If we are storing info as json the data will contain an array of
objects
if (is_object($data[0]))
{
foreach ($data as &$o)
{
$this->convertDataToString($o);
}
}
$r = '<ul
class="fabrikRepeatData"><li>' .
implode('</li><li>', $data) .
'</li></ul>';
}
else
{
$r = empty($data) ? '' : array_shift($data);
}
}
else
{
$r = $data;
}
$displayData = new stdClass;
$displayData->text = $r;
if (ArrayHelper::getValue($opts, 'custom_layout', 1)) {
$layout = $this->getLayout('list');
$res = $layout->render($displayData);
}
else
{
$res = '';
}
// If no custom list layout found revert to the default list renderer
if ($res === '')
{
$basePath = COM_FABRIK_FRONTEND . '/layouts/';
$layout = new FileLayout('fabrik-element-list', $basePath,
array('debug' => false, 'component' =>
'com_fabrik', 'client' => 'site'));
$res = $layout->render($displayData);
}
JDEBUG ? $profiler->mark("renderListDataFinal: parent: end:
{$this->element->name}") : null;
return $res;
}
/**
* Convert an object or array into a <ul>
*
* @param mixed &$o data to convert
*
* @return void
*/
protected function convertDataToString(&$o)
{
if (is_object($o))
{
$s = '<ul>';
foreach ($o as $k => $v)
{
if (!is_string($v))
{
$v = json_encode($v);
}
$s .= '<li>' . $v . '</li>';
}
$s .= '</ul>';
$o = $s;
}
}
/**
* Prepares the element data for CSV export
*
* @param string $data Element data
* @param object &$thisRow All the data in the lists current row
*
* @return string Formatted CSV export value
*/
public function renderListData_csv($data, &$thisRow)
{
return $data;
}
/**
* Builds some html to allow certain elements to display the option to add
in new options
* e.g. picklists, dropdowns radiobuttons
*
* @param bool $repeatCounter repeat group counter
* @param bool $onlylabel only show the label - overrides standard
element settings
*
* @return string
*/
protected function getAddOptionFields($repeatCounter, $onlylabel = false)
{
$params = $this->getParams();
if (!$params->get('allow_frontend_addto'))
{
return;
}
$basePath = COM_FABRIK_BASE .
'/components/com_fabrik/layouts/element';
$layout = new
FileLayout('fabrik-element-addoptions', $basePath,
array('debug' => false, 'component' =>
'com_fabrik', 'client' => 'site'));
$displayData = new stdClass;
$displayData->id =
$this->getHTMLId($repeatCounter);
$displayData->add_image =
FabrikHelperHTML::image('plus', 'form',
@$this->tmpl, array('alt' =>
Text::_('COM_FABRIK_ADD')));
$displayData->allowadd_onlylabel =
$params->get('allowadd-onlylabel');
$displayData->savenewadditions =
$params->get('savenewadditions');
$displayData->onlylabel = $onlylabel;
$displayData->hidden_field =
$this->getHiddenField($displayData->id . '_additions',
'', $displayData->id . '_additions');
return $layout->render($displayData);
}
/**
* Does the element force the form to submit via AJAX
*
* @deprecated - not used
*
* @return bool true if the element type forces the form to
*/
public function requiresAJAXSubmit()
{
return false;
}
/**
* Determine if the element should run its validation plugins on form
submission
*
* @return bool default true
*/
public function mustValidate()
{
return true;
}
/**
* Get the name of the field to order the table data by
* can be overwritten in plugin class - but not currently done so
*
* @return string column to order by tablename___elementname and yes you
can use aliases in the order by clause
*/
public function getOrderByName()
{
return $this->getFullName(true, false);
}
/**
* Not used
*
* @param string $rawval raw value
*
* @deprecated - not used
*
* @return string
*/
public function getFilterLabel($rawval)
{
return $rawval;
}
/**
* Store the element params
*
* @return bool
*
* @throws RuntimeException
*/
public function storeAttribs()
{
$element = $this->getElement();
if (!$element)
{
return false;
}
$db = FabrikWorker::getDbo(true);
$element->params = $this->getParams()->toString();
/*
* Trying to save JSON params larger than the params field totally breaks
the backend. Original size
* of params field was TEXT, and a large serialized calculation could
blow that away.
*
* In 3.6.1 we increased the size of params to MEDIUMTEXT, but people
only updating from github
* may not get that update right away. So just to be on the safe side,
we'll update it on the fly
* if params are big. Leave this in till 3.7.
*/
if (strlen($element->params) > 65535)
{
$db->setQuery("ALTER TABLE `#__fabrik_elements` MODIFY `params`
MEDIUMTEXT");
try
{
$db->execute();
}
catch (RuntimeException $e)
{
// meh
}
}
$query = $db->getQuery(true);
$query->update('#__fabrik_elements')->set('params =
' . $db->q($element->params))->where('id = ' .
(int) $element->id);
$db->setQuery($query);
$res = $db->execute();
return $res;
}
/**
* load a new set of default properties and params for the element
* can be overridden in plugin class
*
* @param array $properties Default props
*
* @return FabrikTableElement element (id = 0)
*/
public function getDefaultProperties($properties = array())
{
$now = $this->date->toSql();
$this->setId(0);
$item = $this->getElement();
$item->set('plugin', $this->_name);
$item->set('params', $this->getDefaultAttribs());
$item->set('created', $now);
$item->set('created_by',
$this->user->get('id'));
$item->set('created_by_alias',
$this->user->get('username'));
$item->set('published', '1');
$item->set('show_in_list_summary', '1');
$item->set('link_to_detail', '1');
$item->bind($properties);
return $item;
}
/**
* Get a json encoded string of the element default parameters
*
* @return string
*/
public function getDefaultAttribs()
{
$o = new stdClass;
$o->rollover = '';
$o->comment = '';
$o->sub_default_value = '';
$o->sub_default_label = '';
$o->element_before_label = 1;
$o->allow_frontend_addtocheckbox = 0;
$o->database_join_display_type = 'dropdown';
$o->joinType = 'simple';
$o->join_conn_id = -1;
$o->date_table_format = 'Y-m-d';
$o->date_form_format = 'Y-m-d H:i:s';
$o->date_showtime = 0;
$o->date_time_format = 'H:i';
$o->date_defaulttotoday = 1;
$o->date_firstday = 0;
$o->multiple = 0;
$o->allow_frontend_addtodropdown = 0;
$o->password = 0;
$o->maxlength = 255;
$o->text_format = 'text';
$o->integer_length = 6;
$o->decimal_length = 2;
$o->guess_linktype = 0;
$o->disable = 0;
$o->readonly = 0;
$o->ul_max_file_size = 16000;
$o->ul_email_file = 0;
$o->ul_file_increment = 0;
$o->upload_allow_folderselect = 1;
$o->fu_fancy_upload = 0;
$o->upload_delete_image = 1;
$o->make_link = 0;
$o->fu_show_image_in_table = 0;
$o->image_library = 'gd2';
$o->make_thumbnail = 0;
$o->imagepath = '/';
$o->selectImage_root_folder = '/';
$o->image_front_end_select = 0;
$o->show_image_in_table = 0;
$o->image_float = 'none';
$o->link_target = '_self';
$o->radio_element_before_label = 0;
$o->options_per_row = 4;
$o->ck_options_per_row = 4;
$o->allow_frontend_addtoradio = 0;
$o->use_wysiwyg = 0;
$o->my_table_data = 'id';
$o->update_on_edit = 0;
$o->view_access = 1;
$o->show_in_rss_feed = 0;
$o->show_label_in_rss_feed = 0;
$o->icon_folder = -1;
$o->use_as_row_class = 0;
$o->filter_access = 1;
$o->full_words_only = 0;
$o->inc_in_adv_search = 1;
$o->sum_on = 0;
$o->sum_access = 0;
$o->avg_on = 0;
$o->avg_access = 0;
$o->median_on = 0;
$o->median_access = 0;
$o->count_on = 0;
$o->count_access = 0;
return json_encode($o);
}
/**
* Do we need to include the light-box js code
*
* @return bool
*/
public function requiresLightBox()
{
return false;
}
/**
* Do we need to include the slide-show js code
*
* @return bool
*/
public function requiresSlideshow()
{
return false;
}
/**
* Get Joomfish options
*
* @deprecated - not supporting joomfish
*
* @return array key=>value options
*/
public function getJoomfishOptions()
{
return array();
}
/**
* When filtering a table determine if the element's filter should be
an exact match
* should take into account if the element is in a non-joined repeat group
*
* @param string $val element value
*
* @return bool
*/
public function isExactMatch($val)
{
$element = $this->getElement();
$filterExactMatch = isset($val['match']) ?
$val['match'] : $element->filter_exact_match;
$group = $this->getGroup();
if (!$group->isJoin() && $group->canRepeat())
{
$filterExactMatch = false;
}
return $filterExactMatch;
}
/**
* Not used
*
* @deprecated - not used
*
* @return boolean
*/
public function onAjax_getFolders()
{
$input = $this->app->getInput();
$rDir = $input->getString('dir');
$folders = Folder::folders($rDir);
if ($folders === false)
{
// $$$ hugh - need to echo empty JSON array otherwise we break JS which
assumes an array
echo json_encode(array());
return false;
}
sort($folders);
echo json_encode($folders);
}
/**
* If used as a filter add in some JS code to watch observed filter
element's changes
* when it changes update the contents of this elements dd filter's
options
*
* @param bool $normal is the filter a normal (true) or advanced
filter
* @param string $container container
*
* @return void
*/
public function filterJS($normal, $container)
{
// Overwritten in plugin
}
/**
* Should the element's data be returned in the search all?
* Looks at the lists selected options, if its there looks at what search
mode the list is using
* and determines if the selected element can be used.
*
* @param bool $advancedMode Is the elements' list is extended
search all mode?
* @param string $search Search string
*
* @return bool true
*/
public function includeInSearchAll($advancedMode = false, $search =
'')
{
if ($this->isJoin() && $advancedMode)
{
return false;
}
$listModel = $this->getListModel();
$listParams = $listModel->getParams();
$searchElements = $listParams->get('list_search_elements',
'');
if ($searchElements === '')
{
return false;
}
$searchElements = json_decode($searchElements);
if (!isset($searchElements->search_elements) ||
!is_array($searchElements->search_elements))
{
return false;
}
if (in_array($this->getId(), $searchElements->search_elements))
{
$advancedMode = $listParams->get('search-mode-advanced');
return $this->canIncludeInSearchAll($advancedMode);
}
}
/**
* Is it possible to include the element in the Search all query?
* true if basic search
* true/false if advanced search
*
* @param bool $advancedMode Is the list using advanced search
*
* @since 3.1b
*
* @return boolean
*/
public function canIncludeInSearchAll($advancedMode)
{
$params = $this->getParams();
if (!$advancedMode)
{
return true;
}
if ($this->ignoreSearchAllDefault)
{
return false;
}
$format = $params->get('text_format');
if ($format == 'integer' || $format == 'decimal')
{
return false;
}
return true;
}
/**
* Modify the label for admin list - filter elements.
* Adds a '*' if the element is not available in advanced search
*
* @param string &$label Element label
*
* @return void
*/
public function availableInAdvancedSearchLabel(&$label)
{
$label = $this->canIncludeInSearchAll(true) ? $label : $label .
'*';
}
/**
* Get the value to use for graph calculations
* see timer which converts the value into seconds
*
* @param string $v standard value
*
* @return mixed calculation value
*/
public function getCalculationValue($v)
{
return (float) $v;
}
/**
* run on formModel::setFormData()
*
* @param int $c repeat group counter
*
* @return void
*/
public function preProcess($c)
{
}
/**
* Called when copy row list plugin called
*
* @param mixed $val value to copy into new record
*
* @return mixed value to copy into new record
*/
public function onCopyRow($val)
{
return $this->defaultOnCopy() ? $this->default : $val;
}
/**
* Called when save as copy form button clicked
*
* @param mixed $val value to copy into new record
*
* @return mixed value to copy into new record
*/
public function onSaveAsCopy($val)
{
return $this->defaultOnCopy() ? $this->default : $val;
}
/**
* Ajax call to get auto complete options (now caches results)
*
* @return string json encoded options
*/
public function onAutocomplete_options()
{
// Needed for ajax update (since we are calling this method via
dispatcher element is not set)
$input = $this->app->getInput();
$o = new stdClass;
$formModel = $this->getFormModel();
$this->setId($input->getInt('element_id'));
$this->loadMeForAjax();
// Check for request forgeries
if ($formModel->spoofCheck() &&
!Session::checkToken('request'))
{
$o->error = Text::_('JERROR_ALERTNOAUTHOR');
echo json_encode($o);
return;
}
if (!$this->canUse()) {
$o->error = Text::_('JERROR_ALERTNOAUTHOR');
echo json_encode($o);
return;
}
$cache = FabrikWorker::getCache();
$search = $input->get('value', '',
'string');
// uh oh, can't serialize PDO db objects so no can cache, as J!
serializes the args
if ($this->config->get('dbtype') ===
'pdomysql')
{
echo $this->cacheAutoCompleteOptions($this, $search);
}
else
{
echo $cache->get(array(get_class($this),
'cacheAutoCompleteOptions'), [$this, $search]);
}
}
/**
* Cache method to populate auto-complete options
*
* @param plgFabrik_Element $elementModel element model
* @param string $search search string
* @param array $opts options, 'label'
=> field to use for label (db join)
*
* @since 3.0.7
*
* @return string json encoded search results
*/
public static function cacheAutoCompleteOptions($elementModel, $search,
$opts = array())
{
$name = $elementModel->getFullName(false, false);
$elementModel->encryptFieldName($name);
$listModel = $elementModel->getListModel();
$db = $listModel->getDb();
$query = $db->getQuery(true);
$tableName = $listModel->getTable()->db_table_name;
$query->select('DISTINCT(' . $name . ') AS value,
' . $name . ' AS text')->from($tableName);
$query->where($name . ' LIKE ' .
$db->q(addslashes('%' . $search . '%')));
$query = $listModel->buildQueryJoin($query);
$query = $listModel->buildQueryWhere(false, $query);
$query = $listModel->pluginQuery($query);
$db->setQuery($query);
$tmp = $db->loadObjectList();
foreach ($tmp as &$t)
{
$elementModel->toLabel($t->text);
$t->text = strip_tags($t->text);
}
return json_encode($tmp);
}
/**
* Get the table name that the element stores to
* can be the main table name or the joined table name
*
* @return string
*/
protected function getTableName()
{
$listModel = $this->getListModel();
$table = $listModel->getTable();
$groupModel = $this->getGroup();
if ($groupModel->isJoin())
{
$joinModel = $groupModel->getJoinModel();
$join = $joinModel->getJoin();
$name = $join->table_join;
}
else
{
$name = $table->db_table_name;
}
return $name;
}
/**
* Converts a raw value into its label equivalent
*
* @param string &$v raw value
*
* @return void
*/
protected function toLabel(&$v)
{
}
/**
* Build group by query to append to list query
*
* @return string getGroupByQuery
*/
public function getGroupByQuery()
{
return '';
}
/**
* Append element where statement to lists where array
*
* @param array &$whereArray list models where statements
*
* @return void
*/
public function appendTableWhere(&$whereArray)
{
$params = $this->getParams();
if ($params->get('append_table_where', false))
{
if (method_exists($this, 'buildQueryWhere'))
{
$where = trim($this->buildQueryWhere(array()));
if ($where != '')
{
$where = StringHelper::substr($where, 5, StringHelper::strlen($where)
- 5);
if (!in_array($where, $whereArray))
{
$whereArray[] = $where;
}
}
}
}
}
/**
* Used by validations
*
* @param string $data this elements data
* @param string $cond what condition to apply
* @param string $compare data to compare element's data to
*
* @return bool
*/
public function greaterOrLessThan($data, $cond, $compare)
{
if ($cond == '>')
{
return $data > $compare;
}
elseif ($cond == '>=')
{
return $data >= $compare;
}
elseif ($cond == '<')
{
return $data < $compare;
}
elseif ($cond == '<=')
{
return $data <= $compare;
}
elseif ($cond == '==')
{
return $data == $compare;
}
return false;
}
/**
* Can the element plugin encrypt data
*
* @return bool
*/
public function canEncrypt()
{
return false;
}
/**
* Should the element's data be encrypted
*
* @return bool
*/
public function encryptMe()
{
$params = $this->getParams();
return ($this->canEncrypt() &&
$params->get('encrypt', false));
}
/**
* Format a number value
*
* @param mixed $data (double/int)
*
* @return string formatted number
*/
protected function numberFormat($data)
{
$params = $this->getParams();
if (!$params->get('field_use_number_format', false) ||
empty($data))
{
return $data;
}
$decimalLength = (int) $params->get('decimal_length', 2);
$decimalSep = $params->get('field_decimal_sep',
'.');
$thousandSep = $params->get('field_thousand_sep',
',');
// Workaround for params not letting us save just a space!
if ($thousandSep == '#32')
{
$thousandSep = ' ';
}
else if ($thousandSep == '#00')
{
$thousandSep = '';
}
return number_format((float) $data, $decimalLength, $decimalSep,
$thousandSep);
}
/**
* Strip number format from a number value
*
* @param mixed $val (double/int)
*
* @return string formatted number
*/
public function unNumberFormat($val)
{
$params = $this->getParams();
if (!$params->get('field_use_number_format', false) ||
is_null($val))
{
return $val;
}
// if we're copying a row, format won't be applied
$formModel = $this->getFormModel();
if (isset($formModel->formData) &&
array_key_exists('fabrik_copy_from_table',
$formModel->formData))
{
return $val;
}
// Might think about rounding to decimal_length, but for now let MySQL do
it
$decimalLength = (int) $params->get('decimal_length', 2);
// Swap dec and thousand seps back to Normal People Decimal Format!
$decimalSep = $params->get('field_decimal_sep',
'.');
$thousandSep = $params->get('field_thousand_sep',
',');
// Workaround for params not letting us save just a space!
if ($thousandSep == '#32')
{
$thousandSep = ' ';
}
else if ($thousandSep == '#00')
{
$thousandSep = '';
}
$val = str_replace($thousandSep, '', $val);
$val = str_replace($decimalSep, '.', $val);
return $val;
}
/**
* Recursively get all linked children of an element
*
* @param int $id element id
*
* @return array
*/
public function getElementDescendents($id = 0)
{
if (empty($id))
{
$id = $this->id;
}
$db = FabrikWorker::getDbo(true);
$query = $db->getQuery(true);
$query->select('id')->from('#__fabrik_elements')->where('parent_id
= ' . (int) $id);
$db->setQuery($query);
$kids = $db->loadObjectList();
$all_kids = array();
foreach ($kids as $kid)
{
$all_kids[] = $kid->id;
$all_kids = array_merge($this->getElementDescendents($kid->id),
$all_kids);
}
return $all_kids;
}
/**
* Get the actual table name to use when building select queries
* so if in a joined group get the joined to table's name otherwise
return the
* table's db table name
*
* @return string
*/
protected function actualTableName()
{
if (isset($this->actualTable))
{
return $this->actualTable;
}
$groupModel = $this->getGroup();
if ($groupModel->isJoin())
{
$joinModel = $groupModel->getJoinModel();
return $joinModel->getJoin()->table_join;
}
$listModel = $this->getListModel();
$this->actualTable = $listModel->getTable()->db_table_name;
return $this->actualTable;
}
/**
* When creating crud query in tableModel::storeRow() each element has the
chance
* to alter the row id - used by sugarid plugin to fudge rowid
*
* @param string &$rowId row id
*
* @deprecated - not used
*
* @return void
*/
public function updateRowId(&$rowId)
{
}
/**
* Fabrik3: moved to Admin Element Model
*
* @deprecated - not used
*
* @return string table name
*/
protected function getRepeatElementTableName()
{
}
/**
* Is the element a repeating element
*
* @return bool
*/
public function isRepeatElement()
{
return $this->isJoin();
}
/**
* fabrik3: moved to Admin Element Model
* if repeated element we need to make a joined db table to store repeated
data in
*
* @depreciated
*
* @return void
*/
public function createRepeatElement()
{
}
/**
* get the element's associated join model
*
* @return FabrikFEModelJoin join model
*/
public function getJoinModel()
{
if (is_null($this->joinModel))
{
$this->joinModel =
Factory::getApplication()->bootComponent('com_fabrik')->getMVCFactory()->createModel('Join',
'FabrikFEModel');
// $$$ rob ensure we load the join by asking for the parents id, but
then ensure we set the element id back to this elements id
$this->joinModel->getJoinFromKey('element_id',
$this->getParent()->id);
$this->joinModel->getJoin()->element_id =
$this->getElement()->id;
}
return $this->joinModel;
}
/**
* When saving an element pk we need to update any join which has the same
params->pk
*
* @param string $oldName (previous element name)
* @param string $newName (new element name)
*
* @since 3.0.6
*
* @return void
*/
public function updateJoinedPks($oldName, $newName)
{
$db = FabrikWorker::getDbo(true);
$item = $this->getListModel()->getTable();
$query = $db->getQuery(true);
// Update linked lists id.
$query->update('#__fabrik_joins')->set('table_key =
' . $db->q($newName))
->where('join_from_table = ' .
$db->q($item->db_table_name))->where('table_key = ' .
$db->q($oldName));
$db->setQuery($query);
$db->execute();
// Update join pk parameter
$query->clear();
$query->select('id')->from('#__fabrik_joins')->where('table_join
= ' . $db->q($item->db_table_name));
$db->setQuery($query);
$ids = $db->loadColumn();
$testPk = $db->qn($item->db_table_name . '.' . $oldName);
$newPk = $db->qn($item->db_table_name . '.' . $newName);
foreach ($ids as $id)
{
$join = FabTable::getInstance('Join',
'FabrikTable');
$join->load($id);
$params = new Registry($join->params);
if ($params->get('pk') === $testPk)
{
$params->set('pk', $newPk);
$join->params = (string) $params;
$join->store();
}
}
}
/**
* Is the element a join
*
* @return bool
*/
public function isJoin()
{
return $this->getParams()->get('repeat', false);
}
/**
* Used by inline edit table plugin
* If returns yes then it means that there are only two possible options
for the
* ajax edit, so we should simply toggle to the alternative value and show
the
* element rendered with that new value (used for yes/no element)
*
* @deprecated - only called in a deprecated element method
*
* @return bool
*/
protected function canToggleValue()
{
return false;
}
/**
* Encrypt an entire columns worth of data, used when updating an element
to encrypted
* with existing data in the column
*
* @return null
*/
public function encryptColumn()
{
$secret = $this->config->get('secret');
$listModel = $this->getListModel();
$db = $listModel->getDb();
$tbl = $this->actualTableName();
$name = $this->getElement()->name;
$db->setQuery("UPDATE $tbl SET " . $name . " =
AES_ENCRYPT(" . $name . ", " . $db->q($secret) .
")");
$db->execute();
}
/**
* Decrypt an entire columns worth of data, used when updating an element
from encrypted to decrypted
* with existing data in the column
*
* @return null
*/
public function decryptColumn()
{
// @TODO this query looks right but when going from encrypted blob to
decrypted field the values are set to null
$secret = $this->config->get('secret');
$listModel = $this->getListModel();
$db = $listModel->getDb();
$tbl = $this->actualTableName();
$name = $this->getElement()->name;
$db->setQuery("UPDATE $tbl SET " . $name . " =
AES_DECRYPT(" . $name . ", " . $db->q($secret) .
")");
$db->execute();
}
/**
* PN 19-Jun-11: Construct an element error string.
*
* @return string
*/
public function selfDiagnose()
{
$retStr = '';
$this->_db->setQuery("SELECT COUNT(*) FROM #__fabrik_groups
" . "WHERE (id = " . $this->element->group_id .
");");
$group_id = $this->_db->loadResult();
if (!$group_id)
{
$retStr = 'No valid group assignment';
}
elseif (!$this->element->plugin)
{
$retStr = 'No plugin';
}
elseif (!$this->element->label)
{
$retStr = 'No element label';
}
else
{
$retStr = '';
}
return $retStr;
}
/**
* Shortcut to get plugin manager
*
* @since 3.0b
*
* @deprecated
*
* @return object plugin manager
*/
public function getPluginManager()
{
return FabrikWorker::getPluginManager();
}
/**
* When the element is a repeatable join (e.g. db join checkbox) then
figure out how many
* records have been selected
*
* @param array $data data
* @param object $oJoin join current join
*
* @since 3.0rc1
*
* @return int number of records selected
*/
public function getJoinRepeatCount($data, $oJoin)
{
return count(FArrayHelper::getValue($data, $oJoin->table_join .
'___id', array()));
}
/**
* When we do ajax requests from the element - as the plugin controller
uses the J dispatcher
* the element hasn't loaded up itself, so any time you have a
function onAjax_doSomething() call this
* helper function first to load up the element. Otherwise things like
parameters will not be loaded
*
* @since 3.0rc1
*
* @return null
*/
protected function loadMeForAjax()
{
$input = $this->app->getInput();
$this->form =
Factory::getApplication()->bootComponent('com_fabrik')->getMVCFactory()->createModel('Form',
'FabrikFEModel');
$formId = $input->getInt('formid');
$this->form->setId($formId);
$this->setId($input->getInt('element_id'));
$this->list =
Factory::getApplication()->bootComponent('com_fabrik')->getMVCFactory()->createModel('List',
'FabrikFEModel');
$this->list->loadFromFormId($formId);
$table = $this->list->getTable(true);
$table->form_id = $formId;
$element = $this->getElement(true);
$this->setEditable($this->canUse('form'));
}
/**
* get the element's cell class
*
* @since 3.0.4
*
* @return string css classes
*/
public function getCellClass()
{
$params = $this->getParams();
$classes = array();
$classes[] = $this->getFullName(true, false);
$classes[] = 'fabrik_element';
$classes[] = 'fabrik_list_' .
$this->getListModel()->getId() . '_group_' .
$this->getGroupModel()->getId();
$c = $params->get('tablecss_cell_class',
'');
if ($c !== '')
{
$classes[] = $c;
}
return implode(' ', $classes);
}
/**
* get the elements list heading class
*
* @since 3.0.4
*
* @return string css classes
*/
public function getHeadingClass()
{
$classes = array();
$classes[] = 'fabrik_ordercell';
$classes[] = $this->getFullName(true, false);
$classes[] = $this->getElement()->id . '_order';
$classes[] = 'fabrik_list_' .
$this->getListModel()->getId() . '_group_' .
$this->getGroupModel()->getId();
$classes[] =
$this->getParams()->get('tablecss_header_class');
return implode(' ', $classes);
}
/**
* convert XML format data into fabrik data (used by web services)
*
* @param mixed $v data
*
* @return mixed data
*/
public function fromXMLFormat($v)
{
return $v;
}
/**
* Allows the element to pre-process a rows data before and join merging
of rows
* occurs. Used in calc element to do calcs on actual row rather than
merged row
*
* @param string $data elements data for the current row
* @param object $row current row's data
*
* @since 3.0.5
*
* @return string formatted value
*/
public function preFormatFormJoins($data, $row)
{
return $data;
}
/**
* Return an array of parameter names which should not get updated if a
linked element's parent is saved
* notably any parameter which references another element id should be
returned in this array
* called from admin element model updateChildIds()
* see cascadingdropdown element for example
*
* @return array parameter names to not alter
*/
public function getFixedChildParameters()
{
return array();
}
/**
* Set row class
*
* @param array &$data row data to set class for
*
* @return null
*/
public function setRowClass(&$data)
{
// first run plugins, which may set row class
$listModel = $this->getListModel();
$args = new stdClass;
$args->rowClass = '';
$args->data = $data;
$pluginResults =
\Fabrik\Helpers\Worker::getPluginManager()->runPlugins(
'onElementSetRowClass',
$listModel,
'list',
$this,
$args
);
// now check built in "use as row class"
$rowClass = $this->getParams()->get('use_as_row_class');
$col = $this->getFullName(true, false);
$rawCol = $col . '_raw';
foreach ($data as $groupKey => $group)
{
for ($i = 0; $i < count($group); $i++)
{
if ($rowClass == 1)
{
$c = false;
if (isset($data[$groupKey][$i]->data->$rawCol))
{
$c = $data[$groupKey][$i]->data->$rawCol;
}
elseif (isset($data[$groupKey][$i]->data->$col))
{
$c = $data[$groupKey][$i]->data->$col;
}
if ($c !== false)
{
// added noRowClass and rowClass for use in div templates that need
to split those out
if (!isset($data[$groupKey][$i]->noRowClass))
{
$data[$groupKey][$i]->noRowClass =
$data[$groupKey][$i]->class;
}
if (!isset($data[$groupKey][$i]->rowClasses))
{
$data[$groupKey][$i]->rowClasses = [];
}
$data[$groupKey][$i]->rowClasses[] = FabrikString::getRowClass($c,
$this->element->name);
// plugins may have already created a rowClass
if (isset($data[$groupKey][$i]->pluginRowClass))
{
$data[$groupKey][$i]->rowClass =
$data[$groupKey][$i]->pluginRowClass . ' ' . implode('
', $data[$groupKey][$i]->rowClasse);
$data[$groupKey][$i]->class =
$data[$groupKey][$i]->noRowClass . ' ' .
$data[$groupKey][$i]->rowClass;
}
else
{
$data[$groupKey][$i]->rowClass = implode(' ',
$data[$groupKey][$i]->rowClasses);
$data[$groupKey][$i]->class =
$data[$groupKey][$i]->noRowClass . ' ' .
$data[$groupKey][$i]->rowClass;
}
}
}
else
{
// plugins may have already created a rowClass
if (isset($data[$groupKey][$i]->pluginRowClass))
{
// added noRowClass and rowClass for use in div templates that need
to split those out
if (!isset($data[$groupKey][$i]->noRowClass))
{
$data[$groupKey][$i]->noRowClass =
$data[$groupKey][$i]->class;
}
$data[$groupKey][$i]->class =
$data[$groupKey][$i]->noRowClass . ' ' .
$data[$groupKey][$i]->pluginRowClass;
}
}
}
}
}
/**
* Set row class
*
* @param object $element element object
*
* @return null
*/
public function getRowClassRO($element)
{
$rowClass = $this->getParams()->get('use_as_row_class',
'0');
if ($rowClass === '1')
{
return FabrikString::getRowClass($element->value,
$this->element->name);
}
return '';
}
/**
* Unset the element models access
*
* @return null
*/
public function clearAccess()
{
unset($this->access);
}
/**
* Forces reset of defaults, etc.
*
* @return null
*/
public function reset()
{
$this->defaults = null;
}
/**
* Clear default values, need to call this if we change an elements value
in any of the formData
* arrays during submission process.
*
* @return null
*/
public function clearDefaults()
{
$this->defaults = null;
}
/**
* Should the 'label' field be quoted. Overridden by
databasejoin and extended classes,
* which may use a CONCAT'ed label which mustn't be quoted.
*
* @since 3.0.6
*
* @return boolean
*/
protected function quoteLabel()
{
return true;
}
/**
* Create the where part for the query that selects the list options
*
* @param array $data Current row data to use in
placeholder replacements
* @param bool $incWhere Should the additional user
defined WHERE statement be included
* @param string $thisTableAlias Db table alias
* @param array $opts Options
* @param JDatabaseQuery|bool $query Append where to
JDatabaseQuery object or return string (false)
*
* @return string|JDatabaseQuery
*/
protected function buildQueryWhere($data = array(), $incWhere = true,
$thisTableAlias = null, $opts = array(), $query = false)
{
return '';
}
/**
* Is the element set to always render in list contexts
*
* @param bool $not_shown_only Not sure???
*
* @return bool
*/
public function isAlwaysRender($not_shown_only = true)
{
$params = $this->getParams();
$element = $this->getElement();
$alwaysRender = $params->get('always_render',
'0');
return $not_shown_only ? $element->show_in_list_summary == 0
&& $alwaysRender == '1' : $alwaysRender == '1';
}
/**
* Called at end of form record save. Used for many-many join elements to
save their data
*
* @param array &$data Form data
*
* @since 3.1rc1
*
* @return void
*/
public function onFinalStoreRow(&$data)
{
if (!$this->isJoin())
{
return;
}
$groupModel = $this->getGroupModel();
$listModel = $this->getListModel();
$db = $listModel->getDb();
$query = $db->getQuery(true);
$formData =& $this->getFormModel()->formDataWithTableName;
// I set this to raw for cdd.
$name = $this->getFullName(true, false);
$ajaxSubmit =
$this->app->getInput()->get('fabrik_ajax');
$rawName = $name . '_raw';
$shortName = $this->getElement()->name;
$join = $this->getJoin();
/*
* The submitted element's values
*
* NOTE - if we are coming from a list row copy, the _raw data is
actually the map table
* id's, not the FK's, because we used form model getData to
load it. So we need to stuff the
* actual FK's (in the _id array) back into _raw
*/
if (array_key_exists('fabrik_copy_from_table',
$formData))
{
$idName = $name . '_id';
$idValues = FArrayHelper::getValue($formData, $idName,
array());
if (!empty($idValues))
{
$formData[$rawName] = $idValues;
}
}
$d = FArrayHelper::getValue($formData, $rawName,
FArrayHelper::getValue($formData, $name));
// set $emptyish to false so if no selection, we don't save a bogus
empty row
$allJoinValues = FabrikWorker::JSONtoData($d, true, false);
if ($groupModel->isJoin())
{
$groupJoinModel = $groupModel->getJoinModel();
$k = str_replace('`', '',
str_replace('.', '___',
$groupJoinModel->getJoin()->params->get('pk')));
$parentIds = (array) $formData[$k];
}
else
{
$k = 'rowid';
$parentIds = empty($allJoinValues) ? array() :
FArrayHelper::array_fill(0, count($allJoinValues), $formData[$k]);
}
$paramsKey = $this->getJoinParamsKey();
$allParams = (array) FArrayHelper::getValue($formData, $paramsKey,
array());
$allParams = array_values($allParams);
$i = 0;
$idsToKeep = array();
foreach ($parentIds as $parentId)
{
if (!array_key_exists($parentId, $idsToKeep))
{
$idsToKeep[$parentId] = array();
}
if ($groupModel->canRepeat())
{
$joinValues = FArrayHelper::getValue($allJoinValues, $i, array());
}
else
{
$joinValues = $allJoinValues;
}
$joinValues = (array) $joinValues;
// Get existing records
if ($parentId == '')
{
$ids = array();
}
else
{
$query->clear();
$query->select('id, ' .
$shortName)->from($join->table_join)->where('parent_id =
' . $parentId);
$db->setQuery($query);
$ids = (array) $db->loadObjectList($shortName);
}
// If doing an ajax form submit and the element is an ajax file upload
then its data is different.
if (get_class($this) === 'PlgFabrik_ElementFileupload'
&& $ajaxSubmit)
{
$allParams = array_key_exists('crop', $joinValues) ?
array_values($joinValues['crop']) : array();
$joinValues = array_key_exists('id', $joinValues) ?
array_keys($joinValues['id']) : $joinValues;
}
foreach ($joinValues as $jIndex => $jid)
{
$record = new stdClass;
$record->parent_id = $parentId;
$fkVal = FArrayHelper::getValue($joinValues, $jIndex);
$record->$shortName = $fkVal;
$record->params = FArrayHelper::getValue($allParams, $jIndex);
// Stop notice with file-upload where fkVal is an array
if (array_key_exists($fkVal, $ids))
{
$record->id = $ids[$fkVal]->id;
$idsToKeep[$parentId][] = $record->id;
}
else
{
$record->id = 0;
}
if ($record->id == 0)
{
$ok = $listModel->insertObject($join->table_join,
$record);
$lastInsertId = $listModel->getDb()->insertid();
if (!$this->allowDuplicates)
{
$newId = new stdClass;
$newId->id = $lastInsertId;
$newId->$shortName = $record->$shortName;
$ids[$record->$shortName] = $newId;
}
$idsToKeep[$parentId][] = $lastInsertId;
}
else
{
$ok = $listModel->updateObject($join->table_join, $record,
'id');
}
if (!$ok)
{
throw new RuntimeException('Didn\'t save dbjoined repeat
element');
}
}
$i++;
}
// Delete any records that were unselected.
$this->deleteDeselectedItems($idsToKeep, $k);
}
/**
* For joins:
* Get the key which contains the linking tables primary key values.
*
* @param bool $step Use step '___' or '.' in full
name
*
* @return boolean|string
*/
public function getJoinIdKey($step = true)
{
if (!$this->isJoin())
{
return false;
}
if ($this->getGroupModel()->isJoin())
{
$join = $this->getJoin();
$idKey = $join->table_join . '___id';
}
else
{
$idKey = $this->getFullName($step, false) . '___id';
}
return $idKey;
}
/**
* For joins:
* Get the key which contains the linking tables params values.
*
* @param bool $step Use step '___' or '.' in full
name
*
* @return boolean|string
*/
public function getJoinParamsKey($step = true)
{
if (!$this->isJoin())
{
return false;
}
if ($this->getGroupModel()->isJoin())
{
$join = $this->getJoin();
$paramsKey = $join->table_join . '___params';
}
else
{
$paramsKey = $this->getFullName($step, false) .
'___params';
}
return $paramsKey;
}
/**
* Delete any deselected items from the cross-reference table
*
* @param array $idsToKeep List of ids to keep
* @param string $k Parent record key name
*
* @return void
*/
protected function deleteDeselectedItems($idsToKeep, $k)
{
$listModel = $this->getListModel();
$join = $this->getJoin();
$db = $listModel->getDb();
$query = $db->getQuery(true);
if (empty($idsToKeep))
{
$formData = $this->getFormModel()->formDataWithTableName;
$parentId = $formData[$k];
if (!empty($parentId))
{
$query->delete($join->table_join)->where('parent_id =
' . $db->q($parentId));
$db->setQuery($query);
$db->execute();
}
}
else
{
foreach ($idsToKeep as $parentId => $ids)
{
$query->clear();
$query->delete($join->table_join)->where($db->quoteName('parent_id')
. ' = ' . $parentId);
if (!empty($ids))
{
$query->where($db->quoteName('id') . ' NOT IN (
' . implode(',', $ids) . ')');
}
$db->setQuery($query);
$db->execute();
}
}
}
/**
* Return an internal validation icon - e.g. for Password element
*
* @return string
*/
public function internalValidationIcon()
{
return '';
}
/**
* Return internal validation hover text - e.g. for Password element
*
* @return string
*/
public function internalValidationText()
{
return '';
}
/**
* Return JS event required to trigger a 'change', usually
'change',
* but some elements need a 'click' or a 'blur'. Used
initially by CDD element.
* NOTE - there is also a getChangeEvent() in element.js, which should
return the same thing, Don't Ask.
*
* @return string
*/
public function getChangeEvent()
{
return 'change';
}
/**
* Returns class name to use for advanced select (no surrounding space),
or empty string
*
* @return bool
*/
public function getAdvancedSelectClass()
{
$fbConfig = ComponentHelper::getParams('com_fabrik');
$params = $this->getParams();
$advancedClass = '';
$globalAdvanced = (int) $fbConfig->get('advanced_behavior',
'0');
if ($globalAdvanced !== 0)
{
$advancedClass = $params->get('advanced_behavior',
'0') == '1' || $globalAdvanced === 2 ?
'advancedSelect' : '';
}
return $advancedClass;
}
/**
* Get the element's LayoutInterface file
* Its actually an instance of LayoutFile which inverses the ordering
added include paths.
* In LayoutFile the addedPath takes precedence over the default paths,
which makes more sense!
*
* @param string $type form/details/list
* @param array $paths Optional paths to add as includes
*
* @return LayoutFile
*/
public function getLayout($type, $paths = array(), $options = array())
{
$defaultOptions = array('debug' => false,
'component' => 'com_fabrik', 'client'
=> 'site');
$options = array_merge($defaultOptions, $options);
$basePath = $this->layoutBasePath();
$layout = new LayoutFile('fabrik-element-' .
$this->getPluginName() . '-' . $type, $basePath, $options);
foreach ($paths as $path)
{
$layout->addIncludePath($path);
}
$layout->addIncludePaths(JPATH_SITE . '/layouts');
$layout->addIncludePaths(JPATH_THEMES . '/' .
$this->app->getTemplate() . '/html/layouts');
$layout->addIncludePaths(JPATH_THEMES . '/' .
$this->app->getTemplate() . '/html/layouts/com_fabrik');
$layout->addIncludePaths(JPATH_THEMES . '/' .
$this->app->getTemplate() .
'/html/layouts/com_fabrik/element/');
// Custom per element layout...
$layout->addIncludePaths(JPATH_THEMES . '/' .
$this->app->getTemplate() .
'/html/layouts/com_fabrik/element/' . $this->getFullName(true,
false));
// Custom per template layout
$view = $this->getFormModel()->isEditable() ? 'form' :
'details';
$layout->addIncludePaths(COM_FABRIK_FRONTEND . '/views/'.
$view . '/tmpl/' . $this->getFormModel()->getTmpl() .
'/layouts/element/');
$layout->addIncludePaths(COM_FABRIK_FRONTEND . '/views/'.
$view . '/tmpl/' . $this->getFormModel()->getTmpl() .
'/layouts/element/' . $this->getFullName(true, false));
return $layout;
}
/**
* Get the LayoutInterface base path for the plugin's layout files.
*
* @return string
*/
protected function layoutBasePath()
{
return COM_FABRIK_BASE . '/plugins/fabrik_element/' .
$this->getPluginName() . '/layouts';
}
/**
* Get lower case plugin name based off class name:
* E.g. PlgFabrik_ElementDatabasejoin => databasejoin
*
* @return string
*/
protected function getPluginName()
{
$name = get_class($this);
if (strstr($name, '\\'))
{
$name = explode('\\', $name);
$name = array_pop($name);
}
return
strtolower(StringHelper::str_ireplace('PlgFabrik_Element',
'', $name));
}
/**
* Validate teh element against a Joomla form Rule
*
* @param string $type Rule type e.g. 'password'
* @param mixed $value Value to validate
* @param mixed $path Optional path to load teh rule from
*
* @throws Exception
*
* @return bool
*/
protected function validateJRule($type, $value, $path = null)
{
if (!is_null($path))
{
FormHelper::addRulePath($path);
}
$rule = FormHelper::loadRuleType($type, true);
$xml = new SimpleXMLElement('<xml></xml>');
$this->lang->load('com_users');
if (!$rule->test($xml, $value))
{
$this->validationError = '';
foreach ($this->app->getMessageQueue() as $i => $msg)
{
if ($msg['type'] === 'warning')
{
$this->validationError .= $msg['message'] . '<br
/>';
}
}
FabrikWorker::killMessage($this->app, 'warning');
return false;
}
return true;
}
/**
* Say an element is in a repeat group, this method gets that repeat
groups primary
* key value.
*
* @param int $repeatCounter
*
* @since 3.3.2
*
* @return mixed False if not in a join
*/
protected function getJoinedGroupPkVal($repeatCounter = 0)
{
$groupModel = $this->getGroupModel();
if (!$groupModel->isJoin())
{
return false;
}
$formModel = $this->getFormModel();
$joinModel = $groupModel->getJoinModel();
$elementModel = $formModel->getElement($joinModel->getForeignID());
return $elementModel->getValue($formModel->data, $repeatCounter);
}
/**
* Is the element published.
*
* @return boolean
*/
public function isPublished()
{
return $this->getElement()->published === 1;
}
/**
* Add any jsJLayout templates to Fabrik.jLayouts js object.
*
* @return void
*/
public function jsJLayout()
{
}
/**
* Give elements a chance to reset data after a failed validation. For
instance, file upload element
* needs to reset the values as they aren't submitted with the form
*
* @param $data array form data
*/
public function setValidationFailedData(&$data)
{
return;
}
/**
* Swap values for labels
*
* @param array &$d Data
*
* @return void
*/
protected function swapValuesForLabels(&$d)
{
$groups = $this->getFormModel()->getGroupsHiarachy();
foreach (array_keys($groups) as $gkey)
{
$group = $groups[$gkey];
$elementModels = $group->getPublishedElements();
for ($j = 0; $j < count($elementModels); $j++)
{
$elementModel = $elementModels[$j];
$elKey = $elementModel->getFullName(true, false);
$v = FArrayHelper::getValue($d, $elKey);
if (is_array($v))
{
$origData = FArrayHelper::getValue($d, $elKey, array());
if (!array_key_exists($elKey . '_raw', $d))
{
$d[$elKey . '_raw'] = $origData;
}
foreach (array_keys($v) as $x)
{
$origVal = FArrayHelper::getValue($origData, $x);
$d[$elKey][$x] = $elementModel->getLabelForValue($v[$x], $origVal,
true);
}
}
else
{
$origData = FArrayHelper::getValue($d, $elKey);
if (!array_key_exists($elKey . '_raw', $d))
{
$d[$elKey . '_raw'] = $origData;
}
$d[$elKey] = $elementModel->getLabelForValue($v, $origData, true);
}
}
}
}
/**
* When running parseMessageForPlaceholder on data we need to set the
none-raw value of things like birthday/time
* elements to that stored in the listModel::storeRow() method
*
* @param array &$data Form data
* @param int $repeatCounter Repeat group counter
*
* @return void
*/
protected function setStoreDatabaseFormat(&$data, $repeatCounter = 0)
{
$formModel = $this->getFormModel();
$groups = $formModel->getGroupsHiarachy();
foreach ($groups as $groupModel)
{
$elementModels = $groupModel->getPublishedElements();
foreach ($elementModels as $elementModel)
{
$fullKey = $elementModel->getFullName(true, false);
$value = $data[$fullKey];
if ($groupModel->canRepeat() && is_array($value))
{
foreach ($value as $k => $v)
{
$data[$fullKey][$k] = $elementModel->storeDatabaseFormat($v,
$data);
}
}
else
{
$data[$fullKey] = $elementModel->storeDatabaseFormat($value,
$data);
}
}
}
}
/* Convert the older BS2 & BS3 column classes to BS5
*
* @param string $oldClass class to convert
*
* @return string BS5 class
*/
protected function getBsClass($oldClass='') {
$classList = [
'input-mini' => 'col-sm-2',
'input-small' => 'col-sm-4',
'input-medium' => 'col-sm-6',
'input-large' => 'col-sm-8',
'input-xlarge' => 'col-sm-10',
'input-xxlarge' => 'col-sm-12',
'input-block-level' => 'col-sm-12',
];
// Some plugins have no format setting, but they should. If not then we
use col-sm-10, 2 grids are used for the label
$bsClass = $oldClass == '' ?
$this->getParams()->get('bootstrap_class',
'col-sm-10') : $oldClass;
// check for old col-sm and span classes
$bsClass = str_replace(['col-md-', 'span'],
'col-sm-', $bsClass);
if (array_key_exists($bsClass, $classList)) {
$bsClass = $classList[$bsClass];
}
if (strlen($bsClass) > 0) $bsClass = ' '.$bsClass;
return $bsClass;
}
}