1<?php
2/**
3 * Copyright since 2007 PrestaShop SA and Contributors
4 * PrestaShop is an International Registered Trademark & Property of PrestaShop SA
5 *
6 * NOTICE OF LICENSE
7 *
8 * This source file is subject to the Open Software License (OSL 3.0)
9 * that is bundled with this package in the file LICENSE.md.
10 * It is also available through the world-wide-web at this URL:
11 * https://opensource.org/licenses/OSL-3.0
12 * If you did not receive a copy of the license and are unable to
13 * obtain it through the world-wide-web, please send an email
14 * to license@prestashop.com so we can send you a copy immediately.
15 *
16 * DISCLAIMER
17 *
18 * Do not edit or add to this file if you wish to upgrade PrestaShop to newer
19 * versions in the future. If you wish to customize PrestaShop for your
20 * needs please refer to https://devdocs.prestashop.com/ for more information.
21 *
22 * @author    PrestaShop SA and Contributors <contact@prestashop.com>
23 * @copyright Since 2007 PrestaShop SA and Contributors
24 * @license   https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
25 */
26use PrestaShop\PrestaShop\Core\Addon\Module\ModuleManagerBuilder;
27
28class AdminDashboardControllerCore extends AdminController
29{
30    public function __construct()
31    {
32        $this->bootstrap = true;
33        $this->display = 'view';
34
35        parent::__construct();
36
37        if (Tools::isSubmit('profitability_conf') || Tools::isSubmit('submitOptionsconfiguration')) {
38            $this->fields_options = $this->getOptionFields();
39        }
40    }
41
42    public function setMedia($isNewTheme = false)
43    {
44        parent::setMedia($isNewTheme);
45
46        $this->addJqueryUI('ui.datepicker');
47        $this->addJS([
48            _PS_JS_DIR_ . 'vendor/d3.v3.min.js',
49            __PS_BASE_URI__ . $this->admin_webpath . '/themes/' . $this->bo_theme . '/js/vendor/nv.d3.min.js',
50            _PS_JS_DIR_ . '/admin/dashboard.js',
51        ]);
52        $this->addCSS(__PS_BASE_URI__ . $this->admin_webpath . '/themes/' . $this->bo_theme . '/css/vendor/nv.d3.css');
53    }
54
55    public function initPageHeaderToolbar()
56    {
57        $this->page_header_toolbar_title = $this->trans('Dashboard', [], 'Admin.Dashboard.Feature');
58        $this->page_header_toolbar_btn['switch_demo'] = [
59            'desc' => $this->trans('Demo mode', [], 'Admin.Dashboard.Feature'),
60            'icon' => 'process-icon-toggle-' . (Configuration::get('PS_DASHBOARD_SIMULATION') ? 'on' : 'off'),
61            'help' => $this->trans('This mode displays sample data so you can try your dashboard without real numbers.', [], 'Admin.Dashboard.Help'),
62        ];
63
64        parent::initPageHeaderToolbar();
65
66        // Remove the last element on this controller to match the title with the rule of the others
67        array_pop($this->meta_title);
68    }
69
70    protected function getOptionFields()
71    {
72        $forms = [];
73        $currency = new Currency(Configuration::get('PS_CURRENCY_DEFAULT'));
74        $carriers = Carrier::getCarriers((int) $this->context->language->id, true, false, false, null, Carrier::ALL_CARRIERS);
75        $modules = Module::getModulesOnDisk(true);
76
77        $forms = [
78            'payment' => ['title' => $this->trans('Average bank fees per payment method', [], 'Admin.Dashboard.Feature'), 'id' => 'payment'],
79            'carriers' => ['title' => $this->trans('Average shipping fees per shipping method', [], 'Admin.Dashboard.Feature'), 'id' => 'carriers'],
80            'other' => ['title' => $this->trans('Other settings', [], 'Admin.Dashboard.Feature'), 'id' => 'other'],
81        ];
82        foreach ($forms as &$form) {
83            $form['icon'] = 'tab-preferences';
84            $form['fields'] = [];
85            $form['submit'] = ['title' => $this->trans('Save', [], 'Admin.Actions')];
86        }
87
88        foreach ($modules as $module) {
89            if (isset($module->tab) && $module->tab == 'payments_gateways' && $module->id) {
90                $moduleClass = Module::getInstanceByName($module->name);
91                if (!$moduleClass->isEnabledForShopContext()) {
92                    continue;
93                }
94
95                $forms['payment']['fields']['CONF_' . strtoupper($module->name) . '_FIXED'] = [
96                    'title' => $module->displayName,
97                    'desc' => $this->trans(
98                        'Choose a fixed fee for each order placed in %currency% with %module%.',
99                        [
100                            '%currency' => $currency->iso_code,
101                            '%module%' => $module->displayName,
102                        ],
103                        'Admin.Dashboard.Help'
104                    ),
105                    'validation' => 'isPrice',
106                    'cast' => 'floatval',
107                    'type' => 'text',
108                    'defaultValue' => '0',
109                    'suffix' => $currency->iso_code,
110                ];
111                $forms['payment']['fields']['CONF_' . strtoupper($module->name) . '_VAR'] = [
112                    'title' => $module->displayName,
113                    'desc' => $this->trans(
114                        'Choose a variable fee for each order placed in %currency% with %module%. It will be applied on the total paid with taxes.',
115                        [
116                            '%currency' => $currency->iso_code,
117                            '%module%' => $module->displayName,
118                        ],
119                        'Admin.Dashboard.Help'
120                    ),
121                    'validation' => 'isPercentage',
122                    'cast' => 'floatval',
123                    'type' => 'text',
124                    'defaultValue' => '0',
125                    'suffix' => '%',
126                ];
127
128                if (Currency::isMultiCurrencyActivated()) {
129                    $forms['payment']['fields']['CONF_' . strtoupper($module->name) . '_FIXED_FOREIGN'] = [
130                        'title' => $module->displayName,
131                        'desc' => $this->trans(
132                            'Choose a fixed fee for each order placed with a foreign currency with %module%.',
133                            [
134                                '%module%' => $module->displayName,
135                            ],
136                            'Admin.Dashboard.Help'
137                        ),
138                        'validation' => 'isPrice',
139                        'cast' => 'floatval',
140                        'type' => 'text',
141                        'defaultValue' => '0',
142                        'suffix' => $currency->iso_code,
143                    ];
144                    $forms['payment']['fields']['CONF_' . strtoupper($module->name) . '_VAR_FOREIGN'] = [
145                        'title' => $module->displayName,
146                        'desc' => $this->trans(
147                            'Choose a variable fee for each order placed with a foreign currency with %module%. It will be applied on the total paid with taxes.',
148                             ['%module%' => $module->displayName],
149                             'Admin.Dashboard.Help'
150                            ),
151                        'validation' => 'isPercentage',
152                        'cast' => 'floatval',
153                        'type' => 'text',
154                        'defaultValue' => '0',
155                        'suffix' => '%',
156                    ];
157                }
158            }
159        }
160
161        foreach ($carriers as $carrier) {
162            $forms['carriers']['fields']['CONF_' . strtoupper($carrier['id_reference']) . '_SHIP'] = [
163                'title' => $carrier['name'],
164                'desc' => $this->trans(
165                    'For the carrier named %s, indicate the domestic delivery costs  in percentage of the price charged to customers.',
166                    [
167                        '%s' => $carrier['name'],
168                    ],
169                    'Admin.Dashboard.Help'
170                ),
171                'validation' => 'isPercentage',
172                'cast' => 'floatval',
173                'type' => 'text',
174                'defaultValue' => '0',
175                'suffix' => '%',
176            ];
177            $forms['carriers']['fields']['CONF_' . strtoupper($carrier['id_reference']) . '_SHIP_OVERSEAS'] = [
178                'title' => $carrier['name'],
179                'desc' => $this->trans(
180                    'For the carrier named %s, indicate the overseas delivery costs in percentage of the price charged to customers.',
181                    [
182                        '%s' => $carrier['name'],
183                    ],
184                    'Admin.Dashboard.Help'
185                ),
186                'validation' => 'isPercentage',
187                'cast' => 'floatval',
188                'type' => 'text',
189                'defaultValue' => '0',
190                'suffix' => '%',
191            ];
192        }
193
194        $forms['carriers']['description'] = $this->trans('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.', [], 'Admin.Dashboard.Help');
195
196        $forms['other']['fields']['CONF_AVERAGE_PRODUCT_MARGIN'] = [
197            'title' => $this->trans('Average gross margin percentage', [], 'Admin.Dashboard.Feature'),
198            'desc' => $this->trans('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.', [], 'Admin.Dashboard.Help'),
199            'validation' => 'isPercentage',
200            'cast' => 'intval',
201            'type' => 'text',
202            'defaultValue' => '0',
203            'suffix' => '%',
204        ];
205
206        $forms['other']['fields']['CONF_ORDER_FIXED'] = [
207            'title' => $this->trans('Other fees per order', [], 'Admin.Dashboard.Feature'),
208            'desc' => $this->trans('You should calculate this value by making the sum of all of your additional costs per order.', [], 'Admin.Dashboard.Help'),
209            'validation' => 'isPrice',
210            'cast' => 'floatval',
211            'type' => 'text',
212            'defaultValue' => '0',
213            'suffix' => $currency->iso_code,
214        ];
215
216        Media::addJsDef([
217            'dashboard_ajax_url' => $this->context->link->getAdminLink('AdminDashboard'),
218            'read_more' => '',
219        ]);
220
221        return $forms;
222    }
223
224    public function renderView()
225    {
226        if (Tools::isSubmit('profitability_conf')) {
227            return parent::renderOptions();
228        }
229
230        $testStatsDateUpdate = $this->context->cookie->__get('stats_date_update');
231        if (!empty($testStatsDateUpdate) && $this->context->cookie->__get('stats_date_update') < strtotime(date('Y-m-d'))) {
232            switch ($this->context->employee->preselect_date_range) {
233                case 'day':
234                    $date_from = date('Y-m-d');
235                    $date_to = date('Y-m-d');
236
237                    break;
238                case 'prev-day':
239                    $date_from = date('Y-m-d', strtotime('-1 day'));
240                    $date_to = date('Y-m-d', strtotime('-1 day'));
241
242                    break;
243                case 'month':
244                default:
245                    $date_from = date('Y-m-01');
246                    $date_to = date('Y-m-d');
247
248                    break;
249                case 'prev-month':
250                    $date_from = date('Y-m-01', strtotime('-1 month'));
251                    $date_to = date('Y-m-t', strtotime('-1 month'));
252
253                    break;
254                case 'year':
255                    $date_from = date('Y-01-01');
256                    $date_to = date('Y-m-d');
257
258                    break;
259                case 'prev-year':
260                    $date_from = date('Y-m-01', strtotime('-1 year'));
261                    $date_to = date('Y-12-t', strtotime('-1 year'));
262
263                    break;
264            }
265            $this->context->employee->stats_date_from = $date_from;
266            $this->context->employee->stats_date_to = $date_to;
267            $this->context->employee->update();
268            $this->context->cookie->__set('stats_date_update', strtotime(date('Y-m-d')));
269            $this->context->cookie->write();
270        }
271
272        $calendar_helper = new HelperCalendar();
273
274        $calendar_helper->setDateFrom(Tools::getValue('date_from', $this->context->employee->stats_date_from));
275        $calendar_helper->setDateTo(Tools::getValue('date_to', $this->context->employee->stats_date_to));
276
277        $stats_compare_from = $this->context->employee->stats_compare_from;
278        $stats_compare_to = $this->context->employee->stats_compare_to;
279
280        if (null === $stats_compare_from || $stats_compare_from == '0000-00-00') {
281            $stats_compare_from = null;
282        }
283
284        if (null === $stats_compare_to || $stats_compare_to == '0000-00-00') {
285            $stats_compare_to = null;
286        }
287
288        $calendar_helper->setCompareDateFrom($stats_compare_from);
289        $calendar_helper->setCompareDateTo($stats_compare_to);
290        $calendar_helper->setCompareOption(Tools::getValue('compare_date_option', $this->context->employee->stats_compare_option));
291
292        $params = [
293            'date_from' => $this->context->employee->stats_date_from,
294            'date_to' => $this->context->employee->stats_date_to,
295        ];
296
297        $moduleManagerBuilder = ModuleManagerBuilder::getInstance();
298        $moduleManager = $moduleManagerBuilder->build();
299
300        $this->tpl_view_vars = [
301            'date_from' => $this->context->employee->stats_date_from,
302            'date_to' => $this->context->employee->stats_date_to,
303            'hookDashboardZoneOne' => Hook::exec('dashboardZoneOne', $params),
304            'hookDashboardZoneTwo' => Hook::exec('dashboardZoneTwo', $params),
305            'action' => '#',
306            'warning' => $this->getWarningDomainName(),
307            'new_version_url' => Tools::getCurrentUrlProtocolPrefix() . _PS_API_DOMAIN_ . '/version/check_version.php?v=' . _PS_VERSION_ . '&lang=' . $this->context->language->iso_code . '&autoupgrade=' . (int) ($moduleManager->isInstalled('autoupgrade') && $moduleManager->isEnabled('autoupgrade')) . '&hosted_mode=' . (int) defined('_PS_HOST_MODE_'),
308            'dashboard_use_push' => Configuration::get('PS_DASHBOARD_USE_PUSH'),
309            'calendar' => $calendar_helper->generate(),
310            'PS_DASHBOARD_SIMULATION' => Configuration::get('PS_DASHBOARD_SIMULATION'),
311            'datepickerFrom' => Tools::getValue('datepickerFrom', $this->context->employee->stats_date_from),
312            'datepickerTo' => Tools::getValue('datepickerTo', $this->context->employee->stats_date_to),
313            'preselect_date_range' => Tools::getValue('preselectDateRange', $this->context->employee->preselect_date_range),
314            'help_center_link' => $this->getHelpCenterLink($this->context->language->iso_code),
315        ];
316
317        return parent::renderView();
318    }
319
320    public function postProcess()
321    {
322        if (Tools::isSubmit('submitDateRealTime')) {
323            if ($use_realtime = (int) Tools::getValue('submitDateRealTime')) {
324                $this->context->employee->stats_date_from = date('Y-m-d');
325                $this->context->employee->stats_date_to = date('Y-m-d');
326                $this->context->employee->stats_compare_option = HelperCalendar::DEFAULT_COMPARE_OPTION;
327                $this->context->employee->stats_compare_from = null;
328                $this->context->employee->stats_compare_to = null;
329                $this->context->employee->update();
330            }
331            Configuration::updateValue('PS_DASHBOARD_USE_PUSH', $use_realtime);
332        }
333
334        if (Tools::isSubmit('submitDateRange')) {
335            if (!Validate::isDate(Tools::getValue('date_from'))
336                || !Validate::isDate(Tools::getValue('date_to'))) {
337                $this->errors[] = $this->trans('The selected date range is not valid.', [], 'Admin.Notifications.Error');
338            }
339
340            if (Tools::getValue('datepicker_compare')) {
341                if (!Validate::isDate(Tools::getValue('compare_date_from'))
342                    || !Validate::isDate(Tools::getValue('compare_date_to'))) {
343                    $this->errors[] = $this->trans('The selected date range is not valid.', [], 'Admin.Notifications.Error');
344                }
345            }
346
347            if (!count($this->errors)) {
348                $this->context->employee->stats_date_from = Tools::getValue('date_from');
349                $this->context->employee->stats_date_to = Tools::getValue('date_to');
350                $this->context->employee->preselect_date_range = Tools::getValue('preselectDateRange');
351
352                if (Tools::getValue('datepicker_compare')) {
353                    $this->context->employee->stats_compare_from = Tools::getValue('compare_date_from');
354                    $this->context->employee->stats_compare_to = Tools::getValue('compare_date_to');
355                    $this->context->employee->stats_compare_option = Tools::getValue('compare_date_option');
356                } else {
357                    $this->context->employee->stats_compare_from = null;
358                    $this->context->employee->stats_compare_to = null;
359                    $this->context->employee->stats_compare_option = HelperCalendar::DEFAULT_COMPARE_OPTION;
360                }
361
362                $this->context->employee->update();
363            }
364        }
365
366        parent::postProcess();
367    }
368
369    protected function getWarningDomainName()
370    {
371        $warning = false;
372        if (Shop::isFeatureActive()) {
373            return;
374        }
375
376        $shop = Context::getContext()->shop;
377        if ($_SERVER['HTTP_HOST'] != $shop->domain && $_SERVER['HTTP_HOST'] != $shop->domain_ssl && Tools::getValue('ajax') == false && !defined('_PS_HOST_MODE_')) {
378            $warning = $this->trans('You are currently connected under the following domain name:', [], 'Admin.Dashboard.Notification') . ' <span style="color: #CC0000;">' . $_SERVER['HTTP_HOST'] . '</span><br />';
379            if (Configuration::get('PS_MULTISHOP_FEATURE_ACTIVE')) {
380                $warning .= $this->trans(
381                    'This is different from the shop domain name set in the Multistore settings: "%s".',
382                    [
383                        '%s' => $shop->domain,
384                    ],
385                    'Admin.Dashboard.Notification'
386                ) . $this->trans(
387                    'If this is your main domain, please {link}change it now{/link}.',
388                    [
389                        '{link}' => '<a href="index.php?controller=AdminShopUrl&id_shop_url=' . (int) $shop->id . '&updateshop_url&token=' . Tools::getAdminTokenLite('AdminShopUrl') . '">',
390                        '{/link}' => '</a>',
391                    ],
392                    'Admin.Dashboard.Notification'
393                );
394            } else {
395                $warning .= $this->trans('This is different from the domain name set in the "SEO & URLs" tab.', [], 'Admin.Dashboard.Notification') . '
396				' . $this->trans(
397                    'If this is your main domain, please {link}change it now{/link}.',
398                    [
399                        '{link}' => '<a href="index.php?controller=AdminMeta&token=' . Tools::getAdminTokenLite('AdminMeta') . '#meta_fieldset_shop_url">',
400                        '{/link}' => '</a>',
401                    ],
402                    'Admin.Dashboard.Notification'
403                );
404            }
405        }
406
407        return $warning;
408    }
409
410    public function ajaxProcessRefreshDashboard()
411    {
412        $id_module = null;
413        if ($module = Tools::getValue('module')) {
414            $module_obj = Module::getInstanceByName($module);
415            if (Validate::isLoadedObject($module_obj)) {
416                $id_module = $module_obj->id;
417            }
418        }
419
420        $params = [
421            'date_from' => $this->context->employee->stats_date_from,
422            'date_to' => $this->context->employee->stats_date_to,
423            'compare_from' => $this->context->employee->stats_compare_from,
424            'compare_to' => $this->context->employee->stats_compare_to,
425            'dashboard_use_push' => (int) Tools::getValue('dashboard_use_push'),
426            'extra' => (int) Tools::getValue('extra'),
427        ];
428
429        die(json_encode(Hook::exec('dashboardData', $params, $id_module, true, true, (int) Tools::getValue('dashboard_use_push'))));
430    }
431
432    public function ajaxProcessSetSimulationMode()
433    {
434        Configuration::updateValue('PS_DASHBOARD_SIMULATION', (int) Tools::getValue('PS_DASHBOARD_SIMULATION'));
435        die('k' . Configuration::get('PS_DASHBOARD_SIMULATION') . 'k');
436    }
437
438    /**
439     * Returns last news from the blog
440     *
441     * @throws PrestaShopException
442     */
443    public function displayAjaxGetBlogRss()
444    {
445        $newsFetcher = $this->get('prestashop.adapter.news.provider');
446        $return = $newsFetcher->getData($this->context->language->iso_code);
447
448        // Response
449        header('Content-Type: application/json');
450        $this->ajaxRender(json_encode($return));
451    }
452
453    public function ajaxProcessSaveDashConfig()
454    {
455        $return = ['has_errors' => false, 'errors' => []];
456        $module = Tools::getValue('module');
457        $hook = Tools::getValue('hook');
458        $configs = Tools::getValue('configs');
459
460        $params = [
461            'date_from' => $this->context->employee->stats_date_from,
462            'date_to' => $this->context->employee->stats_date_to,
463        ];
464
465        if (Validate::isModuleName($module) && $module_obj = Module::getInstanceByName($module)) {
466            $return['errors'] = $module_obj->validateDashConfig($configs);
467            if (count($return['errors'])) {
468                $return['has_errors'] = true;
469            } else {
470                $return['has_errors'] = $module_obj->saveDashConfig($configs);
471            }
472        }
473
474        if (Validate::isHookName($hook) && method_exists($module_obj, $hook)) {
475            $return['widget_html'] = $module_obj->$hook($params);
476        }
477
478        die(json_encode($return));
479    }
480
481    /**
482     * Returns the Help center link for the provided locale
483     *
484     * @param string $languageCode 2-letter locale code
485     *
486     * @return string
487     */
488    private function getHelpCenterLink($languageCode)
489    {
490        $links = [
491            'fr' => 'https://www.prestashop.com/fr/contact?utm_source=back-office&utm_medium=links&utm_campaign=help-center-fr&utm_content=download17',
492            'en' => 'https://www.prestashop.com/en/contact?utm_source=back-office&utm_medium=links&utm_campaign=help-center-en&utm_content=download17',
493            'es' => 'https://www.prestashop.com/es/contacto?utm_source=back-office&utm_medium=links&utm_campaign=help-center-es&utm_content=download17',
494            'de' => 'https://www.prestashop.com/de/kontakt?utm_source=back-office&utm_medium=links&utm_campaign=help-center-de&utm_content=download17',
495            'it' => 'https://www.prestashop.com/it/contatti?utm_source=back-office&utm_medium=links&utm_campaign=help-center-it&utm_content=download17',
496            'nl' => 'https://www.prestashop.com/nl/contacteer-ons?utm_source=back-office&utm_medium=links&utm_campaign=help-center-nl&utm_content=download17',
497            'pt' => 'https://www.prestashop.com/pt/contato?utm_source=back-office&utm_medium=links&utm_campaign=help-center-pt&utm_content=download17',
498            'pl' => 'https://www.prestashop.com/pl/kontakt?utm_source=back-office&utm_medium=links&utm_campaign=help-center-pl&utm_content=download17',
499        ];
500
501        return isset($links[$languageCode]) ? $links[$languageCode] : $links['en'];
502    }
503}
504