1<?php
2/**
3 * 2007-2016 PrestaShop
4 *
5 * thirty bees is an extension to the PrestaShop e-commerce software developed by PrestaShop SA
6 * Copyright (C) 2017-2018 thirty bees
7 *
8 * NOTICE OF LICENSE
9 *
10 * This source file is subject to the Open Software License (OSL 3.0)
11 * that is bundled with this package in the file LICENSE.txt.
12 * It is also available through the world-wide-web at this URL:
13 * http://opensource.org/licenses/osl-3.0.php
14 * If you did not receive a copy of the license and are unable to
15 * obtain it through the world-wide-web, please send an email
16 * to license@thirtybees.com so we can send you a copy immediately.
17 *
18 * DISCLAIMER
19 *
20 * Do not edit or add to this file if you wish to upgrade PrestaShop to newer
21 * versions in the future. If you wish to customize PrestaShop for your
22 * needs please refer to https://www.thirtybees.com for more information.
23 *
24 *  @author    thirty bees <contact@thirtybees.com>
25 *  @author    PrestaShop SA <contact@prestashop.com>
26 *  @copyright 2017-2018 thirty bees
27 *  @copyright 2007-2016 PrestaShop SA
28 *  @license   http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
29 *  PrestaShop is an internationally registered trademark & property of PrestaShop SA
30 */
31
32/**
33 * Class ModuleGraphCore
34 *
35 * @since 1.0.0
36 */
37abstract class ModuleGraphCore extends Module
38{
39    // @codingStandardsIgnoreStart
40    /** @var Employee $_employee */
41    protected $_employee;
42    /** @var int[] graph data */
43    protected $_values = [];
44    /** @var string[] graph legends (X axis) */
45    protected $_legend = [];
46    /**@var string[] graph titles */
47    protected $_titles = ['main' => null, 'x' => null, 'y' => null];
48    /** @var ModuleGraphEngine graph engine */
49    protected $_render;
50    // @codingStandardsIgnoreEnd
51
52    /**
53     * @param int $idEmployee
54     *
55     * @since 1.0.0
56     * @version 1.0.0 Initial version
57     */
58    public function setEmployee($idEmployee)
59    {
60        $this->_employee = new Employee($idEmployee);
61    }
62
63    /**
64     * @param int $idLang
65     *
66     * @since 1.0.0
67     * @version 1.0.0 Initial version
68     */
69    public function setLang($idLang)
70    {
71        $this->_id_lang = $idLang;
72    }
73
74    /**
75     * @param      $layers
76     * @param bool $legend
77     *
78     * @since 1.0.0
79     * @version 1.0.0 Initial version
80     */
81    protected function setDateGraph($layers, $legend = false)
82    {
83        // Get dates in a manageable format
84        $fromArray = getdate(strtotime($this->_employee->stats_date_from));
85        $toArray = getdate(strtotime($this->_employee->stats_date_to));
86
87        // If the granularity is inferior to 1 day
88        if ($this->_employee->stats_date_from == $this->_employee->stats_date_to) {
89            if ($legend) {
90                for ($i = 0; $i < 24; $i++) {
91                    if ($layers == 1) {
92                        $this->_values[$i] = 0;
93                    } else {
94                        for ($j = 0; $j < $layers; $j++) {
95                            $this->_values[$j][$i] = 0;
96                        }
97                    }
98                    $this->_legend[$i] = ($i % 2) ? '' : sprintf('%02dh', $i);
99                }
100            }
101            if (is_callable([$this, 'setDayValues'])) {
102                $this->setDayValues($layers);
103            }
104        } elseif (strtotime($this->_employee->stats_date_to) - strtotime($this->_employee->stats_date_from) <= 2678400) {
105            // If the granularity is inferior to 1 month
106            // @TODO : change to manage 28 to 31 days
107            if ($legend) {
108                $days = [];
109                if ($fromArray['mon'] == $toArray['mon']) {
110                    for ($i = $fromArray['mday']; $i <= $toArray['mday']; ++$i) {
111                        $days[] = $i;
112                    }
113                } else {
114                    $imax = date('t', mktime(0, 0, 0, $fromArray['mon'], 1, $fromArray['year']));
115                    for ($i = $fromArray['mday']; $i <= $imax; ++$i) {
116                        $days[] = $i;
117                    }
118                    for ($i = 1; $i <= $toArray['mday']; ++$i) {
119                        $days[] = $i;
120                    }
121                }
122                foreach ($days as $i) {
123                    if ($layers == 1) {
124                        $this->_values[$i] = 0;
125                    } else {
126                        for ($j = 0; $j < $layers; $j++) {
127                            $this->_values[$j][$i] = 0;
128                        }
129                    }
130                    $this->_legend[$i] = ($i % 2) ? '' : sprintf('%02d', $i);
131                }
132            }
133            if (is_callable([$this, 'setMonthValues'])) {
134                $this->setMonthValues($layers);
135            }
136        } elseif (strtotime('-1 year', strtotime($this->_employee->stats_date_to)) < strtotime($this->_employee->stats_date_from)) {
137            // If the granularity is less than 1 year
138            if ($legend) {
139                $months = [];
140                if ($fromArray['year'] == $toArray['year']) {
141                    for ($i = $fromArray['mon']; $i <= $toArray['mon']; ++$i) {
142                        $months[] = $i;
143                    }
144                } else {
145                    for ($i = $fromArray['mon']; $i <= 12; ++$i) {
146                        $months[] = $i;
147                    }
148                    for ($i = 1; $i <= $toArray['mon']; ++$i) {
149                        $months[] = $i;
150                    }
151                }
152                foreach ($months as $i) {
153                    if ($layers == 1) {
154                        $this->_values[$i] = 0;
155                    } else {
156                        for ($j = 0; $j < $layers; $j++) {
157                            $this->_values[$j][$i] = 0;
158                        }
159                    }
160                    $this->_legend[$i] = sprintf('%02d', $i);
161                }
162            }
163            if (is_callable([$this, 'setYearValues'])) {
164                $this->setYearValues($layers);
165            }
166        } else {
167            // If the granularity is greater than 1 year
168            if ($legend) {
169                $years = [];
170                for ($i = $fromArray['year']; $i <= $toArray['year']; ++$i) {
171                    $years[] = $i;
172                }
173                foreach ($years as $i) {
174                    if ($layers == 1) {
175                        $this->_values[$i] = 0;
176                    } else {
177                        for ($j = 0; $j < $layers; $j++) {
178                            $this->_values[$j][$i] = 0;
179                        }
180                    }
181                    $this->_legend[$i] = sprintf('%04d', $i);
182                }
183            }
184            if (is_callable([$this, 'setAllTimeValues'])) {
185                $this->setAllTimeValues($layers);
186            }
187        }
188    }
189
190    /**
191     * @param $datas
192     *
193     * @since 1.0.0
194     * @version 1.0.0 Initial version
195     */
196    protected function csvExport($datas)
197    {
198        $context = Context::getContext();
199
200        $this->setEmployee($context->employee->id);
201        $this->setLang($context->language->id);
202
203        $layers = isset($datas['layers']) ?  $datas['layers'] : 1;
204        if (isset($datas['option'])) {
205            $this->setOption($datas['option'], $layers);
206        }
207        $this->getData($layers);
208
209        // @todo use native CSV PHP functions ?
210        // Generate first line (column titles)
211        if (is_array($this->_titles['main'])) {
212            for ($i = 0, $totalMain = count($this->_titles['main']); $i <= $totalMain; $i++) {
213                if ($i > 0) {
214                    $this->_csv .= ';';
215                }
216                if (isset($this->_titles['main'][$i])) {
217                    $this->_csv .= $this->_titles['main'][$i];
218                }
219            }
220        } else { // If there is only one column title, there is in fast two column (the first without title)
221            $this->_csv .= ';'.$this->_titles['main'];
222        }
223        $this->_csv .= "\n";
224        if (count($this->_legend)) {
225            $total = 0;
226            if ($datas['type'] == 'pie') {
227                foreach ($this->_legend as $key => $legend) {
228                    for ($i = 0, $totalMain = (is_array($this->_titles['main']) ? count($this->_values) : 1); $i < $totalMain; ++$i) {
229                        $total += (is_array($this->_values[$i])  ? $this->_values[$i][$key] : $this->_values[$key]);
230                    }
231                }
232            }
233            foreach ($this->_legend as $key => $legend) {
234                $this->_csv .= $legend.';';
235                for ($i = 0, $totalMain = (is_array($this->_titles['main']) ? count($this->_values) : 1); $i < $totalMain; ++$i) {
236                    if (!isset($this->_values[$i]) || !is_array($this->_values[$i])) {
237                        if (isset($this->_values[$key])) {
238                            // We don't want strings to be divided. Example: product name
239                            if (is_numeric($this->_values[$key])) {
240                                $this->_csv .= $this->_values[$key] / (($datas['type'] == 'pie') ? $total : 1);
241                            } else {
242                                $this->_csv .= $this->_values[$key];
243                            }
244                        } else {
245                            $this->_csv .= '0';
246                        }
247                    } else {
248                        // We don't want strings to be divided. Example: product name
249                        if (is_numeric($this->_values[$i][$key])) {
250                            $this->_csv .= $this->_values[$i][$key] / (($datas['type'] == 'pie') ? $total : 1);
251                        } else {
252                            $this->_csv .= $this->_values[$i][$key];
253                        }
254                    }
255                    $this->_csv .= ';';
256                }
257                $this->_csv .= "\n";
258            }
259        }
260        $this->_displayCsv();
261    }
262
263    /**
264     * @since 1.0.0
265     * @version 1.0.0 Initial version
266     */
267    protected function _displayCsv()
268    {
269        if (ob_get_level() && ob_get_length() > 0) {
270            ob_end_clean();
271        }
272        header('Content-Type: application/octet-stream');
273        header('Content-Disposition: attachment; filename="'.$this->displayName.' - '.time().'.csv"');
274        echo $this->_csv;
275        exit;
276    }
277
278    /**
279     * @param mixed $render
280     * @param mixed $type
281     * @param mixed $width
282     * @param mixed $height
283     * @param mixed $layers
284     *
285     * @since 1.0.0
286     * @version 1.0.0 Initial version
287     */
288    public function create($render, $type, $width, $height, $layers)
289    {
290        if (!Validate::isModuleName($render)) {
291            die(Tools::displayError());
292        }
293        if (!file_exists($file = _PS_ROOT_DIR_.'/modules/'.$render.'/'.$render.'.php')) {
294            die(Tools::displayError());
295        }
296        require_once($file);
297        $this->_render = new $render($type);
298
299        $this->getData($layers);
300        $this->_render->createValues($this->_values);
301        $this->_render->setSize($width, $height);
302        $this->_render->setLegend($this->_legend);
303        $this->_render->setTitles($this->_titles);
304    }
305
306    /**
307     * @since 1.0.0
308     * @version 1.0.0 Initial version
309     */
310    public function draw()
311    {
312        $this->_render->draw();
313    }
314
315    /**
316     * @param mixed $option
317     * @param int   $layers
318     *
319     * @since 1.0.0
320     * @version 1.0.0 Initial version
321     */
322    public function setOption($option, $layers = 1)
323    {
324    }
325
326    /**
327     * @param array $params
328     *
329     * @return array|mixed|string
330     *
331     * @since 1.0.0
332     * @version 1.0.0 Initial version
333     */
334    public function engine($params)
335    {
336        $context = Context::getContext();
337        $render = Configuration::get('PS_STATS_RENDER');
338        $idEmployee = (int) $context->employee->id;
339        $idLang = (int) $context->language->id;
340
341        if (!isset($params['layers'])) {
342            $params['layers'] = 1;
343        }
344        if (!isset($params['type'])) {
345            $params['type'] = 'column';
346        }
347        if (!isset($params['width'])) {
348            $params['width'] = '100%';
349        }
350        if (!isset($params['height'])) {
351            $params['height'] = 270;
352        }
353
354        $urlParams = $params;
355        $urlParams['render'] = $render;
356        $urlParams['module'] = Tools::getValue('module');
357        $urlParams['id_employee'] = $idEmployee;
358        $urlParams['id_lang'] = $idLang;
359        $drawer = 'drawer.php?'.http_build_query(array_map('Tools::safeOutput', $urlParams), '', '&');
360
361        if (file_exists(_PS_ROOT_DIR_.'/modules/'.$render.'/'.$render.'.php')) {
362            require_once(_PS_ROOT_DIR_.'/modules/'.$render.'/'.$render.'.php');
363
364            return call_user_func([$render, 'hookGraphEngine'], $params, $drawer);
365        } else {
366            return call_user_func(['ModuleGraphEngine', 'hookGraphEngine'], $params, $drawer);
367        }
368    }
369
370    /**
371     * @param null         $employee
372     * @param Context|null $context
373     *
374     * @return bool|Employee|null
375     *
376     * @since 1.0.0
377     * @version 1.0.0 Initial version
378     */
379    public static function getEmployee($employee = null, Context $context = null)
380    {
381        if (!Validate::isLoadedObject($employee)) {
382            if (!$context) {
383                $context = Context::getContext();
384            }
385            if (!Validate::isLoadedObject($context->employee)) {
386                return false;
387            }
388            $employee = $context->employee;
389        }
390
391        if (empty($employee->stats_date_from) || empty($employee->stats_date_to)
392            || $employee->stats_date_from == '0000-00-00' || $employee->stats_date_to == '0000-00-00') {
393            if (empty($employee->stats_date_from) || $employee->stats_date_from == '0000-00-00') {
394                $employee->stats_date_from = date('Y').'-01-01';
395            }
396            if (empty($employee->stats_date_to) || $employee->stats_date_to == '0000-00-00') {
397                $employee->stats_date_to = date('Y').'-12-31';
398            }
399            $employee->update();
400        }
401
402        return $employee;
403    }
404
405    /**
406     * @return string
407     *
408     * @since 1.0.0
409     * @version 1.0.0 Initial version
410     */
411    public function getDate()
412    {
413        return ModuleGraph::getDateBetween($this->_employee);
414    }
415
416    /**
417     * @param null $employee
418     *
419     * @return string
420     *
421     * @since 1.0.0
422     * @version 1.0.0 Initial version
423     */
424    public static function getDateBetween($employee = null)
425    {
426        if ($employee = ModuleGraph::getEmployee($employee)) {
427            return ' \''.$employee->stats_date_from.' 00:00:00\' AND \''.$employee->stats_date_to.' 23:59:59\' ';
428        }
429
430        return ' \''.date('Y-m').'-01 00:00:00\' AND \''.date('Y-m-t').' 23:59:59\' ';
431    }
432
433    /**
434     * @return mixed
435     *
436     * @since 1.0.0
437     * @version 1.0.0 Initial version
438     */
439    public function getLang()
440    {
441        return $this->_id_lang;
442    }
443
444    /**
445     * @param $layers
446     *
447     * @return mixed
448     *
449     * @since 1.0.0
450     * @version 1.0.0 Initial version
451     */
452    abstract protected function getData($layers);
453}
454