<?php
/**
 * 2007-2016 PrestaShop
 *
 * thirty bees is an extension to the PrestaShop e-commerce software developed by PrestaShop SA
 * Copyright (C) 2017-2024 thirty bees
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the Open Software License (OSL 3.0)
 * that is bundled with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://opensource.org/licenses/osl-3.0.php
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@thirtybees.com so we can send you a copy immediately.
 *
 * DISCLAIMER
 *
 * Do not edit or add to this file if you wish to upgrade PrestaShop to newer
 * versions in the future. If you wish to customize PrestaShop for your
 * needs please refer to https://www.thirtybees.com for more information.
 *
 * @author    thirty bees <contact@thirtybees.com>
 * @author    PrestaShop SA <contact@prestashop.com>
 * @copyright 2017-2024 thirty bees
 * @copyright 2007-2016 PrestaShop SA
 * @license   http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
 *  PrestaShop is an internationally registered trademark & property of PrestaShop SA
 */

use Thirtybees\Core\Error\ErrorUtils;

/**
 * Class AdminDashboardControllerCore
 */
class AdminDashboardControllerCore extends AdminController
{
    /**
     * AdminDashboardControllerCore constructor.
     *
     * @throws PrestaShopException
     */
    public function __construct()
    {
        $this->bootstrap = true;
        $this->display = 'view';

        parent::__construct();

        if (Tools::isSubmit('profitability_conf') || Tools::isSubmit('submitOptionsconfiguration')) {
            $this->fields_options = $this->getOptionFields();
        }
    }

