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
32use \GuzzleHttp\Exception\RequestException;
33
34/**
35 * Class EmployeeCore
36 *
37 * @since 1.0.0
38 */
39class EmployeeCore extends ObjectModel
40{
41    // @codingStandardsIgnoreStart
42    /** @var string Determine employee profile */
43    public $id_profile;
44    /** @var string employee language */
45    public $id_lang;
46    /** @var string Lastname */
47    public $lastname;
48    /** @var string Firstname */
49    public $firstname;
50    /** @var string e-mail */
51    public $email;
52    /** @var string Password */
53    public $passwd;
54    /** @var datetime Password */
55    public $last_passwd_gen;
56    /** @var string $stats_date_from */
57    public $stats_date_from;
58    /** @var string $stats_date_to */
59    public $stats_date_to;
60    /** @var string $stats_compare_from */
61    public $stats_compare_from;
62    /** @var string $stats_compare_to */
63    public $stats_compare_to;
64    /** @var int $stats_compare_option */
65    public $stats_compare_option = 1;
66    /** @var string $preselect_date_range */
67    public $preselect_date_range;
68    /** @var string Display back office background in the specified color */
69    public $bo_color;
70    public $default_tab;
71    /** @var string employee's chosen theme */
72    public $bo_theme;
73    /** @var string employee's chosen css file */
74    public $bo_css = 'admin-theme.css';
75    /** @var int employee desired screen width */
76    public $bo_width;
77
78    /* Deprecated */
79    /** @var bool, false */
80    public $bo_menu = 1;
81    public $bo_show_screencast = false;
82    /** @var bool Status */
83    public $active = 1;
84    /** @var bool Optin status */
85    public $optin = 1;
86
87    /* employee notifications */
88    public $remote_addr;
89    public $id_last_order;
90    public $id_last_customer_message;
91    public $id_last_customer;
92    protected $associated_shops = [];
93    // @codingStandardsIgnoreEnd
94
95    /**
96     * @see ObjectModel::$definition
97     */
98    public static $definition = [
99        'table'   => 'employee',
100        'primary' => 'id_employee',
101        'fields'  => [
102            'lastname'                 => ['type' => self::TYPE_STRING, 'validate' => 'isName',        'required' => true, 'size' => 32 ],
103            'firstname'                => ['type' => self::TYPE_STRING, 'validate' => 'isName',        'required' => true, 'size' => 32 ],
104            'email'                    => ['type' => self::TYPE_STRING, 'validate' => 'isEmail',       'required' => true, 'size' => 128],
105            'id_lang'                  => ['type' => self::TYPE_INT,    'validate' => 'isUnsignedInt', 'required' => true               ],
106            'passwd'                   => ['type' => self::TYPE_STRING, 'validate' => 'isPasswdAdmin', 'required' => true, 'size' => 60 ],
107            'last_passwd_gen'          => ['type' => self::TYPE_STRING                                                                  ],
108            'active'                   => ['type' => self::TYPE_BOOL,   'validate' => 'isBool'                                          ],
109            'optin'                    => ['type' => self::TYPE_BOOL,   'validate' => 'isBool'                                          ],
110            'id_profile'               => ['type' => self::TYPE_INT,    'validate' => 'isInt',         'required' => true               ],
111            'bo_color'                 => ['type' => self::TYPE_STRING, 'validate' => 'isColor',                           'size' => 32 ],
112            'default_tab'              => ['type' => self::TYPE_INT,    'validate' => 'isInt'                                           ],
113            'bo_theme'                 => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName',                     'size' => 32 ],
114            'bo_css'                   => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName',                     'size' => 64 ],
115            'bo_width'                 => ['type' => self::TYPE_INT,    'validate' => 'isUnsignedInt'                                   ],
116            'bo_menu'                  => ['type' => self::TYPE_BOOL,   'validate' => 'isBool'                                          ],
117            'stats_date_from'          => ['type' => self::TYPE_DATE,   'validate' => 'isDate'                                          ],
118            'stats_date_to'            => ['type' => self::TYPE_DATE,   'validate' => 'isDate'                                          ],
119            'stats_compare_from'       => ['type' => self::TYPE_DATE,   'validate' => 'isDate'                                          ],
120            'stats_compare_to'         => ['type' => self::TYPE_DATE,   'validate' => 'isDate'                                          ],
121            'stats_compare_option'     => ['type' => self::TYPE_INT,    'validate' => 'isUnsignedInt'                                   ],
122            'preselect_date_range'     => ['type' => self::TYPE_STRING,                                                    'size' => 32 ],
123            'id_last_order'            => ['type' => self::TYPE_INT,    'validate' => 'isUnsignedInt'                                   ],
124            'id_last_customer_message' => ['type' => self::TYPE_INT,    'validate' => 'isUnsignedInt'                                   ],
125            'id_last_customer'         => ['type' => self::TYPE_INT,    'validate' => 'isUnsignedInt'                                   ],
126        ],
127    ];
128
129    protected $webserviceParameters = [
130        'fields' => [
131            'id_lang'            => ['xlink_resource' => 'languages'],
132            'last_passwd_gen'    => ['setter' => null],
133            'stats_date_from'    => ['setter' => null],
134            'stats_date_to'      => ['setter' => null],
135            'stats_compare_from' => ['setter' => null],
136            'stats_compare_to'   => ['setter' => null],
137            'passwd'             => ['setter' => 'setWsPasswd'],
138        ],
139    ];
140
141    /**
142     * EmployeeCore constructor.
143     *
144     * @param int|null $id
145     * @param int|null $idLang
146     * @param int|null $idShop
147     *
148     * @throws PrestaShopDatabaseException
149     * @throws PrestaShopException
150     * @since   1.0.0
151     * @version 1.0.0 Initial version
152     */
153    public function __construct($id = null, $idLang = null, $idShop = null)
154    {
155        parent::__construct($id, null, $idShop);
156
157        if (!is_null($idLang)) {
158            $this->id_lang = (int) (Language::getLanguage($idLang) !== false) ? $idLang : Configuration::get('PS_LANG_DEFAULT');
159        }
160
161        if ($this->id) {
162            $this->associated_shops = $this->getAssociatedShops();
163        }
164
165        $this->image_dir = _PS_EMPLOYEE_IMG_DIR_;
166    }
167
168    /**
169     * Return list of employees
170     *
171     * @param bool $activeOnly Filter employee by active status
172     *
173     * @return array|false Employees or false
174     *
175     * @throws PrestaShopDatabaseException
176     * @throws PrestaShopException
177     * @since   1.0.0
178     * @version 1.0.0 Initial version
179     */
180    public static function getEmployees($activeOnly = true)
181    {
182        $sql = new DbQuery();
183        $sql->select('`id_employee`, `firstname`, `lastname`');
184        $sql->from(bqSQL(static::$definition['table']));
185        if ($activeOnly) {
186            $sql->where('`active` = 1');
187        }
188        $sql->orderBy('`lastname` ASC');
189
190        return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql);
191    }
192
193    /**
194     * @param string $email
195     *
196     * @return bool
197     *
198     * @since   1.0.0
199     * @version 1.0.0 Initial version
200     * @throws PrestaShopException
201     */
202    public static function employeeExists($email)
203    {
204        if (!Validate::isEmail($email)) {
205            die(Tools::displayError());
206        }
207
208        return (bool) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue(
209            (new DbQuery())
210                ->select('`id_employee`')
211                ->from('employee')
212                ->where('`email` = \''.pSQL($email).'\'')
213        );
214    }
215
216    /**
217     * @param int  $idProfile
218     * @param bool $activeOnly
219     *
220     * @return array|false|mysqli_result|null|PDOStatement|resource
221     *
222     * @throws PrestaShopDatabaseException
223     * @throws PrestaShopException
224     * @since   1.0.0
225     * @version 1.0.0 Initial version
226     */
227    public static function getEmployeesByProfile($idProfile, $activeOnly = false)
228    {
229        $sql = new DbQuery();
230        $sql->select('*');
231        $sql->from(bqSQL(static::$definition['table']));
232        $sql->where('`id_profile` = '.(int) $idProfile);
233        if ($activeOnly) {
234            $sql->where('`active` = 1');
235        }
236
237        return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql);
238    }
239
240    /**
241     * @param int $idEmployee
242     *
243     * @return bool
244     *
245     * @throws PrestaShopDatabaseException
246     * @throws PrestaShopException
247     * @since   1.0.0
248     * @version 1.0.0 Initial version
249     */
250    public static function setLastConnectionDate($idEmployee)
251    {
252        return Db::getInstance()->update(
253            bqSQL(static::$definition['table']),
254            [
255                'last_connection_date' => ['type' => 'sql', 'value' => 'CURRENT_DATE()'],
256            ],
257            '`id_employee` = '.(int) $idEmployee.' AND `last_connection_date` < CURRENT_DATE()'
258        );
259    }
260
261    /**
262     * @see     ObjectModel::getFields()
263     * @return array
264     *
265     * @since   1.0.0
266     * @version 1.0.0 Initial version
267     * @throws PrestaShopException
268     */
269    public function getFields()
270    {
271        if (empty($this->stats_date_from) || $this->stats_date_from == '0000-00-00') {
272            $this->stats_date_from = date('Y-m-d', strtotime('-1 month'));
273        }
274
275        if (empty($this->stats_compare_from) || $this->stats_compare_from == '0000-00-00') {
276            $this->stats_compare_from = null;
277        }
278
279        if (empty($this->stats_date_to) || $this->stats_date_to == '0000-00-00') {
280            $this->stats_date_to = date('Y-m-d');
281        }
282
283        if (empty($this->stats_compare_to) || $this->stats_compare_to == '0000-00-00') {
284            $this->stats_compare_to = null;
285        }
286
287        return parent::getFields();
288    }
289
290    /**
291     * @param bool $autoDate
292     * @param bool $nullValues
293     *
294     * @return bool
295     *
296     * @since   1.0.0
297     * @version 1.0.0 Initial version
298     * @throws PrestaShopException
299     */
300    public function add($autoDate = true, $nullValues = true)
301    {
302        $this->last_passwd_gen = date('Y-m-d H:i:s', strtotime('-'.Configuration::get('PS_PASSWD_TIME_BACK').'minutes'));
303        $this->saveOptin();
304        $this->updateTextDirection();
305
306        return parent::add($autoDate, $nullValues);
307    }
308
309    /**
310     * Subscribe to the thirty bees newsletter. Also resets $this->optin on
311     * failure.
312     *
313     * @return bool Wether un/registration was successful.
314     *
315     * @since   1.0.0
316     * @version 1.0.0 Initial version
317     * @version 1.0.6 Added return value.
318     */
319    protected function saveOptin()
320    {
321        $success = true;
322
323        if (!defined('TB_INSTALLATION_IN_PROGRESS')) {
324            if ($this->optin) {
325                $context = Context::getContext();
326
327                $guzzle = new \GuzzleHttp\Client([
328                    'base_uri'    => 'https://api.thirtybees.com',
329                    'timeout'     => 20,
330                    'verify'      => _PS_TOOL_DIR_.'cacert.pem',
331                ]);
332
333                try {
334                    $body = $guzzle->post(
335                        '/newsletter/', [
336                            'json' => [
337                                'email'    => $this->email,
338                                'fname'    => $this->firstname,
339                                'lname'    => $this->lastname,
340                                'activity' => Configuration::get('PS_SHOP_ACTIVITY'),
341                                'country'  => $context->country->iso_code,
342                                'language' => $context->language->iso_code,
343                                'URL'      => $context->shop->getBaseURL(),
344                            ],
345                        ]
346                    )->getBody();
347                } catch (RequestException $e) {
348                    $success = false;
349                    $this->optin = false;
350                }
351                if ((string) $body) {
352                    // Service itsself wasn't successful.
353                    $success = false;
354                    $this->optin = false;
355                }
356            } else {
357                // TODO: actually unregister
358            }
359        }
360
361        return $success;
362    }
363
364    /**
365     * @since   1.0.0
366     * @version 1.0.0 Initial version
367     * @version 1.0.8 Operate during initial shop installation as well.
368     */
369    protected function updateTextDirection()
370    {
371        if (defined('_PS_ADMIN_DIR_')) {
372            $path = _PS_ADMIN_DIR_.'/themes/'.$this->bo_theme.'/css/';
373        } else {
374            // Probably installation in progress.
375            $path = _PS_ROOT_DIR_.'/admin/themes/'.$this->bo_theme.'/css/';
376            if ( ! is_dir($path)) {
377                $path = _PS_ROOT_DIR_.'/admin-dev/themes/'.$this->bo_theme.'/css/';
378                if ( ! is_dir($path)) {
379                    // Give up.
380                    return;
381                }
382            }
383        }
384
385        $language = new Language($this->id_lang);
386
387        if ($language->is_rtl && !strpos($this->bo_css, '_rtl')) {
388            $boCss = preg_replace('/^(.*)\.css$/', '$1_rtl.css', $this->bo_css);
389            $boCss = str_replace('schemes/', 'schemes_rtl/', $boCss);
390
391            if (file_exists($path.$boCss)) {
392                $this->bo_css = $boCss;
393            }
394        } elseif (!$language->is_rtl && strpos($this->bo_css, '_rtl')) {
395            $boCss = str_replace('_rtl', '', $this->bo_css);
396
397            if (file_exists($path.$boCss)) {
398                $this->bo_css = $boCss;
399            }
400        }
401    }
402
403    /**
404     * Update the database record. Also used by AdminDashboardController for
405     * newsletter registration.
406     *
407     * @param bool $nullValues
408     *
409     * @return bool
410     *
411     * @since   1.0.0
412     * @version 1.0.0 Initial version
413     */
414    public function update($nullValues = false)
415    {
416        $success = true;
417
418        if (empty($this->stats_date_from) || $this->stats_date_from == '0000-00-00') {
419            $this->stats_date_from = date('Y-m-d');
420        }
421
422        if (empty($this->stats_date_to) || $this->stats_date_to == '0000-00-00') {
423            $this->stats_date_to = date('Y-m-d');
424        }
425
426        $currentEmployee = new Employee((int) $this->id);
427        if ($currentEmployee->optin != $this->optin
428            || $currentEmployee->email != $this->email
429            || !Configuration::get('TB_STORE_REGISTERED')) {
430            $success = $this->saveOptin();
431        }
432
433        $this->updateTextDirection();
434
435        return $success && parent::update($nullValues);
436    }
437
438    /**
439     * Return employee instance from its e-mail (optionally check password)
440     *
441     * @param string $email             E-mail
442     * @param string $plainTextPassword Password is also checked if specified
443     * @param bool   $activeOnly        Filter employee by active status
444     *
445     * @return Employee|bool Employee instance
446     *
447     * @throws PrestaShopDatabaseException
448     * @throws PrestaShopException
449     * @since   1.0.0
450     * @version 1.0.0 Initial version
451     */
452    public function getByEmail($email, $plainTextPassword = null, $activeOnly = true)
453    {
454        if (!Validate::isEmail($email) || ($plainTextPassword && !Validate::isPasswdAdmin($plainTextPassword))) {
455            return false;
456        }
457
458        $sql = new DbQuery();
459        $sql->select('*');
460        $sql->from('employee');
461        $sql->where('`email` = \''.pSQL($email).'\'');
462        if ($activeOnly) {
463            $sql->where('`active` = 1');
464        }
465        $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow($sql);
466
467        if (!$result) {
468            return false;
469        }
470
471        // If password is provided but doesn't match.
472        if ($plainTextPassword && !password_verify($plainTextPassword, $result['passwd'])) {
473            // Check if it matches the legacy md5 hashing and, if it does, rehash it.
474            if (Validate::isMd5($result['passwd']) && $result['passwd'] === md5(_COOKIE_KEY_.$plainTextPassword)) {
475                $newHash = Tools::hash($plainTextPassword);
476                Db::getInstance()->update(
477                    bqSQL(static::$definition['table']),
478                    [
479                        'passwd' => pSQL($newHash),
480                    ],
481                    'id_employee = '.(int) $result['id_employee']
482                );
483                $result['passwd'] = $newHash;
484            } else {
485                return false;
486            }
487        }
488
489        $this->id = $result['id_employee'];
490        $this->id_profile = $result['id_profile'];
491        foreach ($result as $key => $value) {
492            if (property_exists($this, $key)) {
493                $this->{$key} = $value;
494            }
495        }
496
497        return $this;
498    }
499
500    /**
501     * @return bool
502     *
503     * @since   1.0.0
504     * @version 1.0.0 Initial version
505     * @throws PrestaShopException
506     */
507    public function isLastAdmin()
508    {
509        return ($this->isSuperAdmin()
510            && Employee::countProfile($this->id_profile, true) == 1
511            && $this->active
512        );
513    }
514
515    /**
516     * Check if current employee is super administrator
517     *
518     * @return bool
519     *
520     * @since   1.0.0
521     * @version 1.0.0 Initial version
522     */
523    public function isSuperAdmin()
524    {
525        return $this->id_profile == _PS_ADMIN_PROFILE_;
526    }
527
528    /**
529     * @param int  $idProfile
530     * @param bool $activeOnly
531     *
532     * @return false|null|string
533     *
534     * @since   1.0.0
535     * @version 1.0.0 Initial version
536     * @throws PrestaShopException
537     */
538    public static function countProfile($idProfile, $activeOnly = false)
539    {
540        $sql = new DbQuery();
541        $sql->select('COUNT(*)');
542        $sql->from(bqSQL(static::$definition['table']));
543        $sql->where('`id_profile` = '.(int) $idProfile);
544        if ($activeOnly) {
545            $sql->where('`active` = 1');
546        }
547
548        return Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($sql);
549    }
550
551    /**
552     * @param string $plainTextPassword
553     *
554     * @return bool
555     *
556     * @since   1.0.0
557     * @version 1.0.0 Initial version
558     */
559    public function setWsPasswd($plainTextPassword)
560    {
561        if ($this->id != 0) {
562            if ($this->passwd != $plainTextPassword) {
563                $this->passwd = Tools::hash($plainTextPassword);
564            }
565        } else {
566            $this->passwd = Tools::hash($plainTextPassword);
567        }
568
569        return true;
570    }
571
572    /**
573     * Check employee informations saved into cookie and return employee validity
574     *
575     * @return bool employee validity
576     *
577     * @since   1.0.0
578     * @version 1.0.0 Initial version
579     * @throws PrestaShopException
580     */
581    public function isLoggedBack()
582    {
583        if (!Cache::isStored('isLoggedBack'.$this->id)) {
584            /* Employee is valid only if it can be load and if cookie password is the same as database one */
585            $result = (
586                $this->id && Validate::isUnsignedId($this->id) && Employee::checkPassword($this->id, Context::getContext()->cookie->passwd)
587                && (!isset(Context::getContext()->cookie->remote_addr) || Context::getContext()->cookie->remote_addr == ip2long(Tools::getRemoteAddr()) || !Configuration::get('PS_COOKIE_CHECKIP'))
588            );
589            Cache::store('isLoggedBack'.$this->id, $result);
590
591            return $result;
592        }
593
594        return Cache::retrieve('isLoggedBack'.$this->id);
595    }
596
597    /**
598     * Check if employee password is the right one
599     *
600     * @param int    $idEmployee
601     * @param string $hashedPassword Password
602     *
603     * @return bool result
604     *
605     * @since   1.0.0
606     * @version 1.0.0 Initial version
607     * @throws PrestaShopException
608     */
609    public static function checkPassword($idEmployee, $hashedPassword)
610    {
611        if (!Validate::isUnsignedId($idEmployee) || !Validate::isPasswd($hashedPassword, 8)) {
612            die(Tools::displayError());
613        }
614
615        $sql = new DbQuery();
616        $sql->select('`id_employee`');
617        $sql->from('employee');
618        $sql->where('`id_employee` = '.(int) $idEmployee);
619        $sql->where('`active` = 1');
620        $sql->where('`passwd` = \''.pSQL($hashedPassword).'\'');
621
622        return (bool) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($sql);
623    }
624
625    /**
626     * Logout
627     *
628     * @since   1.0.0
629     * @version 1.0.0 Initial version
630     */
631    public function logout()
632    {
633        if (isset(Context::getContext()->cookie)) {
634            Context::getContext()->cookie->logout();
635            Context::getContext()->cookie->write();
636        }
637        $this->id = null;
638    }
639
640    /**
641     * @return array|false|mysqli_result|null|PDOStatement|resource
642     *
643     * @throws PrestaShopDatabaseException
644     * @throws PrestaShopException
645     * @since   1.0.0
646     * @version 1.0.0 Initial version
647     */
648    public function favoriteModulesList()
649    {
650        return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS(
651            (new DbQuery())
652                ->select('module')
653                ->from('module_preference')
654                ->where('`id_employee` = '.(int) $this->id)
655                ->where('`favorite` = 1')
656                ->where('`interest` = 1 OR `interest` IS NULL')
657        );
658    }
659
660    /**
661     * Check if the employee is associated to a specific shop
662     *
663     * @param int $idShop
664     *
665     * @return bool
666     *
667     * @since   1.0.0
668     * @version 1.0.0 Initial version
669     */
670    public function hasAuthOnShop($idShop)
671    {
672        return $this->isSuperAdmin() || in_array($idShop, $this->associated_shops);
673    }
674
675    /**
676     * Check if the employee is associated to a specific shop group
677     *
678     * @param int $idShopGroup
679     *
680     * @return bool
681     *
682     * @since   1.0.0
683     * @version 1.0.0 Initial version
684     */
685    public function hasAuthOnShopGroup($idShopGroup)
686    {
687        if ($this->isSuperAdmin()) {
688            return true;
689        }
690
691        foreach ($this->associated_shops as $idShop) {
692            if ($idShopGroup == Shop::getGroupFromShop($idShop, true)) {
693                return true;
694            }
695        }
696
697        return false;
698    }
699
700    /**
701     * Get default id_shop with auth for current employee
702     *
703     * @return int
704     *
705     * @since   1.0.0
706     * @version 1.0.0 Initial version
707     * @throws PrestaShopException
708     */
709    public function getDefaultShopID()
710    {
711        if ($this->isSuperAdmin() || in_array(Configuration::get('PS_SHOP_DEFAULT'), $this->associated_shops)) {
712            return Configuration::get('PS_SHOP_DEFAULT');
713        }
714
715        return $this->associated_shops[0];
716    }
717
718    /**
719     * @return string
720     *
721     * @since   1.0.0
722     * @version 1.0.0 Initial version
723     */
724    public function getImage()
725    {
726        return 'https://www.gravatar.com/avatar/'.md5(mb_strtolower(trim($this->email))).'?s=200&d=mm';
727    }
728
729    /**
730     * @param string $element
731     *
732     * @return int
733     *
734     * @since   1.0.0
735     * @version 1.0.0 Initial version
736     * @throws PrestaShopException
737     */
738    public function getLastElementsForNotify($element)
739    {
740        $element = bqSQL($element);
741        $max = Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue(
742            (new DbQuery())
743                ->select('MAX(`id_'.bqSQL($element).'`) as `id_'.bqSQL($element).'`')
744                ->from(bqSQL($element).($element == 'order' ? 's' : ''))
745        );
746
747        // if no rows in table, set max to 0
748        if ((int) $max < 1) {
749            $max = 0;
750        }
751
752        return (int) $max;
753    }
754}
755