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