    /**
     * @return array
     *
     * @throws PrestaShopDatabaseException
     * @throws PrestaShopException
     */
    protected function getOptionFields()
    {
        $currency = new Currency(Configuration::get('PS_CURRENCY_DEFAULT'));
        $carriers = Carrier::getCarriers($this->context->language->id, true, false, false, null, 'ALL_CARRIERS');
        $modules = Module::getModulesOnDisk(true);

        $forms = [
            'payment'  => ['title' => $this->l('Average bank fees per payment method'), 'id' => 'payment'],
            'carriers' => ['title' => $this->l('Average shipping fees per shipping method'), 'id' => 'carriers'],
            'other'    => ['title' => $this->l('Other settings'), 'id' => 'other'],
        ];
        foreach ($forms as &$form) {
            $form['icon'] = 'tab-preferences';
            $form['fields'] = [];
            $form['submit'] = ['title' => $this->l('Save')];
        }

        foreach ($modules as $module) {
            if (isset($module->tab) && $module->tab == 'payments_gateways' && $module->id) {
                $moduleClass = Module::getInstanceByName($module->name);
                if (!$moduleClass->isEnabledForShopContext()) {
                    continue;
                }

                $forms['payment']['fields']['CONF_'.strtoupper($module->name).'_FIXED'] = [
                    'title'        => $module->displayName,
                    'desc'         => sprintf($this->l('Choose a fixed fee for each order placed in %1$s with %2$s.'), $currency->iso_code, $module->displayName),
                    'validation'   => 'isPrice',
                    'cast'         => 'priceval',
                    'type'         => 'text',
                    'defaultValue' => '0',
                    'suffix'       => $currency->iso_code,
                ];
                $forms['payment']['fields']['CONF_'.strtoupper($module->name).'_VAR'] = [
                    'title'        => $module->displayName,
                    'desc'         => sprintf($this->l('Choose a variable fee for each order placed in %1$s with %2$s. It will be applied on the total paid with taxes.'), $currency->iso_code, $module->displayName),
                    'validation'   => 'isFloat',
                    'cast'         => 'floatval',
                    'type'         => 'text',
                    'defaultValue' => '0',
                    'suffix'       => '%',
                ];

                if (Currency::isMultiCurrencyActivated()) {
                    $forms['payment']['fields']['CONF_'.strtoupper($module->name).'_FIXED_FOREIGN'] = [
                        'title'        => $module->displayName,
                        'desc'         => sprintf($this->l('Choose a fixed fee for each order placed with a foreign currency with %s.'), $module->displayName),
                        'validation'   => 'isPrice',
                        'cast'         => 'priceval',
                        'type'         => 'text',
                        'defaultValue' => '0',
                        'suffix'       => $currency->iso_code,
                    ];
                    $forms['payment']['fields']['CONF_'.strtoupper($module->name).'_VAR_FOREIGN'] = [
                        'title'        => $module->displayName,
                        'desc'         => sprintf($this->l('Choose a variable fee for each order placed with a foreign currency with %s. It will be applied on the total paid with taxes.'), $module->displayName),
                        'validation'   => 'isPrice',
                        'cast'         => 'floatval',
                        'type'         => 'text',
                        'defaultValue' => '0',
                        'suffix'       => '%',
                    ];
                }
            }
        }

        foreach ($carriers as $carrier) {
            $forms['carriers']['fields']['CONF_'.strtoupper($carrier['id_reference']).'_SHIP'] = [
                'title'        => $carrier['name'],
                'desc'         => sprintf($this->l('For the carrier named %s, indicate the domestic delivery costs  in percentage of the price charged to customers.'), $carrier['name']),
                'validation'   => 'isFloat',
                'cast'         => 'floatval',
                'type'         => 'text',
                'defaultValue' => '0',
                'suffix'       => '%',
            ];
            $forms['carriers']['fields']['CONF_'.strtoupper($carrier['id_reference']).'_SHIP_OVERSEAS'] = [
                'title'        => $carrier['name'],
                'desc'         => sprintf($this->l('For the carrier named %s, indicate the overseas delivery costs in percentage of the price charged to customers.'), $carrier['name']),
                'validation'   => 'isFloat',
                'cast'         => 'floatval',
                'type'         => 'text',
                'defaultValue' => '0',
                'suffix'       => '%',
            ];
        }

        $forms['carriers']['description'] = $this->l('Method: Indicate the percentage of your carrier margin. For example, if you charge $10 of shipping fees to your customer for each shipment, but you really pay $4 to this carrier, then you should indicate "40" in the percentage field.');

        $forms['other']['fields']['CONF_AVERAGE_PRODUCT_MARGIN'] = [
            'title'        => $this->l('Average gross margin percentage'),
            'desc'         => $this->l('You should calculate this percentage as follows: ((total sales revenue) - (cost of goods sold)) / (total sales revenue) * 100. This value is only used to calculate the Dashboard approximate gross margin, if you do not specify the wholesale price for each product.'),
            'validation'   => 'isFloat',
            'cast'         => 'intval',
            'type'         => 'text',
            'defaultValue' => '0',
            'suffix'       => '%',
        ];

        $forms['other']['fields']['CONF_ORDER_FIXED'] = [
            'title'        => $this->l('Other fees per order'),
            'desc'         => $this->l('You should calculate this value by making the sum of all of your additional costs per order.'),
            'validation'   => 'isPrice',
            'cast'         => 'priceval',
            'type'         => 'text',
            'defaultValue' => '0',
            'suffix'       => $currency->iso_code,
        ];

        $forms['other']['fields']['ORDER_STATS_DATE_COLUMN'] = [
            'title'        => $this->l('Include orders by'),
            'desc'         => $this->l('Choose if orders should be counted by order invoice date, or order creation date'),
            'type'         => 'select',
            'defaultValue' => AdminStatsController::ORDER_DATE_COLUMN_INVOICE,
            'identifier'   => 'id',
            'list' => [
                [
                    'id' => AdminStatsController::ORDER_DATE_COLUMN_INVOICE,
                    'name' => $this->l('Order invoice date')
                ],
                [
                    'id' => AdminStatsController::ORDER_DATE_COLUMN_DATE,
                    'name' => $this->l('Order creation date'),
                ],
            ]
        ];

        Media::addJsDef(
            [
                'dashboard_ajax_url' => $this->context->link->getAdminLink('AdminDashboard'),
                'read_more'          => '',
            ]
        );

        return $forms;
    }

    /**
     * @throws PrestaShopException
     */
    public function setMedia()
    {
        parent::setMedia();

        $this->addJqueryUI('ui.datepicker');
        $this->addJS(
            [
                _PS_JS_DIR_.'vendor/d3.v3.min.js',
                __PS_BASE_URI__.$this->admin_webpath.'/themes/'.$this->bo_theme.'/js/vendor/nv.d3.min.js',
                _PS_JS_DIR_.'/admin/dashboard.js',
            ]
        );
        $this->addCSS(__PS_BASE_URI__.$this->admin_webpath.'/themes/'.$this->bo_theme.'/css/vendor/nv.d3.css');
    }

    /**
     * @throws PrestaShopException
     */
    public function initPageHeaderToolbar()
    {
        $this->page_header_toolbar_title = $this->l('Dashboard');
        $this->page_header_toolbar_btn['switch_demo'] = [
            'desc' => $this->l('Demo mode', null, null, false),
            'icon' => 'process-icon-toggle-'.(Configuration::get('PS_DASHBOARD_SIMULATION') ? 'on' : 'off'),
            'help' => $this->l('This mode displays sample data so you can try your dashboard without real numbers.', null, null, false),
        ];

        parent::initPageHeaderToolbar();

        // Remove the last element on this controller to match the title with the rule of the others
        array_pop($this->meta_title);
    }

