<?php
/**
* @version	$Id: formatter.php 16565 2017-07-16 15:21:40Z alex $
* @package	In-Portal
* @copyright	Copyright (C) 1997 - 2011 Intechnic. All rights reserved.
* @license      GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/

defined('FULL_PATH') or die('restricted access!');

class kFormatter extends kBase {

	/**
	 * Reference to category helper
	 *
	 * @var CategoryHelper
	 */
	var $_categoryHelper = null;

	/**
	 * Creates formatter instance
	 *
	 * @access public
	 */
	public function __construct()
	{
		parent::__construct();

		$this->_categoryHelper = $this->Application->recallObject('CategoryHelper');
	}

	/**
	 * Replace FCK links like "@@ID@@" to real page urls, when "using_fck" option is set.
	 *
	 * @param string $value
	 * @param Array $options
	 * @param string $format
	 * @return string
	 */
	function _replaceFCKLinks(&$value, $options, $format = null)
	{
		if ((isset($format) && strpos($format, 'fck_ready') !== false) || (!array_key_exists('using_fck', $options) || !$options['using_fck'])) {
			// in textarea, where fck will be used OR not using fck
			return $value;
		}

		return $this->_categoryHelper->replacePageIds($value);
	}

	/**
	 * Convert's value to match type from config
	 *
	 * @param mixed $value
	 * @param Array $options
	 * @return mixed
	 * @access protected
	 */
	function TypeCast($value, $options)
	{
		if ( isset($options['type']) ) {
			$field_type = $options['type'];

			if ($field_type == 'numeric') {
				trigger_error('Invalid field type <strong>' . $field_type . '</strong> (in TypeCast method), please use <strong>float</strong> instead', E_USER_NOTICE);
				$field_type = 'float';
			}
			elseif ( $field_type == 'string' ) {
				if ( isset($options['allow_html']) && $options['allow_html'] ) {
					$value = $this->Application->unescapeRequestVariable($value);
				}

				return $value;
			}

			$value = $this->formatNumber($value);
			$is_numeric_type = preg_match('#int|integer|double|float|real#', $field_type);

			if ( $value != '' && $is_numeric_type ) {
				$tc_value = $value;
				settype($tc_value, $field_type);

				// Type casing is considered a success only, when type casted value visually looks the same.
				return (string)$value === (string)$tc_value ? $tc_value : false;
			}
		}

		return $value;
	}

	/**
	 * Formats number, according to regional settings
	 *
	 * @param string $number
	 * @return float
	 */
	function formatNumber($number)
	{
		static $comma = null, $thousands = null;

		if ( !isset($comma) || !isset($thousands) ) {
			/** @var LanguagesItem $lang */
			$lang = $this->Application->recallObject('lang.current');

			$comma = $lang->GetDBField('DecimalPoint');
			$thousands = $lang->GetDBField('ThousandSep');
		}

		$number = str_replace($thousands, '', $number);
		$number = str_replace($comma, '.', $number);

		return $number;
	}

	/**
	 * Applies type casting on each array element
	 * @param Array $src
	 * @param kDBItem|kDBList|kDBBase $object
	 * @return Array
	 * @access public
	 */
	public function TypeCastArray($src, &$object)
	{
		$dst = array ();

		foreach ($src as $id => $row) {
			$tmp_row = array ();
			foreach ($row as $fld => $value) {
				$field_options = $object->GetFieldOptions($fld);
				$tmp_row[$fld] = $this->TypeCast($value, $field_options);
			}
			$dst[$id] = $tmp_row;
		}

		return $dst;
	}

	/**
	 * Formats value of a given field
	 *
	 * @param string $value
	 * @param string $field_name
	 * @param kDBItem|kDBList|kDBBase $object
	 * @param string $format
	 * @return string
	 */
	function Format($value, $field_name, &$object, $format = null)
	{
		if ( is_null($value) ) {
			return '';
		}

		$options = $object->GetFieldOptions($field_name);
		if (!isset($format) && array_key_exists('format', $options)) {
			$format = $options['format'];
		}

		if ($value === false) {
			// used ?
			return $value; // for leaving badly formatted date on the form
		}

		$original_format = $format;
		if (isset($format)) {
			if (strpos($format, 'fck_ready') !== false) {
				$format = trim(str_replace('fck_ready', '', $format), ';');
			}
		}

		if (isset($format) && $format) {
			if ( substr($format, -1) === 'f' ) {
				// High precision formats (e.g. '%01.20f') are not supported to keep code below as fast as possible.
				$value = round($value, substr($format, -2, 1));
			}

			$value = sprintf($format, $value);

			if ( isset($options['cut_zeros']) && $options['cut_zeros'] ) {
				// Remove trailing zeros in decimal part (including "." if any left at the end).
				$value = preg_replace('/\.0+$/', '', $value);
				$value = preg_replace('/(\.\d+?)0+$/', '$1', $value);
			}
		}

		if (preg_match('#int|integer|double|float|real|numeric#', $options['type'])) {
			/** @var LanguagesItem $lang */
			$lang = $this->Application->recallObject('lang.current');

			return $lang->formatNumber($value);
		}
		elseif ($options['type'] == 'string') {
			$value = $this->_replaceFCKLinks($value, $options, $original_format);
		}

		return $value;
	}

	/**
	 * Performs basic type validation on form field value
	 *
	 * @param mixed $value
	 * @param string $field_name
	 * @param kDBItem|kDBList|kDBBase $object
	 * @return mixed
	 * @access public
	 */
	public function Parse($value, $field_name, &$object)
	{
		$options = $object->GetFieldOptions($field_name);

		if ($value == '') {
			return $options['type'] == 'string' ? $value : null;
		}

		$tc_value = $this->TypeCast($value, $options);

		if ($tc_value === false) {
			return $value; // for leaving badly formatted date on the form
		}

		if(isset($options['type'])) {
			if (preg_match('#double|float|real|numeric#', $options['type'])) {
				$tc_value = str_replace(',', '.', $tc_value);
			}
		}

		if (isset($options['regexp'])) {
			if (!preg_match($options['regexp'], $value)) {
				$object->SetError($field_name, 'invalid_format');
			}
		}

		return $tc_value;
	}

	function HumanFormat($format)
	{
		return $format;
	}


	/**
	 * The method is supposed to alter config options or cofigure object in some way based on its usage of formatters
	 * The methods is called for every field with formatter defined when configuring item.
	 * Could be used for adding additional VirtualFields to an object required by some special Formatter
	 *
	 * @param string $field_name
	 * @param array $field_options
	 * @param kDBBase $object
	 */
	function PrepareOptions($field_name, &$field_options, &$object)
	{

	}

	/**
	 * Used for split fields like timestamp -> date, time
	 * Called from DBItem to update sub fields values after loading item
	 *
	 * @param string $field
	 * @param string $value
	 * @param Array $options
	 * @param kDBItem|kDBList|kDBBase $object
	 * @return void
	 * @access public
	 */
	public function UpdateSubFields($field, $value, &$options, &$object)
	{

	}

	/**
	 * Used for split fields like timestamp -> date, time
	 * Called from DBItem Validate (before validation) to get back master field value from its sub_fields
	 *
	 * @param string $field
	 * @param mixed $value
	 * @param Array $options
	 * @param kDBItem|kDBList|kDBBase $object
	 */
	function UpdateMasterFields($field, $value, &$options, &$object)
	{

	}

	/**
	 * Return sample value, that can be entered in this field
	 *
	 * @param string $field
	 * @param Array $options
	 * @param kDBItem|kDBList|kDBBase $object
	 * @return string
	 * @access public
	 */
	public function GetSample($field, &$options, &$object)
	{
		return isset($options['sample_value']) ? $options['sample_value'] : '';
	}

}
