YetiForceCompany/YetiForceCRM

View on GitHub
include/fields/CurrencyField.php

Summary

Maintainability
D
2 days
Test Coverage
D
60%
<?php
/* +**********************************************************************************
 * The contents of this file are subject to the vtiger CRM Public License Version 1.0
 * ("License"); You may not use this file except in compliance with the License
 * The Original Code is:  vtiger CRM Open Source
 * The Initial Developer of the Original Code is vtiger.
 * Portions created by vtiger are Copyright (C) vtiger.
 * All Rights Reserved.
 * Contributor(s): YetiForce S.A.
 * ********************************************************************************** */

class CurrencyField
{
    private $CURRENCY_PATTERN_PLAIN = '123456789';
    private $CURRENCY_PATTERN_SINGLE_GROUPING = '123456,789';
    private $CURRENCY_PATTERN_THOUSAND_GROUPING = '123,456,789';
    private $CURRENCY_PATTERN_MIXED_GROUPING = '12,34,56,789';

    /**
     * Currency Format(3,3,3) or (2,2,3).
     *
     * @var string
     */
    public $currencyFormat = '123,456,789';

    /**
     * Currency Separator for example (comma, dot, hash).
     *
     * @var string
     */
    public $currencySeparator = ',';

    /**
     * Decimal Separator for example (dot, comma, space).
     *
     * @var <type>
     */
    public $decimalSeparator = '.';

    /**
     * Number of Decimal Numbers.
     *
     * @var int
     */
    public $numberOfDecimal = 3;

    /**
     * Currency Id.
     *
     * @var int
     */
    public $currencyId = 1;

    /**
     * Currency Symbol.
     *
     * @var string
     */
    public $currencySymbol;

    /**
     * Currency Symbol Placement.
     */
    public $currencySymbolPlacement;

    /**
     * Currency Conversion Rate.
     *
     * @var Number
     */
    public $conversionRate = 1;

    /**
     * Value to be converted.
     *
     * @param Number $value
     */
    public $value;

    /**
     * Maximum Number Of Currency Decimals.
     *
     * @var Number
     */
    public $maxNumberOfDecimals = 5;

    /**
     * Constructor.
     *
     * @param Number $value
     */
    public function __construct($value)
    {
        $this->value = $value;
    }

    /**
     * Initializes the User's Currency Details.
     *
     * @param \App\User $user
     */
    public function initialize($user = null)
    {
        $defaultCharset = App\Config::main('default_charset');
        if (empty($user)) {
            $user = \App\User::getCurrentUserModel();
        }
        if (!empty($user->getDetail('currency_grouping_pattern'))) {
            $this->currencyFormat = html_entity_decode($user->getDetail('currency_grouping_pattern'), ENT_QUOTES, $defaultCharset);
            $this->currencySeparator = str_replace("\xC2\xA0", ' ', html_entity_decode($user->getDetail('currency_grouping_separator'), ENT_QUOTES, $defaultCharset));
            $this->decimalSeparator = str_replace("\xC2\xA0", ' ', html_entity_decode($user->getDetail('currency_decimal_separator'), ENT_QUOTES, $defaultCharset));
        }
        if (!empty($user->getDetail('currency_id'))) {
            $this->currencyId = $user->getDetail('currency_id');
        } else {
            $this->currencyId = self::getDBCurrencyId();
        }
        $currencyData = \App\Fields\Currency::getById($this->currencyId);
        $this->currencySymbol = $currencyData['currency_symbol'];
        $this->conversionRate = $currencyData['conversion_rate'];
        $this->currencySymbolPlacement = $user->getDetail('currency_symbol_placement');
        $this->numberOfDecimal = (empty($user->getDetail('no_of_currency_decimals')) && 0 !== (int) $user->getDetail('no_of_currency_decimals')) ? 2 : (int) $user->getDetail('no_of_currency_decimals');
    }

    public function getCurrencySymbol()
    {
        return $this->currencySymbol;
    }