    /**
     * @return string
     *
     * @throws PrestaShopException
     * @throws SmartyException
     */
    public function renderView()
    {
        if (Tools::isSubmit('profitability_conf')) {
            return parent::renderOptions();
        }

        $employee = $this->context->employee;

        // resolve date range
        $now = date('Y-m-d');
        $dateFrom = static::getDate(Tools::getValue('date_from', $employee->stats_date_from), $now);
        $dateTo = static::getDate(Tools::getValue('date_to', $employee->stats_date_to), $now);
        if (strtotime($dateFrom) > strtotime($dateTo)) {
            $dateFrom = $dateTo;
        }

        $calendarHelper = new HelperCalendar();
        $calendarHelper->setDateFrom($dateFrom);
        $calendarHelper->setDateTo($dateTo);
        $calendarHelper->setCompareDateFrom(static::getDate($employee->stats_compare_from));
        $calendarHelper->setCompareDateTo(static::getDate($employee->stats_compare_to));
        $calendarHelper->setCompareOption($employee->stats_compare_option);

        $params = [
            'date_from' => $dateFrom,
            'date_to'   => $dateTo,
        ];

        $this->tpl_view_vars = [
            'date_from'               => $dateFrom,
            'date_to'                 => $dateTo,
            'hookDashboardZoneOne'    => Hook::displayHook('dashboardZoneOne', $params),
            'hookDashboardZoneTwo'    => Hook::displayHook('dashboardZoneTwo', $params),
            'action'                  => '#',
            'warning'                 => $this->getWarningDomainName(),
            'calendar'                => $calendarHelper->generate(),
            'PS_DASHBOARD_SIMULATION' => Configuration::get('PS_DASHBOARD_SIMULATION'),
            'datepickerFrom'          => $dateFrom,
            'datepickerTo'            => $dateTo,
            'preselect_date_range'    => $employee->preselect_date_range,
        ];

        return parent::renderView();
    }

    /**
     * @return string|null
     *
     * @throws PrestaShopException
     */
    protected function getWarningDomainName()
    {
        if (Shop::isFeatureActive()) {
            return null;
        }

        $shop = $this->context->shop;
        if ($_SERVER['HTTP_HOST'] != $shop->domain && $_SERVER['HTTP_HOST'] != $shop->domain_ssl && Tools::getValue('ajax') == false) {
            $warning = $this->l('You are currently connected under the following domain name:').' <span style="color: #CC0000;">'.$_SERVER['HTTP_HOST'].'</span><br />';
            if (Configuration::get('PS_MULTISHOP_FEATURE_ACTIVE')) {
                $warning .= sprintf($this->l('This is different from the shop domain name set in the Multistore settings: "%s".'), $shop->domain).'
				'.preg_replace('@{link}(.*){/link}@', '<a href="index.php?controller=AdminShopUrl&id_shop_url='.(int) $shop->id.'&updateshop_url&token='.Tools::getAdminTokenLite('AdminShopUrl').'">$1</a>', $this->l('If this is your main domain, please {link}change it now{/link}.'));
            } else {
                $warning .= $this->l('This is different from the domain name set in the "SEO & URLs" tab.').'
				'.preg_replace('@{link}(.*){/link}@', '<a href="index.php?controller=AdminMeta&token='.Tools::getAdminTokenLite('AdminMeta').'#meta_fieldset_shop_url">$1</a>', $this->l('If this is your main domain, please {link}change it now{/link}.'));
            }
            return $warning;
        }

        return null;
    }

    /**
     * @throws PrestaShopException
     */
    public function postProcess()
    {
        if (Tools::isSubmit('submitDateRange')) {
            if (!Validate::isDate(Tools::getValue('date_from'))
                || !Validate::isDate(Tools::getValue('date_to'))
            ) {
                $this->errors[] = Tools::displayError('The selected date range is not valid.');
            }

            if (Tools::getValue('datepicker_compare')) {
                if (!Validate::isDate(Tools::getValue('compare_date_from'))
                    || !Validate::isDate(Tools::getValue('compare_date_to'))
                ) {
                    $this->errors[] = Tools::displayError('The selected date range is not valid.');
                }
            }

            if (!count($this->errors)) {
                $employee = $this->context->employee;
                $employee->stats_date_from = static::getDate(Tools::getValue('date_from'), $employee->stats_date_from);
                $employee->stats_date_to = static::getDate(Tools::getValue('date_to'), $employee->stats_date_to);
                $employee->preselect_date_range = Tools::getValue('preselectDateRange');

                if (Tools::getValue('datepicker_compare')) {
                    $employee->stats_compare_from = static::getDate(Tools::getValue('compare_date_from'));
                    $employee->stats_compare_to = static::getDate(Tools::getValue('compare_date_to'));
                    $employee->stats_compare_option = Tools::getValue('compare_date_option');
                } else {
                    $employee->stats_compare_from = null;
                    $employee->stats_compare_to = null;
                    $employee->stats_compare_option = HelperCalendar::DEFAULT_COMPARE_OPTION;
                }

                $employee->update();
            }
        }

        parent::postProcess();
    }

