Spade
Mini Shell
| Directory:~$ /proc/self/root/home/lmsyaran/public_html/joomla5/plugins/fabrik_element/calc/ |
| [Home] [System Details] [Kill Me] |
<?php
/**
* Plugin element to render field with PHP calculated value
*
* @package Joomla.Plugin
* @subpackage Fabrik.element.calc
* @copyright Copyright (C) 2005-2020 Media A-Team, Inc. - All rights
reserved.
* @license GNU/GPL http://www.gnu.org/copyleft/gpl.html
*/
// No direct access
defined('_JEXEC') or die('Restricted access');
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Model\BaseDatabaseModel;
use Joomla\CMS\Filter\InputFilter;
use Joomla\Utilities\ArrayHelper;
use Fabrik\Helpers\Php;
/**
* Plugin element to render field with PHP calculated value
*
* @package Joomla.Plugin
* @subpackage Fabrik.element.calc
* @since 3.0
*/
class PlgFabrik_ElementCalc extends PlgFabrik_Element
{
/**
* 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, true, true);
$this->default = $default;
}
return $this->default;
}
/**
* Get value
*
* @param array $data Value
* @param int $repeatCounter Repeat group counter
*
* @return string
*/
private function _getV($data, $repeatCounter)
{
$w = new FabrikWorker;
$groupModel = $this->getGroup();
$name = $this->getFullName(true, false);
$params = $this->getParams();
// $$$ hugh - if we don't do this, we get the cached default from
the previous repeat
if ($repeatCounter > 0)
{
unset($this->default);
}
/**
* $$$ hugh - don't think we want to do this here, otherwise calc
gets run regardless of calc_on_save_only,
* it just won't get used if 'true'
* $default = $this->getDefaultValue($data, $repeatCounter);
*/
$default = '';
/**
* If viewing form or details view and calc set to always run then
return the $default
* which has had the calculation run on it.
*/
$task = $this->app->input->get('task', '');
if (!$params->get('calc_on_save_only', true) || $task ==
'form.process' || $task == 'process')
{
// $default = $this->getDefaultValue($data, $repeatCounter);
$this->swapValuesForLabels($data);
$this->setStoreDatabaseFormat($data, $repeatCounter);
$default =
$w->parseMessageForRepeats($params->get('calc_calculation'),
$data, $this, $repeatCounter);
$default = $w->parseMessageForPlaceHolder($default, $data, true,
true);
$formModel = $this->getFormModel();
// $$$ hugh - standardizing on $data but need need $d here for backward
compat
$d = $data;
FabrikWorker::clearEval();
$res = Php::Eval(['code' => $default,
'vars'=>['data'=>$data, 'd'=>$data,
'repeatCounter'=>$repeatCounter,
'formModel'=>$formModel]]);
FabrikWorker::logEval($res, 'Eval exception : ' .
$this->getElement()->name . ' (id ' . $this->getId() .
')::_getV() : ' .
str_replace('%','%%',$default) . ' : %s');
return $res;
}
$rawName = $name . '_raw';
if ($groupModel->isJoin())
{
$data = (array) $data;
if (array_key_exists($name, $data))
{
$data[$name] = (array) $data[$name];
}
if ($groupModel->canRepeat())
{
if (array_key_exists($name, $data) &&
array_key_exists($repeatCounter, $data[$name]))
{
$default = $data[$name][$repeatCounter];
}
else
{
// erm ... ?? ... ^^
if (array_key_exists($name, $data) &&
array_key_exists($repeatCounter, $data[$name]))
{
$default = $data[$name][$repeatCounter];
}
}
}
else
{
if (array_key_exists($name, $data))
{
$default = $data[$name];
}
else
{
if (array_key_exists($rawName, $data))
{
$default = $data[$rawName];
}
}
}
}
else
{
// When called from getFilterArray via getROElement, $data doesn't
exist
// (i.e. when specified as a table___name=foo in a content plugin)
if (is_array($data))
{
if (array_key_exists($name, $data))
{
$default = $data[$name];
}
else
{
if (array_key_exists($rawName, $data))
{
$default = $data[$rawName];
}
}
}
}
return $default;
}
/**
* 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
*
* @return string value
*/
public function getValue($data, $repeatCounter = 0, $opts = array())
{
if (!isset($this->defaults) || is_null($this->defaults))
{
$this->defaults = array();
}
if (!array_key_exists($repeatCounter, $this->defaults))
{
$element = $this->getElement();
$element->default = $this->_getV($data, $repeatCounter);
$formModel = $this->getFormModel();
// Stops this getting called from form validation code as it messes up
repeated/join group validations
if (array_key_exists('runplugins', $opts) &&
$opts['runplugins'] == 1)
{
FabrikWorker::getPluginManager()->runPlugins('onGetElementDefault',
$formModel, 'form', $this);
}
if (is_array($element->default))
{
$element->default = implode(',', $element->default);
}
$this->defaults[$repeatCounter] = $element->default;
}
return $this->defaults[$repeatCounter];
}
/**
* run on formModel::setFormData()
* Appends the calculation to the form's data when the form is
submitted
*
* @param int $c Repeat group counter
*
* @return void
*/
public function preProcess($c)
{
$form = $this->getFormModel();
$data = unserialize(serialize($form->formData));
/**
* get the key name in dot format for updateFormData method
* $$$ hugh - added $rawKey stuff, otherwise when we did "$key .
'_raw'" in the updateFormData
* below on repeat data, it ended up in the wrong format, like
join.XX.table___element.0_raw
*/
$key = $this->getFullName(true, false);
$rawKey = $key . '_raw';
$this->swapValuesForLabels($data);
$res = $this->_getV($data, $c);
// Create arrays for calc values as needed
if (is_array($data[$key]))
{
$data[$key][$c] = $res;
}
elseif (!isset($data[$key]) && $c == 0)
{
$data[$key] = $res;
}
else
{
$group = $this->getGroup();
if ($group->canRepeat())
{
if (!isset($data[$key]))
{
$data[$key] = array();
}
else
{
$data[$key] = array($data[$key]);
}
$data[$key][$c] = $res;
}
else
{
$data[$key] = $res;
}
}
$form->updateFormData($key, $data[$key]);
$form->updateFormData($rawKey, $data[$key]);
}
/**
* 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)
{
$params = $this->getParams();
$element_data = $data;
if ($params->get('calc_on_save_only', 0))
{
$element_data = $this->getFormattedValue($element_data);
return parent::preFormatFormJoins($element_data, $row);
}
else
{
$element = $this->getElement();
$cal = $params->get('calc_calculation', '');
$listModel = $this->getlistModel();
$formModel = $this->getFormModel();
$data = ArrayHelper::fromObject($row);
$data['rowid'] = $data['__pk_val'];
$data['fabrik'] = $formModel->getId();
// $$$ Paul - Because this is run on List rows before repeat-group
merges, repeat group placeholders are OK.
// $$$ hugh - standardizing on $data but need need $d here for backward
compat
$d = $data;
$w = new FabrikWorker;
$cal = $w->parseMessageForPlaceHolder($cal, $data, true, true);
FabrikWorker::clearEval();
$res = Php::Eval(['code' => $cal,
'vars'=>['data'=>$data, 'd'=>$data,
'formModel'=>$formModel]]);
FabrikWorker::logEval($res, 'Eval exception : ' .
$element->name . ' (id ' . $this->getId() .
')::preFormatFormJoins() : ' .
str_replace('%','%%',$cal) . ' : %s');
$res = $this->getFormattedValue($res);
// $$$ hugh - need to set _raw, might be needed if (say) calc is being
used as 'use_as_row_class'
// See comments in formatData() in table model, we might could move this
to a renderRawListData() method.
$raw_name = $this->getFullName(true, false) . '_raw';
$row->$raw_name = str_replace(GROUPSPLITTER, ',', $res);
return parent::preFormatFormJoins($res, $row);
}
}
/**
* 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 value
*/
public function renderListData_csv($data, &$thisRow)
{
$val = $this->renderListData($data, $thisRow);
$col = $this->getFullName(true, false);
$raw = $col . '_raw';
$thisRow->$raw = $val;
return $val;
}
/**
* 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)
{
$params = $this->getParams();
$element = $this->getElement();
$this->getFormModel()->getData();
$data = $this->getFormModel()->data;
$value = $this->getFormattedValue($this->getValue($data,
$repeatCounter));
$name = $this->getHTMLName($repeatCounter);
$id = $this->getHTMLId($repeatCounter);
$str = array();
if ($this->canView())
{
if (!$this->isEditable())
{
$value = $this->replaceWithIcons($value);
$str[] = $value;
}
else
{
$layout = $this->getLayout('form');
$layoutData = new stdClass;
$layoutData->id = $id;
$layoutData->name = $name;
$layoutData->height = $element->height;
$layoutData->value = $value;
$layoutData->cols = $element->width;
$layoutData->rows = $element->height;
$str[] = $layout->render($layoutData);
}
}
else
{
// Make a hidden field instead
$str[] = '<input type="hidden"
class="fabrikinput" name="' . $name . '"
id="' . $id . '" value="' . $value .
'" />';
}
if (in_array($this->app->input->get('format',
'html'), array('html', 'partial')))
{
$opts = array('alt' =>
Text::_('PLG_ELEMENT_CALC_LOADING'), 'style' =>
'display:none;padding-left:10px;', 'class' =>
'loader');
$str[] = FabrikHelperHTML::image('ajax-loader.gif',
'form', @$this->tmpl, $opts);
}
return implode("\n", $str);
}
/**
* Returns javascript which creates an instance of the class defined in
formJavascriptClass()
*
* @param int $repeatCounter Repeat group counter
*
* @return array
*/
public function elementJavascript($repeatCounter)
{
$id = $this->getHTMLId($repeatCounter);
$opts = $this->getElementJSOptions($repeatCounter);
$params = $this->getParams();
$calc = $params->get('calc_calculation');
$obs = [];
$opts->ajax = $params->get('calc_ajax', 0) == 0 ? false :
true;
if ($opts->ajax)
{
if ($params->get('calc_ajax_observe_all', '0')
=== '0' )
{
$obs = '';
if ( !empty($params->get('calc_ajax_observe')) ) {
$obs = preg_replace('#\s#', '',
$params->get('calc_ajax_observe'));
}
$obs = explode(',', $obs);
if (preg_match_all("/{[^}\s]+}/i", $calc, $matches) !== 0)
{
$matches = $matches[0];
$obs = array_merge($obs, $matches);
}
foreach ($obs as $key => &$m)
{
if (empty($m))
{
unset($obs[$key]);
continue;
}
$m = str_replace(array('{', '}'), '',
$m);
// $$$ hugh - we need to knock any _raw off, so JS can match actual
element ID
$m = preg_replace('#_raw$#', '', $m);
}
}
else
{
$formModel = $this->getFormModel();
$groups = $formModel->getGroupsHiarachy();
foreach ($groups as $groupModel)
{
$elementModels = $groupModel->getPublishedElements();
foreach ($elementModels as $elementModel)
{
if ($elementModel->getElement()->plugin !== 'calc')
{
$obs[] = $elementModel->getFullName(true, false);
}
}
}
}
}
$opts->observe = array_values(array_unique($obs));
$opts->calcOnLoad = (bool) $params->get('calc_on_load',
false);
$opts->calcOnRepeat = (bool)
$params->get('calc_on_repeat', false);
$opts->id = $this->id;
return array('FbCalc', $id, $opts);
}
/**
* Perform calculation from ajax request
*
* @return void
*/
public function onAjax_calc()
{
$input = $this->app->input;
$this->setId($input->getInt('element_id'));
$this->loadMeForAjax();
$params = $this->getParams();
$w = new FabrikWorker;
$filter = InputFilter::getInstance();
$d = $filter->clean($_REQUEST, 'array');
$formModel = $this->getFormModel();
$repeatCounter =
$this->app->input->get('repeatCounter', '0');
$formModel->addEncrytedVarsToArray($d);
$this->getFormModel()->data = $d;
$this->swapValuesForLabels($d);
$calc = $params->get('calc_calculation');
$this->setStoreDatabaseFormat($d);
// $$$ hugh - trying to standardize on $data so scripts know where data
is
$data = $d;
$calc = $w->parseMessageForRepeats($calc, $data, $this,
$repeatCounter);
$calc = $w->parseMessageForPlaceHolder($calc, $d);
FabrikWorker::clearEval();
$c = Php::Eval(['code' => $calc,
'vars'=>['data'=>$data, 'd'=>$data,
'repeatCounter'=>$repeatCounter,
'formModel'=>$formModel]]);
FabrikWorker::logEval($c, 'Caught exception on ajax eval of calc
' . $this->getElement()->name . ': %s');
$c = preg_replace('#(\/\*.*?\*\/)#', '', $c);
$c = $this->getFormattedValue($c);
echo urldecode($c);
}
/**
* Get sum query
*
* @param FabrikFEModelList &$listModel List model
* @param array $labels Label
*
* @return string
*/
protected function getSumQuery(&$listModel, $labels = array())
{
$fields = $listModel->getDBFields($this->getTableName(),
'Field');
$name = $this->getElement()->name;
$field = FArrayHelper::getValue($fields, $name, false);
if ($field !== false && $field->Type == 'time')
{
$db = $listModel->getDb();
$label = count($labels) == 0 ? "'calc' AS label" :
'CONCAT(' . implode(', " & " , ',
$labels) . ') AS label';
$name = $this->getFullName(false, false);
$table = $listModel->getTable();
$joinSQL = $listModel->buildQueryJoin();
$whereSQL = $listModel->buildQueryWhere();
return "SELECT SEC_TO_TIME(SUM(TIME_TO_SEC($name))) AS value,
$label FROM " . $db->qn($table->db_table_name)
. " $joinSQL $whereSQL";
}
else
{
return parent::getSumQuery($listModel, $labels);
}
}
/**
* 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())
{
$fields = $listModel->getDBFields($this->getTableName(),
'Field');
$name = $this->getElement()->name;
$field = FArrayHelper::getValue($fields, $name, false);
if ($field !== false && $field->Type == 'time')
{
$db = $listModel->getDb();
$label = count($labels) == 0 ? "'calc' AS label" :
'CONCAT(' . implode(', " & " , ',
$labels) . ') AS label';
$name = $this->getFullName(false, false);
$table = $listModel->getTable();
$joinSQL = $listModel->buildQueryJoin();
$whereSQL = $listModel->buildQueryWhere();
return "SELECT SEC_TO_TIME(AVG(TIME_TO_SEC($name))) AS value,
$label FROM " . $db->qn($table->db_table_name)
. " $joinSQL $whereSQL";
}
else
{
return parent::getAvgQuery($listModel, $labels);
}
}
/**
* Get a query for our median query
*
* @param FabrikFEModelList &$listModel List
* @param array $labels Label
*
* @return string
*/
protected function getMedianQuery(&$listModel, $labels = array())
{
$fields = $listModel->getDBFields($this->getTableName(),
'Field');
$name = $this->getElement()->name;
$field = FArrayHelper::getValue($fields, $name, false);
if ($field !== false && $field->Type == 'time')
{
$db = $listModel->getDb();
$label = count($labels) == 0 ? "'calc' AS label" :
'CONCAT(' . implode(', " & " , ',
$labels) . ') AS label';
$name = $this->getFullName(false, false);
$table = $listModel->getTable();
$joinSQL = $listModel->buildQueryJoin();
$whereSQL = $listModel->buildQueryWhere();
return "SELECT SEC_TO_TIME(TIME_TO_SEC($name)) AS value, $label
FROM " . $db->qn($table->db_table_name)
. " $joinSQL $whereSQL";
}
else
{
return parent::getMedianQuery($listModel, $labels);
}
}
/**
* Get the sprintf format string
*
* @since 3.0.4
*
* @return string
*/
public function getFormatString()
{
$params = $this->getParams();
return $params->get('calc_format_string');
}
/**
* Get the formatted value
*
* @param $value
*
* @since 3.5
*/
public function getFormattedValue($value)
{
$format = $this->getFormatString();
if (!empty($format))
{
$value = sprintf($format, $value);
}
return $value;
}
/**
* Get JS code for ini element list js
* Overwritten in plugin classes
*
* @return string
*/
public function elementListJavascript()
{
$params = $this->getParams();
$id = $this->getHTMLId();
$list = $this->getlistModel()->getTable();
$opts = new stdClass;
$opts->listid = $list->id;
$opts->listRef = 'list_' .
$this->getlistModel()->getRenderContext();
$opts->formid = $this->getFormModel()->getId();
$opts->elid = $this->getElement()->id;
$opts->doListUpdate = $params->get('calc_on_save_only',
'1') == '0' &&
$params->get('calc_ajax', '0') == '1';
$opts = json_encode($opts);
return "new FbCalcList('$id', $opts);\n";
}
/**
* Update list data
*
* @return void
*/
public function onAjax_listUpdate()
{
$input = $this->app->input;
$listId = $input->getInt('listid');
$elId = $input->getInt('element_id');
$this->setId($elId);
$this->loadMeForAjax();
/** @var FabrikFEModelList $listModel */
$listModel = BaseDatabaseModel::getInstance('List',
'FabrikFEModel');
$listModel->setId($listId);
$data = $listModel->getData();
$return = new stdClass;
/**
* $$$ hugh ... no, we never need to store in this context. The
'calc_on_save_only' param simply dictates
* whether we re-calc when displaying the element, or just use the stored
value. So if calc_on_save_only is
* set, then when displaying in lists, we don't execute the calc, we
just used the stored value fro the database.
* And that logic is handled in _getV(), so we don't need to do the
$store stuff.
*/
$listRef = 'list_' . $listModel->getRenderContext() .
'_row_';
foreach ($data as $group)
{
foreach ($group as $row)
{
$key = $listRef . $row->__pk_val;
$row->rowid = $row->__pk_val;
$return->$key =
$this->getFormattedValue($this->_getV(ArrayHelper::fromObject($row),
0));
}
}
echo json_encode($return);
}
/**
* Turn form value into email formatted value
* $$$ hugh - I added this as for reasons I don't understand,
something to do with
* how the value gets calc'ed during preProcess, sometimes the calc
is "right" when
* it's submitted to the database, but wrong during form email plugin
processing. So
* I gave up trying to work out why, and now just re-calc it during
getEmailData()
*
* @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)
{
$params = $this->getParams();
if (!$params->get('calc_on_save_only', true))
{
$value = $this->_getV($data, $repeatCounter);
}
$value = $this->getFormattedValue($value);
return $value;
}
/**
* Get database field description
* For calc, as we have no idea what they will be storing, needs to be
TEXT.
*
* @return string db field type
*/
public function getFieldDescription()
{
if ($this->encryptMe())
{
return 'BLOB';
}
return 'TEXT';
}
/**
* 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.
*
* @param array $data Data to test against
* @param int $repeatCounter Repeat group #
*
* @return bool
*/
public function dataConsideredEmpty($data, $repeatCounter)
{
$parts = explode("\n", $data);
// see if all it contains is the "\n" and loader gif
added in render ...
if (count($parts) === 2)
{
if (empty($parts[0]) && strstr($parts[1],
'loader'))
{
return true;
}
}
return false;
}
}