    /**
     * Returns the Formatted Currency value for the User.
     *
     * @global Users    $current_user
     *
     * @param \App\User $user
     * @param bool      $skipConversion
     * @param mixed     $value
     * @param mixed     $skipFormatting
     *
     * @deprecated        Recommend using function \App\Fields\Currency::formatToDisplay
     *
     * @return string Formatted Currency
     */
    public static function convertToUserFormat($value, $user = null, $skipConversion = false, $skipFormatting = false)
    {
        // To support negative values
        $negative = false;
        if (0 === stripos($value, '-')) {
            $negative = true;
            $value = substr($value, 1);
        }
        $self = new self($value);
        $value = $self->getDisplayValue($user, $skipConversion, $skipFormatting);
        return ($negative) ? '-' . $value : $value;
    }

    public static function convertToUserFormatSymbol($value, $skipConversion = false, $currencySymbol = false, $skipFormatting = false)
    {
        // To support negative values
        $negative = false;
        if (0 === stripos($value, '-')) {
            $negative = true;
            $value = substr($value, 1);
        }
        $self = new self($value);
        $formattedValue = $self->getDisplayValue(null, $skipConversion, $skipFormatting);
        if (false === $currencySymbol) {
            $currencySymbol = $self->currencySymbol;
        }
        $value = self::appendCurrencySymbol($formattedValue, $currencySymbol, $self->currencySymbolPlacement);
        return ($negative) ? '-' . $value : $value;
    }

    /**
     * Function that converts the Number into Users Currency.
     *
     * @param \App\User $user
     * @param bool      $skipConversion
     * @param mixed     $skipFormatting
     *
     * @return string Formatted Currency
     */
    public function getDisplayValue($user = null, $skipConversion = false, $skipFormatting = false)
    {
        if (empty($user)) {
            $user = \App\User::getCurrentUserModel();
        }
        $this->initialize($user);
        $displayValue = $this->value;
        if (empty($displayValue)) {
            $displayValue = 0;
        }
        if (false === $skipConversion) {
            $displayValue = self::convertFromDollar($displayValue, $this->conversionRate);
        }
        if (false === $skipFormatting) {
            $displayValue = $this->formatCurrencyValue($displayValue);
        }
        return $this->currencyDecimalFormat($displayValue, $user);
    }

    /**
     * Function that converts the Number into Users Currency along with currency symbol.
     *
     * @param \App\User $user
     * @param bool      $skipConversion
     *
     * @return string Formatted Currency
     */
    public function getDisplayValueWithSymbol($user = null, $skipConversion = false)
    {
        $formattedValue = $this->getDisplayValue($user, $skipConversion);
        return self::appendCurrencySymbol($formattedValue, $this->currencySymbol, $this->currencySymbolPlacement);
    }

    /**
     * Static Function that appends the currency symbol to a given currency value, based on the preferred symbol placement.
     *
     * @param Number $currencyValue
     * @param string $currencySymbol
     * @param string $currencySymbolPlacement
     *
     * @return string Currency value appended with the currency symbol
     */
    public static function appendCurrencySymbol($currencyValue, $currencySymbol, $currencySymbolPlacement = '')
    {
        if (!$currencySymbolPlacement) {
            $currencySymbolPlacement = \App\User::getCurrentUserModel()->getDetail('currency_symbol_placement');
        }
        switch ($currencySymbolPlacement) {
            case '1.0$':
                $returnValue = $currencyValue . ' ' . $currencySymbol;
                break;
            case '$1.0':
            default:
                $returnValue = html_entity_decode($currencySymbol, ENT_COMPAT) . ' ' . $currencyValue;
        }
        return $returnValue;
    }