    /**
     * @throws PrestaShopException
     */
    public function ajaxProcessRefreshDashboard()
    {
        $idModule = null;
        if ($module = Tools::getValue('module')) {
            $moduleObj = Module::getInstanceByName($module);
            if (Validate::isLoadedObject($moduleObj)) {
                $idModule = $moduleObj->id;
            }
        }

        $params = [
            'date_from'          => $this->context->employee->stats_date_from,
            'date_to'            => $this->context->employee->stats_date_to,
            'compare_from'       => $this->context->employee->stats_compare_from,
            'compare_to'         => $this->context->employee->stats_compare_to,
            'extra'              => Tools::getIntValue('extra'),
        ];

        $this->ajaxDie(json_encode(Hook::getResponses('dashboardData', $params, $idModule)));
    }

    /**
     * @throws PrestaShopException
     */
    public function ajaxProcessSetSimulationMode()
    {
        Configuration::updateValue('PS_DASHBOARD_SIMULATION', Tools::getIntValue('PS_DASHBOARD_SIMULATION'));
        $this->ajaxDie('k'.Configuration::get('PS_DASHBOARD_SIMULATION').'k');
    }

    /**
     * @throws PrestaShopException
     */
    public function ajaxProcessGetBlogRss()
    {
        $return = ['has_errors' => false, 'rss' => []];
        if (!$this->isFresh('/config/xml/blog-'.$this->context->language->iso_code.'.xml', 86400)) {
            if (!$this->refresh('/config/xml/blog-'.$this->context->language->iso_code.'.xml', 'https://thirtybees.com/feed/')) {
                $return['has_errors'] = true;
            }
        }
        if (!$return['has_errors']) {
            $rss = @simplexml_load_file(_PS_ROOT_DIR_.'/config/xml/blog-'.$this->context->language->iso_code.'.xml');
            if (!$rss) {
                $return['has_errors'] = true;
            }
            $articlesLimit = 2;
            if ($rss) {
                foreach ($rss->channel->item as $item) {
                    if ($articlesLimit > 0
                        && isset($item->link)
                        && isset($item->title)
                        && Validate::isCleanHtml((string) $item->title)
                        && Validate::isCleanHtml((string) $item->description)
                    ) {
                        $return['rss'][] = [
                            'date'       => Tools::displayDate(date('Y-m-d', strtotime((string) $item->pubDate))),
                            'title'      => (string) Tools::htmlentitiesUTF8($item->title),
                            'short_desc' => Tools::truncateString(strip_tags((string) $item->description), 150),
                            'link'       => $this->getArticleLink($item),
                        ];
                    } else {
                        break;
                    }
                    $articlesLimit--;
                }
            }
        }
        $this->ajaxDie(json_encode($return));
    }

    /**
     * @throws PrestaShopDatabaseException
     * @throws PrestaShopException
     */
    public function ajaxProcessSaveDashConfig()
    {
        $return = [
            'has_errors' => false,
            'errors' => []
        ];
        $module = Tools::getValue('module');
        $hook = Tools::getValue('hook');
        $configs = Tools::getValue('configs');

        $params = [
            'date_from' => $this->context->employee->stats_date_from,
            'date_to'   => $this->context->employee->stats_date_to,
        ];

        if (Validate::isModuleName($module)) {
            $moduleObj = Module::getInstanceByName($module);
            if (Validate::isLoadedObject($moduleObj)) {

                if (method_exists($moduleObj, 'validateDashConfig')) {
                    $return['errors'] = $moduleObj->validateDashConfig($configs);
                }

                if (!count($return['errors'])) {
                    if (method_exists($moduleObj, 'saveDashConfig')) {
                        try {
                            $moduleObj->saveDashConfig($configs);
                        } catch (Throwable $e) {
                            $return['has_errors'] = true;
                            $return['errors'][] = Tools::displayError('Exception thrown when saving dashboard configuration');
                            static::getErrorHandler()->logFatalError(ErrorUtils::describeException($e));
                        }
                    } else {
                        $return['errors'][] = sprintf(Tools::displayError('Module %s does not implement saveDashConfig method!'), $module);
                        $return['has_errors'] = true;
                    }
                } else {
                    $return['has_errors'] = true;
                }

                if (Validate::isHookName($hook) && method_exists($moduleObj, $hook)) {
                    $return['widget_html'] = $moduleObj->$hook($params);
                }
            }
        }

        $this->ajaxDie(json_encode($return));
    }

