Spade

Mini Shell

Directory:~$ /proc/self/root/home/lmsyaran/public_html/joomla5/plugins/fabrik_element/calc/
Upload File

[Home] [System Details] [Kill Me]
Current File:~$ //proc/self/root/home/lmsyaran/public_html/joomla5/plugins/fabrik_element/calc/calc.php

<?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;
    }
}