    /**
     * Function that formats the Number based on the User configured Pattern, Currency separator and Decimal separator.
     *
     * @param Number $value
     *
     * @return bool|string Formatted Currency
     */
    private function formatCurrencyValue($value)
    {
        $currencyPattern = $this->currencyFormat;
        $curSeparator = $this->currencySeparator;
        $decSeparator = $this->decimalSeparator;
        $currencyDecimalPlaces = $this->numberOfDecimal;
        $value = number_format($value, $currencyDecimalPlaces, '.', '');
        if (empty($curSeparator)) {
            $curSeparator = ' ';
        }
        if (empty($decSeparator)) {
            $decSeparator = ' ';
        }

        if ($value < 0) {
            $sign = '-';
            $value = substr($value, 1);
        } else {
            $sign = '';
        }

        if ($currencyPattern == $this->CURRENCY_PATTERN_PLAIN) {
            // Replace '.' with Decimal Separator
            $number = str_replace('.', $decSeparator, $value);

            return $sign . $number;
        }
        if ($currencyPattern == $this->CURRENCY_PATTERN_SINGLE_GROUPING) {
            // Separate the numeric and decimal parts
            $numericParts = explode('.', $value);
            $wholeNumber = $numericParts[0];
            // First part of the number which remains intact
            if (\strlen($wholeNumber) > 3) {
                $wholeNumberFirstPart = substr($wholeNumber, 0, \strlen($wholeNumber) - 3);
            }
            // Second Part of the number (last 3 digits) which should be separated from the First part using Currency Separator
            $wholeNumberLastPart = substr($wholeNumber, -3);
            // Re-create the whole number with user's configured currency separator
            if (!empty($wholeNumberFirstPart)) {
                $numericParts[0] = $wholeNumberFirstPart . $curSeparator . $wholeNumberLastPart;
            } else {
                $numericParts[0] = $wholeNumberLastPart;
            }
            // Re-create the currency value combining the whole number and the decimal part using Decimal separator
            $number = implode($decSeparator, $numericParts);

            return $sign . $number;
        }
        if ($currencyPattern == $this->CURRENCY_PATTERN_THOUSAND_GROUPING) {
            $negativeNumber = false;
            if ($value < 0) {
                $negativeNumber = true;
            }

            // Separate the numeric and decimal parts
            $numericParts = explode('.', $value);
            $wholeNumber = $numericParts[0];

            //check the whole number is negative value, then separate the negative symbol from whole number
            if ($wholeNumber < 0 || $negativeNumber) {
                $negativeNumber = true;
                $positiveValues = explode('-', $wholeNumber);
                $wholeNumber = $positiveValues[1];
            }

            // Pad the rest of the length in the number string with Leading 0, to get it to the multiples of 3
            $numberLength = \strlen($wholeNumber);
            // First grouping digits length
            $OddGroupLength = $numberLength % 3;
            $gapsToBeFilled = 0;
            if ($OddGroupLength > 0) {
                $gapsToBeFilled = 3 - $OddGroupLength;
            }
            $wholeNumber = str_pad($wholeNumber, $numberLength + $gapsToBeFilled, '0', STR_PAD_LEFT);
            // Split the whole number into chunks of 3 digits
            $wholeNumberParts = str_split($wholeNumber, 3);
            // Re-create the whole number with user's configured currency separator
            $numericParts[0] = $wholeNumber = implode($curSeparator, $wholeNumberParts);
            if (0 != $wholeNumber) {
                $numericParts[0] = ltrim($wholeNumber, '0');
            } else {
                $numericParts[0] = 0;
            }

            //if its negative number, append-back the negative symbol to the whole number part
            if ($negativeNumber) {
                $numericParts[0] = '-' . $numericParts[0];
            }

            // Re-create the currency value combining the whole number and the decimal part using Decimal separator
            $number = implode($decSeparator, $numericParts);

            return $sign . $number;
        }
        if ($currencyPattern == $this->CURRENCY_PATTERN_MIXED_GROUPING) {
            $negativeNumber = false;
            if ($value < 0) {
                $negativeNumber = true;
            }

            // Separate the numeric and decimal parts
            $numericParts = explode('.', $value);
            $wholeNumber = $numericParts[0];

            //check the whole number is negative value, then separate the negative symbol from whole number
            if ($wholeNumber < 0 || $negativeNumber) {
                $negativeNumber = true;
                $positiveValues = explode('-', $wholeNumber);
                $wholeNumber = $positiveValues[1];
            }

            // First part of the number which needs separate division
            if (\strlen($wholeNumber) > 3) {
                $wholeNumberFirstPart = substr($wholeNumber, 0, \strlen($wholeNumber) - 3);
            }
            // Second Part of the number (last 3 digits) which should be separated from the First part using Currency Separator
            $wholeNumberLastPart = substr($wholeNumber, -3);
            if (!empty($wholeNumberFirstPart)) {
                // Pad the rest of the length in the number string with Leading 0, to get it to the multiples of 2
                $numberLength = \strlen($wholeNumberFirstPart);
                // First grouping digits length
                $OddGroupLength = $numberLength % 2;
                $gapsToBeFilled = 0;
                if ($OddGroupLength > 0) {
                    $gapsToBeFilled = 2 - $OddGroupLength;
                }
                $wholeNumberFirstPart = str_pad($wholeNumberFirstPart, $numberLength + $gapsToBeFilled, '0', STR_PAD_LEFT);
                // Split the first part of tne number into chunks of 2 digits
                $wholeNumberFirstPartElements = str_split($wholeNumberFirstPart, 2);
                $wholeNumberFirstPart = implode($curSeparator, $wholeNumberFirstPartElements);
                if (0 != $wholeNumberFirstPart) {
                    $wholeNumberFirstPart = ltrim($wholeNumberFirstPart, '0');
                } else {
                    $wholeNumberFirstPart = 0;
                }
                // Re-create the whole number with user's configured currency separator
                $numericParts[0] = $wholeNumberFirstPart . $curSeparator . $wholeNumberLastPart;
            } else {
                $numericParts[0] = $wholeNumberLastPart;
            }

            //if its negative number, append-back the negative symbol to the whole number part
            if ($negativeNumber) {
                $numericParts[0] = '-' . $numericParts[0];
            }

            // Re-create the currency value combining the whole number and the decimal part using Decimal separator
            $number = implode($decSeparator, $numericParts);
            return $sign . $number;
        }
        return $value;
    }