    /**
     * @throws PrestaShopException
     * @throws SmartyException
     */
    public function initContent()
    {
        $this->updateDateRange();
        parent::initContent();
    }

    /**
     * Automatically update date range stored in Employee record
     *
     * Employee record contains three dashboard related properties
     *   - stats_date_from - date
     *   - stats_date_to - date
     *   - preselect_date_range (day|prev-day|month|prev-month|year|prev-year)
     *
     * Range range start and end dates (stored in $stats_date_from and $stats_date_to) should be
     * derived from preset $preselect_date_range. We need to recalculate date range at least
     * once a day
     *
     * @throws PrestaShopException
     */
    protected function updateDateRange()
    {
        $employee = $this->context->employee;
        $cookie = $this->context->cookie;
        $lastDateRangeUpdate = (int)$cookie->stats_date_update;
        $dateRangeUpdate = (int)strtotime(date('Y-m-d'));

        if ($lastDateRangeUpdate !== $dateRangeUpdate) {
            $cookie->stats_date_update = $dateRangeUpdate;
            switch ($employee->preselect_date_range) {
                case 'day':
                    $dateFrom = date('Y-m-d');
                    $dateTo = date('Y-m-d');
                    break;
                case 'prev-day':
                    $dateFrom = date('Y-m-d', strtotime('-1 day'));
                    $dateTo = date('Y-m-d', strtotime('-1 day'));
                    break;
                case 'month':
                default:
                    $dateFrom = date('Y-m-01');
                    $dateTo = date('Y-m-d');
                    break;
                case 'prev-month':
                    $dateFrom = date('Y-m-01', strtotime('-1 month'));
                    $dateTo = date('Y-m-t', strtotime('-1 month'));
                    break;
                case 'year':
                    $dateFrom = date('Y-01-01');
                    $dateTo = date('Y-m-d');
                    break;
                case 'prev-year':
                    $dateFrom = date('Y-m-01', strtotime('-1 year'));
                    $dateTo = date('Y-12-t', strtotime('-1 year'));
                    break;
            }
            if ($dateTo != $employee->stats_date_to || $dateFrom != $employee->stats_date_from) {
                $employee->stats_date_from = $dateFrom;
                $employee->stats_date_to = $dateTo;
                $employee->update();
            }
        }
    }

    /**
     * Validates input string $strValue that is is valid date
     *
     * @param string $strValue
     * @param string|null $defaultValue
     * @return false|string|null
     */
    protected function getDate($strValue, $defaultValue=null)
    {
        if (is_string($strValue) && Validate::isDate($strValue) && $strValue != '0000-00-00') {
            $timestamp = strtotime($strValue);
            if ($timestamp !== false) {
                return date('Y-m-d', $timestamp);
            }
        }
        return $defaultValue;
    }

    /**
     * @param SimpleXMLElement $item
     *
     * @return string
     * @throws PrestaShopException
     */
    protected function getArticleLink(SimpleXMLElement $item): string
    {
        $link = (string)$item->link;
        $shopDefaultCountryId = (int)Configuration::get('PS_COUNTRY_DEFAULT');
        $shopDefaultIsoCountry = strtoupper(Country::getIsoById($shopDefaultCountryId));
        $analyticsParams = [
            'utm_source' => 'back-office',
            'utm_medium' => 'rss',
            'utm_campaign' => 'back-office-' . $shopDefaultIsoCountry,
            'utm_content' => 'download'
        ];

        $urlQuery = parse_url($link, PHP_URL_QUERY);
        if ($urlQuery) {
            parse_str($urlQuery, $linkQueryParams);
            if ($linkQueryParams) {
                $fullUrlParams = array_merge($linkQueryParams, $analyticsParams);
                $baseUrl = explode('?', $link);
                $baseUrl = (string)$baseUrl[0];
                return $baseUrl . '?' . http_build_query($fullUrlParams);
            }
        }

        return $link . '?' . http_build_query($analyticsParams);
    }

}