    /**
     * Returns the Currency value without formatting for DB Operations.
     *
     * @param \App\User $user
     * @param bool      $skipConversion
     *
     * @return Number
     */
    public function getDBInsertedValue($user = null, $skipConversion = false)
    {
        if (empty($user)) {
            $user = \App\User::getCurrentUserModel();
        }
        $this->initialize($user);
        $curSeparator = $this->currencySeparator;
        $decSeparator = $this->decimalSeparator;
        if (empty($curSeparator)) {
            $curSeparator = ' ';
        }
        if (empty($decSeparator)) {
            $decSeparator = ' ';
        }
        $dbValue = str_replace($curSeparator, '', $this->value);
        $dbValue = str_replace($decSeparator, '.', $dbValue);
        $dbValue = (float) preg_replace('/[^0-9\.-]/', '', $dbValue);
        if (false === $skipConversion) {
            $dbValue = self::convertToDollar($dbValue, $this->conversionRate);
        }
        return $dbValue;
    }

    /**
     * Returns the Currency value without formatting for DB Operations.
     *
     * @param Number    $value
     * @param \App\User $user
     * @param bool      $skipConversion
     *
     * @return Number
     */
    public static function convertToDBFormat($value, $user = null, $skipConversion = false)
    {
        if (empty($value)) {
            return 0;
        }
        $self = new self($value);
        return $self->getDBInsertedValue($user, $skipConversion);
    }

    /**
     * Function to get the default CRM currency.
     *
     * @return int Default system currency id
     */
    public static function getDBCurrencyId()
    {
        $id = (new \App\Db\Query())->select(['id'])->from('vtiger_currency_info')->where(['<', 'defaultid', 0])->scalar();
        if ($id) {
            return $id;
        }
        return null;
    }

    public static function convertToDollar($amount, $conversionRate)
    {
        if (0 == $conversionRate) {
            return 0;
        }
        return $amount / $conversionRate;
    }

    public static function convertFromDollar($amount, $conversionRate)
    {
        $currencyField = new self($amount);

        return round($amount * $conversionRate, $currencyField->maxNumberOfDecimals);
    }

    public function currencyDecimalFormat($value, $user = null)
    {
        if (!$user) {
            $user = \App\User::getCurrentUserModel();
        }
        if ($user->getDetail('truncate_trailing_zeros')) {
            if (0 !== strpos($value, $user->getDetail('currency_decimal_separator'))) {
                $value = \App\Fields\Double::truncateZeros($value);
            }
            $decSeparator = $user->getDetail('currency_decimal_separator');
            $fieldValue = explode(App\Purifier::decodeHtml($decSeparator), $value);
            $valueField = $fieldValue[0];
            if (0 === (int) $valueField || !isset($fieldValue[1]) || \strlen($fieldValue[1]) <= 1) {
                if (isset($fieldValue[1]) && 1 <= \strlen($fieldValue[1])) {
                    return $value = $valueField . $decSeparator . $fieldValue[1];
                }
                if (!isset($fieldValue[1])) {
                    return $value = $valueField;
                }
                return $value = $valueField;
            }
            return preg_replace('/(?<=\\.[0-9])[0]+$/', '', $value);
        }
        return $value;
    }
}