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\Adapter\CoreException; 27use PrestaShop\PrestaShop\Adapter\ServiceLocator; 28 29/*** 30 * Class CustomerCore 31 */ 32class CustomerCore extends ObjectModel 33{ 34 /** @var int Customer ID */ 35 public $id; 36 37 /** @var int Shop ID */ 38 public $id_shop; 39 40 /** @var int ShopGroup ID */ 41 public $id_shop_group; 42 43 /** @var string Secure key */ 44 public $secure_key; 45 46 /** @var string protected note */ 47 public $note; 48 49 /** @var int Gender ID */ 50 public $id_gender = 0; 51 52 /** @var int Default group ID */ 53 public $id_default_group; 54 55 /** @var int Current language used by the customer */ 56 public $id_lang; 57 58 /** @var string Lastname */ 59 public $lastname; 60 61 /** @var string Firstname */ 62 public $firstname; 63 64 /** @var string Birthday (yyyy-mm-dd) */ 65 public $birthday = null; 66 67 /** @var string e-mail */ 68 public $email; 69 70 /** @var bool Newsletter subscription */ 71 public $newsletter; 72 73 /** @var string Newsletter ip registration */ 74 public $ip_registration_newsletter; 75 76 /** @var string Newsletter registration date */ 77 public $newsletter_date_add; 78 79 /** @var bool Opt-in subscription */ 80 public $optin; 81 82 /** @var string WebSite * */ 83 public $website; 84 85 /** @var string Company */ 86 public $company; 87 88 /** @var string SIRET */ 89 public $siret; 90 91 /** @var string APE */ 92 public $ape; 93 94 /** @var float Outstanding allow amount (B2B opt) */ 95 public $outstanding_allow_amount = 0; 96 97 /** @var int Show public prices (B2B opt) */ 98 public $show_public_prices = 0; 99 100 /** @var int Risk ID (B2B opt) */ 101 public $id_risk; 102 103 /** @var int Max payment day */ 104 public $max_payment_days = 0; 105 106 /** @var string Password */ 107 public $passwd; 108 109 /** @var string Datetime Password */ 110 public $last_passwd_gen; 111 112 /** @var bool Status */ 113 public $active = true; 114 115 /** @var bool Status */ 116 public $is_guest = 0; 117 118 /** @var bool True if carrier has been deleted (staying in database as deleted) */ 119 public $deleted = 0; 120 121 /** @var string Object creation date */ 122 public $date_add; 123 124 /** @var string Object last modification date */ 125 public $date_upd; 126 127 public $years; 128 public $days; 129 public $months; 130 131 /** @var int customer id_country as determined by geolocation */ 132 public $geoloc_id_country; 133 /** @var int customer id_state as determined by geolocation */ 134 public $geoloc_id_state; 135 /** @var string customer postcode as determined by geolocation */ 136 public $geoloc_postcode; 137 138 /** @var bool is the customer logged in */ 139 public $logged = 0; 140 141 /** @var int id_guest meaning the guest table, not the guest customer */ 142 public $id_guest; 143 144 public $groupBox; 145 146 /** @var string Unique token for forgot password feature */ 147 public $reset_password_token; 148 149 /** @var string token validity date for forgot password feature */ 150 public $reset_password_validity; 151 152 protected $webserviceParameters = [ 153 'objectMethods' => [ 154 'add' => 'addWs', 155 'update' => 'updateWs', 156 ], 157 'fields' => [ 158 'id_default_group' => ['xlink_resource' => 'groups'], 159 'id_lang' => ['xlink_resource' => 'languages'], 160 'newsletter_date_add' => [], 161 'ip_registration_newsletter' => [], 162 'last_passwd_gen' => ['setter' => null], 163 'secure_key' => ['setter' => null], 164 'deleted' => [], 165 'passwd' => ['setter' => 'setWsPasswd'], 166 ], 167 'associations' => [ 168 'groups' => ['resource' => 'group'], 169 ], 170 ]; 171 172 /** 173 * @see ObjectModel::$definition 174 */ 175 public static $definition = [ 176 'table' => 'customer', 177 'primary' => 'id_customer', 178 'fields' => [ 179 'secure_key' => ['type' => self::TYPE_STRING, 'validate' => 'isMd5', 'copy_post' => false], 180 'lastname' => ['type' => self::TYPE_STRING, 'validate' => 'isCustomerName', 'required' => true, 'size' => 255], 181 'firstname' => ['type' => self::TYPE_STRING, 'validate' => 'isCustomerName', 'required' => true, 'size' => 255], 182 'email' => ['type' => self::TYPE_STRING, 'validate' => 'isEmail', 'required' => true, 'size' => 255], 183 'passwd' => ['type' => self::TYPE_STRING, 'validate' => 'isPasswd', 'required' => true, 'size' => 255], 184 'last_passwd_gen' => ['type' => self::TYPE_STRING, 'copy_post' => false], 185 'id_gender' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'], 186 'birthday' => ['type' => self::TYPE_DATE, 'validate' => 'isBirthDate'], 187 'newsletter' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], 188 'newsletter_date_add' => ['type' => self::TYPE_DATE, 'copy_post' => false], 189 'ip_registration_newsletter' => ['type' => self::TYPE_STRING, 'copy_post' => false], 190 'optin' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], 191 'website' => ['type' => self::TYPE_STRING, 'validate' => 'isUrl'], 192 'company' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName'], 193 'siret' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName'], 194 'ape' => ['type' => self::TYPE_STRING, 'validate' => 'isApe'], 195 'outstanding_allow_amount' => ['type' => self::TYPE_FLOAT, 'validate' => 'isFloat', 'copy_post' => false], 196 'show_public_prices' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool', 'copy_post' => false], 197 'id_risk' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt', 'copy_post' => false], 198 'max_payment_days' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt', 'copy_post' => false], 199 'active' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool', 'copy_post' => false], 200 'deleted' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool', 'copy_post' => false], 201 'note' => ['type' => self::TYPE_HTML, 'size' => 65000, 'copy_post' => false], 202 'is_guest' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool', 'copy_post' => false], 203 'id_shop' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'copy_post' => false], 204 'id_shop_group' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'copy_post' => false], 205 'id_default_group' => ['type' => self::TYPE_INT, 'copy_post' => false], 206 'id_lang' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'copy_post' => false], 207 'date_add' => ['type' => self::TYPE_DATE, 'validate' => 'isDate', 'copy_post' => false], 208 'date_upd' => ['type' => self::TYPE_DATE, 'validate' => 'isDate', 'copy_post' => false], 209 'reset_password_token' => ['type' => self::TYPE_STRING, 'validate' => 'isSha1', 'size' => 40, 'copy_post' => false], 210 'reset_password_validity' => ['type' => self::TYPE_DATE, 'validate' => 'isDateOrNull', 'copy_post' => false], 211 ], 212 ]; 213 214 protected static $_defaultGroupId = []; 215 protected static $_customerHasAddress = []; 216 protected static $_customer_groups = []; 217 218 /** 219 * CustomerCore constructor. 220 * 221 * @param int|null $id 222 */ 223 public function __construct($id = null) 224 { 225 // It sets default value for customer group even when customer does not exist 226 $this->id_default_group = (int) Configuration::get('PS_CUSTOMER_GROUP'); 227 parent::__construct($id); 228 } 229 230 /** 231 * Adds current Customer as a new Object to the database. 232 * 233 * @param bool $autoDate Automatically set `date_upd` and `date_add` columns 234 * @param bool $nullValues Whether we want to use NULL values instead of empty quotes values 235 * 236 * @return bool Indicates whether the Customer has been successfully added 237 * 238 * @throws PrestaShopDatabaseException 239 * @throws PrestaShopException 240 */ 241 public function add($autoDate = true, $nullValues = true) 242 { 243 $this->id_shop = ($this->id_shop) ? $this->id_shop : Context::getContext()->shop->id; 244 $this->id_shop_group = ($this->id_shop_group) ? $this->id_shop_group : Context::getContext()->shop->id_shop_group; 245 $this->id_lang = ($this->id_lang) ? $this->id_lang : Context::getContext()->language->id; 246 $this->birthday = (empty($this->years) ? $this->birthday : (int) $this->years . '-' . (int) $this->months . '-' . (int) $this->days); 247 $this->secure_key = md5(uniqid(mt_rand(0, mt_getrandmax()), true)); 248 $this->last_passwd_gen = date('Y-m-d H:i:s', strtotime('-' . Configuration::get('PS_PASSWD_TIME_FRONT') . 'minutes')); 249 250 if ($this->newsletter && !Validate::isDate($this->newsletter_date_add)) { 251 $this->newsletter_date_add = date('Y-m-d H:i:s'); 252 } 253 254 if ($this->id_default_group == Configuration::get('PS_CUSTOMER_GROUP')) { 255 if ($this->is_guest) { 256 $this->id_default_group = (int) Configuration::get('PS_GUEST_GROUP'); 257 } else { 258 $this->id_default_group = (int) Configuration::get('PS_CUSTOMER_GROUP'); 259 } 260 } 261 262 /* Can't create a guest customer, if this feature is disabled */ 263 if ($this->is_guest && !Configuration::get('PS_GUEST_CHECKOUT_ENABLED')) { 264 return false; 265 } 266 $success = parent::add($autoDate, $nullValues); 267 $this->updateGroup($this->groupBox); 268 269 return $success; 270 } 271 272 /** 273 * Adds current Customer as a new Object to the database. 274 * 275 * @param bool $autoDate Automatically set `date_upd` and `date_add` columns 276 * @param bool $nullValues Whether we want to use NULL values instead of empty quotes values 277 * 278 * @return bool Indicates whether the Customer has been successfully added 279 * 280 * @throws PrestaShopDatabaseException 281 * @throws PrestaShopException 282 */ 283 public function addWs($autodate = true, $null_values = false) 284 { 285 if (Customer::customerExists($this->email)) { 286 WebserviceRequest::getInstance()->setError( 287 500, 288 $this->trans( 289 'The email is already used, please choose another one', 290 [], 291 'Admin.Notifications.Error' 292 ), 293 140 294 ); 295 296 return false; 297 } 298 299 return $this->add($autodate, $null_values); 300 } 301 302 /** 303 * Updates the current Customer in the database. 304 * 305 * @param bool $nullValues Whether we want to use NULL values instead of empty quotes values 306 * 307 * @return bool Indicates whether the Customer has been successfully updated 308 * 309 * @throws PrestaShopDatabaseException 310 * @throws PrestaShopException 311 */ 312 public function update($nullValues = false) 313 { 314 $this->birthday = (empty($this->years) ? $this->birthday : (int) $this->years . '-' . (int) $this->months . '-' . (int) $this->days); 315 316 if ($this->newsletter && !Validate::isDate($this->newsletter_date_add)) { 317 $this->newsletter_date_add = date('Y-m-d H:i:s'); 318 } 319 if (isset(Context::getContext()->controller) && Context::getContext()->controller->controller_type == 'admin') { 320 $this->updateGroup($this->groupBox); 321 } 322 323 if ($this->deleted) { 324 $addresses = $this->getAddresses((int) Configuration::get('PS_LANG_DEFAULT')); 325 foreach ($addresses as $address) { 326 $obj = new Address((int) $address['id_address']); 327 $obj->deleted = true; 328 $obj->save(); 329 } 330 } 331 332 try { 333 return parent::update(true); 334 } catch (\PrestaShopException $exception) { 335 $message = $exception->getMessage(); 336 error_log($message); 337 338 return false; 339 } 340 } 341 342 /** 343 * Updates the current Customer in the database. 344 * 345 * @param bool $nullValues Whether we want to use NULL values instead of empty quotes values 346 * 347 * @return bool Indicates whether the Customer has been successfully updated 348 * 349 * @throws PrestaShopDatabaseException 350 * @throws PrestaShopException 351 */ 352 public function updateWs($nullValues = false) 353 { 354 if (Customer::customerExists($this->email) 355 && Customer::customerExists($this->email, true) !== (int) $this->id 356 ) { 357 WebserviceRequest::getInstance()->setError( 358 500, 359 $this->trans( 360 'The email is already used, please choose another one', 361 [], 362 'Admin.Notifications.Error' 363 ), 364 141 365 ); 366 367 return false; 368 } 369 370 return $this->update($nullValues = false); 371 } 372 373 /** 374 * Deletes current Customer from the database. 375 * 376 * @return bool True if delete was successful 377 * 378 * @throws PrestaShopException 379 */ 380 public function delete() 381 { 382 if (!count(Order::getCustomerOrders((int) $this->id))) { 383 $addresses = $this->getAddresses((int) Configuration::get('PS_LANG_DEFAULT')); 384 foreach ($addresses as $address) { 385 $obj = new Address((int) $address['id_address']); 386 $obj->delete(); 387 } 388 } 389 Db::getInstance()->execute('DELETE FROM `' . _DB_PREFIX_ . 'customer_group` WHERE `id_customer` = ' . (int) $this->id); 390 Db::getInstance()->execute('DELETE FROM ' . _DB_PREFIX_ . 'message WHERE id_customer=' . (int) $this->id); 391 Db::getInstance()->execute('DELETE FROM ' . _DB_PREFIX_ . 'specific_price WHERE id_customer=' . (int) $this->id); 392 393 $carts = Db::getInstance()->executeS('SELECT id_cart FROM ' . _DB_PREFIX_ . 'cart WHERE id_customer=' . (int) $this->id); 394 if ($carts) { 395 foreach ($carts as $cart) { 396 Db::getInstance()->execute('DELETE FROM ' . _DB_PREFIX_ . 'cart WHERE id_cart=' . (int) $cart['id_cart']); 397 Db::getInstance()->execute('DELETE FROM ' . _DB_PREFIX_ . 'cart_product WHERE id_cart=' . (int) $cart['id_cart']); 398 } 399 } 400 401 $cts = Db::getInstance()->executeS('SELECT id_customer_thread FROM ' . _DB_PREFIX_ . 'customer_thread WHERE id_customer=' . (int) $this->id); 402 if ($cts) { 403 foreach ($cts as $ct) { 404 Db::getInstance()->execute('DELETE FROM ' . _DB_PREFIX_ . 'customer_thread WHERE id_customer_thread=' . (int) $ct['id_customer_thread']); 405 Db::getInstance()->execute('DELETE FROM ' . _DB_PREFIX_ . 'customer_message WHERE id_customer_thread=' . (int) $ct['id_customer_thread']); 406 } 407 } 408 409 CartRule::deleteByIdCustomer((int) $this->id); 410 411 return parent::delete(); 412 } 413 414 /** 415 * Return customers list. 416 * 417 * @param bool|null $onlyActive Returns only active customers when `true` 418 * 419 * @return array Customers 420 */ 421 public static function getCustomers($onlyActive = null) 422 { 423 return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS( 424 ' 425 SELECT `id_customer`, `email`, `firstname`, `lastname` 426 FROM `' . _DB_PREFIX_ . 'customer` 427 WHERE 1 ' . Shop::addSqlRestriction(Shop::SHARE_CUSTOMER) . 428 ($onlyActive ? ' AND `active` = 1' : '') . ' 429 ORDER BY `id_customer` ASC' 430 ); 431 } 432 433 /** 434 * Return customer instance from its e-mail (optionally check password). 435 * 436 * @param string $email e-mail 437 * @param string $plaintextPassword Password is also checked if specified 438 * @param bool $ignoreGuest 439 * 440 * @return bool|Customer|CustomerCore Customer instance 441 * 442 * @throws \InvalidArgumentException if given input is not valid 443 */ 444 public function getByEmail($email, $plaintextPassword = null, $ignoreGuest = true) 445 { 446 if (!Validate::isEmail($email)) { 447 throw new \InvalidArgumentException(sprintf( 448 'Cannot get customer by email as %s is not a valid email', 449 $email 450 )); 451 } 452 if (($plaintextPassword && !Validate::isPlaintextPassword($plaintextPassword))) { 453 throw new \InvalidArgumentException( 454 'Cannot get customer by email as given password is not a valid password' 455 ); 456 } 457 458 $shopGroup = Shop::getGroupFromShop(Shop::getContextShopID(), false); 459 460 $sql = new DbQuery(); 461 $sql->select('c.`passwd`'); 462 $sql->from('customer', 'c'); 463 $sql->where('c.`email` = \'' . pSQL($email) . '\''); 464 if (Shop::getContext() == Shop::CONTEXT_SHOP && $shopGroup['share_customer']) { 465 $sql->where('c.`id_shop_group` = ' . (int) Shop::getContextShopGroupID()); 466 } else { 467 $sql->where('c.`id_shop` IN (' . implode(', ', Shop::getContextListShopID(Shop::SHARE_CUSTOMER)) . ')'); 468 } 469 470 if ($ignoreGuest) { 471 $sql->where('c.`is_guest` = 0'); 472 } 473 $sql->where('c.`deleted` = 0'); 474 475 $passwordHash = Db::getInstance()->getValue($sql); 476 477 try { 478 /** @var \PrestaShop\PrestaShop\Core\Crypto\Hashing $crypto */ 479 $crypto = ServiceLocator::get('\\PrestaShop\\PrestaShop\\Core\\Crypto\\Hashing'); 480 } catch (CoreException $e) { 481 return false; 482 } 483 484 $shouldCheckPassword = null !== $plaintextPassword; 485 if ($shouldCheckPassword && !$crypto->checkHash($plaintextPassword, $passwordHash)) { 486 return false; 487 } 488 489 $sql = new DbQuery(); 490 $sql->select('c.*'); 491 $sql->from('customer', 'c'); 492 $sql->where('c.`email` = \'' . pSQL($email) . '\''); 493 if (Shop::getContext() == Shop::CONTEXT_SHOP && $shopGroup['share_customer']) { 494 $sql->where('c.`id_shop_group` = ' . (int) Shop::getContextShopGroupID()); 495 } else { 496 $sql->where('c.`id_shop` IN (' . implode(', ', Shop::getContextListShopID(Shop::SHARE_CUSTOMER)) . ')'); 497 } 498 if ($ignoreGuest) { 499 $sql->where('c.`is_guest` = 0'); 500 } 501 $sql->where('c.`deleted` = 0'); 502 503 $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow($sql); 504 505 if (!$result) { 506 return false; 507 } 508 509 $this->id = $result['id_customer']; 510 foreach ($result as $key => $value) { 511 if (property_exists($this, $key)) { 512 $this->{$key} = $value; 513 } 514 } 515 516 if ($shouldCheckPassword && !$crypto->isFirstHash($plaintextPassword, $passwordHash)) { 517 $this->passwd = $crypto->hash($plaintextPassword); 518 $this->update(); 519 } 520 521 return $this; 522 } 523 524 /** 525 * Retrieve customers by email address. 526 * 527 * @param string $email 528 * 529 * @return array 530 */ 531 public static function getCustomersByEmail($email) 532 { 533 $sql = 'SELECT * 534 FROM `' . _DB_PREFIX_ . 'customer` 535 WHERE `email` = \'' . pSQL($email) . '\' 536 ' . Shop::addSqlRestriction(Shop::SHARE_CUSTOMER); 537 538 return Db::getInstance()->executeS($sql); 539 } 540 541 /** 542 * Check id the customer is active or not. 543 * 544 * @param int $idCustomer 545 * 546 * @return bool Customer validity 547 */ 548 public static function isBanned($idCustomer) 549 { 550 if (!Validate::isUnsignedId($idCustomer)) { 551 return true; 552 } 553 $cacheId = 'Customer::isBanned_' . (int) $idCustomer; 554 if (!Cache::isStored($cacheId)) { 555 $result = (bool) !Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow(' 556 SELECT `id_customer` 557 FROM `' . _DB_PREFIX_ . 'customer` 558 WHERE `id_customer` = \'' . (int) $idCustomer . '\' 559 AND active = 1 560 AND `deleted` = 0'); 561 Cache::store($cacheId, $result); 562 563 return $result; 564 } 565 566 return Cache::retrieve($cacheId); 567 } 568 569 /** 570 * Check if e-mail is already registered in database. 571 * 572 * @param string $email e-mail 573 * @param bool $returnId 574 * @param bool $ignoreGuest To exclude guest customer 575 * 576 * @return bool|int Customer ID if found 577 * `false` otherwise 578 */ 579 public static function customerExists($email, $returnId = false, $ignoreGuest = true) 580 { 581 if (!Validate::isEmail($email)) { 582 return false; 583 } 584 585 $result = Db::getInstance()->getValue(' 586 SELECT `id_customer` 587 FROM `' . _DB_PREFIX_ . 'customer` 588 WHERE `email` = \'' . pSQL($email) . '\' 589 ' . Shop::addSqlRestriction(Shop::SHARE_CUSTOMER) . ' 590 ' . ($ignoreGuest ? ' AND `is_guest` = 0' : ''), false); 591 592 return $returnId ? (int) $result : (bool) $result; 593 } 594 595 /** 596 * Check if an address is owned by a customer. 597 * 598 * @param int $idCustomer Customer ID 599 * @param int $idAddress Address ID 600 * 601 * @return bool result 602 */ 603 public static function customerHasAddress($idCustomer, $idAddress) 604 { 605 $key = (int) $idCustomer . '-' . (int) $idAddress; 606 if (!array_key_exists($key, self::$_customerHasAddress)) { 607 self::$_customerHasAddress[$key] = (bool) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue(' 608 SELECT `id_address` 609 FROM `' . _DB_PREFIX_ . 'address` 610 WHERE `id_customer` = ' . (int) $idCustomer . ' 611 AND `id_address` = ' . (int) $idAddress . ' 612 AND `deleted` = 0'); 613 } 614 615 return self::$_customerHasAddress[$key]; 616 } 617 618 /** 619 * Reset Address cache. 620 * 621 * @param int $idCustomer Customer ID 622 * @param int $idAddress Address ID 623 */ 624 public static function resetAddressCache($idCustomer = null, $idAddress = null) 625 { 626 if ($idCustomer === null || $idAddress === null) { 627 self::$_customerHasAddress = []; 628 self::$_customer_groups = []; 629 self::$_defaultGroupId = []; 630 } 631 $key = (int) $idCustomer . '-' . (int) $idAddress; 632 if (array_key_exists($key, self::$_customerHasAddress)) { 633 unset(self::$_customerHasAddress[$key]); 634 } 635 } 636 637 /** 638 * Return customer addresses. 639 * 640 * @param int $idLang Language ID 641 * 642 * @return array Addresses 643 */ 644 public function getAddresses($idLang) 645 { 646 $group = Context::getContext()->shop->getGroup(); 647 $shareOrder = isset($group->share_order) ? (bool) $group->share_order : false; 648 $cacheId = 'Customer::getAddresses' 649 . '-' . (int) $this->id 650 . '-' . (int) $idLang 651 . '-' . ($shareOrder ? 1 : 0); 652 if (!Cache::isStored($cacheId)) { 653 $sql = 'SELECT DISTINCT a.*, cl.`name` AS country, s.name AS state, s.iso_code AS state_iso 654 FROM `' . _DB_PREFIX_ . 'address` a 655 LEFT JOIN `' . _DB_PREFIX_ . 'country` c ON (a.`id_country` = c.`id_country`) 656 LEFT JOIN `' . _DB_PREFIX_ . 'country_lang` cl ON (c.`id_country` = cl.`id_country`) 657 LEFT JOIN `' . _DB_PREFIX_ . 'state` s ON (s.`id_state` = a.`id_state`) 658 ' . ($shareOrder ? '' : Shop::addSqlAssociation('country', 'c')) . ' 659 WHERE `id_lang` = ' . (int) $idLang . ' AND `id_customer` = ' . (int) $this->id . ' AND a.`deleted` = 0'; 660 661 $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql); 662 Cache::store($cacheId, $result); 663 664 return $result; 665 } 666 667 return Cache::retrieve($cacheId); 668 } 669 670 /** 671 * Get simplified Addresses arrays. 672 * 673 * @param int|null $idLang Language ID 674 * 675 * @return array 676 */ 677 public function getSimpleAddresses($idLang = null) 678 { 679 if (!$this->id) { 680 return []; 681 } 682 683 if (null === $idLang) { 684 $idLang = Context::getContext()->language->id; 685 } 686 687 $sql = $this->getSimpleAddressSql(null, $idLang); 688 $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql); 689 $addresses = []; 690 foreach ($result as $addr) { 691 $addresses[$addr['id']] = $addr; 692 } 693 694 return $addresses; 695 } 696 697 /** 698 * Get Address as array. 699 * 700 * @param int $idAddress Address ID 701 * @param int|null $idLang Language ID 702 * 703 * @return array|false|mysqli_result|PDOStatement|resource|null 704 */ 705 public function getSimpleAddress($idAddress, $idLang = null) 706 { 707 if (!$this->id || !(int) $idAddress || !$idAddress) { 708 return [ 709 'id' => '', 710 'alias' => '', 711 'firstname' => '', 712 'lastname' => '', 713 'company' => '', 714 'address1' => '', 715 'address2' => '', 716 'postcode' => '', 717 'city' => '', 718 'id_state' => '', 719 'state' => '', 720 'state_iso' => '', 721 'id_country' => '', 722 'country' => '', 723 'country_iso' => '', 724 'other' => '', 725 'phone' => '', 726 'phone_mobile' => '', 727 'vat_number' => '', 728 'dni' => '', 729 ]; 730 } 731 732 $sql = $this->getSimpleAddressSql($idAddress, $idLang); 733 $res = Db::getInstance()->executeS($sql); 734 if (count($res) === 1) { 735 return $res[0]; 736 } else { 737 return $res; 738 } 739 } 740 741 /** 742 * Get SQL query to retrieve Address in an array. 743 * 744 * @param int|null $idAddress Address ID 745 * @param int|null $idLang Language ID 746 * 747 * @return string 748 */ 749 public function getSimpleAddressSql($idAddress = null, $idLang = null) 750 { 751 if (null === $idLang) { 752 $idLang = Context::getContext()->language->id; 753 } 754 $shareOrder = (bool) Context::getContext()->shop->getGroup()->share_order; 755 756 $sql = 'SELECT DISTINCT 757 a.`id_address` AS `id`, 758 a.`alias`, 759 a.`firstname`, 760 a.`lastname`, 761 a.`company`, 762 a.`address1`, 763 a.`address2`, 764 a.`postcode`, 765 a.`city`, 766 a.`id_state`, 767 s.name AS state, 768 s.`iso_code` AS state_iso, 769 a.`id_country`, 770 cl.`name` AS country, 771 co.`iso_code` AS country_iso, 772 a.`other`, 773 a.`phone`, 774 a.`phone_mobile`, 775 a.`vat_number`, 776 a.`dni` 777 FROM `' . _DB_PREFIX_ . 'address` a 778 LEFT JOIN `' . _DB_PREFIX_ . 'country` co ON (a.`id_country` = co.`id_country`) 779 LEFT JOIN `' . _DB_PREFIX_ . 'country_lang` cl ON (co.`id_country` = cl.`id_country`) 780 LEFT JOIN `' . _DB_PREFIX_ . 'state` s ON (s.`id_state` = a.`id_state`) 781 ' . ($shareOrder ? '' : Shop::addSqlAssociation('country', 'co')) . ' 782 WHERE 783 `id_lang` = ' . (int) $idLang . ' 784 AND `id_customer` = ' . (int) $this->id . ' 785 AND a.`deleted` = 0 786 AND a.`active` = 1'; 787 788 if (null !== $idAddress) { 789 $sql .= ' AND a.`id_address` = ' . (int) $idAddress; 790 } 791 792 return $sql; 793 } 794 795 /** 796 * Count the number of addresses for a customer. 797 * 798 * @param int $idCustomer Customer ID 799 * 800 * @return int Number of addresses 801 */ 802 public static function getAddressesTotalById($idCustomer) 803 { 804 return Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue( 805 ' 806 SELECT COUNT(`id_address`) 807 FROM `' . _DB_PREFIX_ . 'address` 808 WHERE `id_customer` = ' . (int) $idCustomer . ' 809 AND `deleted` = 0' 810 ); 811 } 812 813 /** 814 * Check if customer password is the right one. 815 * 816 * @param int $idCustomer Customer ID 817 * @param string $passwordHash Hashed password 818 * 819 * @return bool result 820 */ 821 public static function checkPassword($idCustomer, $passwordHash) 822 { 823 if (!Validate::isUnsignedId($idCustomer)) { 824 die(Tools::displayError()); 825 } 826 827 // Check that customers password hasn't changed since last login 828 $context = Context::getContext(); 829 if ($passwordHash != $context->cookie->__get('passwd')) { 830 return false; 831 } 832 833 $cacheId = 'Customer::checkPassword' . (int) $idCustomer . '-' . $passwordHash; 834 if (!Cache::isStored($cacheId)) { 835 $sql = new DbQuery(); 836 $sql->select('c.`id_customer`'); 837 $sql->from('customer', 'c'); 838 $sql->where('c.`id_customer` = ' . (int) $idCustomer); 839 $sql->where('c.`passwd` = \'' . pSQL($passwordHash) . '\''); 840 841 $result = (bool) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($sql); 842 843 Cache::store($cacheId, $result); 844 845 return $result; 846 } 847 848 return Cache::retrieve($cacheId); 849 } 850 851 /** 852 * Light back office search for customers. 853 * 854 * @param string $query Searched string 855 * @param int|null $limit Limit query results 856 * 857 * @return array|false|mysqli_result|PDOStatement|resource|null Corresponding customers 858 * 859 * @throws PrestaShopDatabaseException 860 */ 861 public static function searchByName($query, $limit = null) 862 { 863 $sql = 'SELECT * 864 FROM `' . _DB_PREFIX_ . 'customer` 865 WHERE 1'; 866 $search_items = explode(' ', $query); 867 $research_fields = ['id_customer', 'firstname', 'lastname', 'email']; 868 if (Configuration::get('PS_B2B_ENABLE')) { 869 $research_fields[] = 'company'; 870 } 871 872 $items = []; 873 foreach ($research_fields as $field) { 874 foreach ($search_items as $item) { 875 $items[$item][] = $field . ' LIKE \'%' . pSQL($item) . '%\' '; 876 } 877 } 878 879 foreach ($items as $likes) { 880 $sql .= ' AND (' . implode(' OR ', $likes) . ') '; 881 } 882 883 $sql .= Shop::addSqlRestriction(Shop::SHARE_CUSTOMER); 884 885 if ($limit) { 886 $sql .= ' LIMIT 0, ' . (int) $limit; 887 } 888 889 return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql); 890 } 891 892 /** 893 * Search for customers by ip address. 894 * 895 * @param string $ip Searched string 896 * 897 * @return array|false|mysqli_result|PDOStatement|resource|null 898 */ 899 public static function searchByIp($ip) 900 { 901 return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS(' 902 SELECT DISTINCT c.* 903 FROM `' . _DB_PREFIX_ . 'customer` c 904 LEFT JOIN `' . _DB_PREFIX_ . 'guest` g ON g.id_customer = c.id_customer 905 LEFT JOIN `' . _DB_PREFIX_ . 'connections` co ON g.id_guest = co.id_guest 906 WHERE co.`ip_address` = \'' . (int) ip2long(trim($ip)) . '\''); 907 } 908 909 /** 910 * Return several useful statistics about customer. 911 * 912 * @return array Stats 913 */ 914 public function getStats() 915 { 916 $result = Db::getInstance()->getRow(' 917 SELECT COUNT(`id_order`) AS nb_orders, SUM(`total_paid` / o.`conversion_rate`) AS total_orders 918 FROM `' . _DB_PREFIX_ . 'orders` o 919 WHERE o.`id_customer` = ' . (int) $this->id . ' 920 AND o.valid = 1'); 921 922 $result2 = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow(' 923 SELECT c.`date_add` AS last_visit 924 FROM `' . _DB_PREFIX_ . 'connections` c 925 LEFT JOIN `' . _DB_PREFIX_ . 'guest` g USING (id_guest) 926 WHERE g.`id_customer` = ' . (int) $this->id . ' ORDER BY c.`date_add` DESC '); 927 928 $result3 = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow(' 929 SELECT (YEAR(CURRENT_DATE)-YEAR(c.`birthday`)) - (RIGHT(CURRENT_DATE, 5)<RIGHT(c.`birthday`, 5)) AS age 930 FROM `' . _DB_PREFIX_ . 'customer` c 931 WHERE c.`id_customer` = ' . (int) $this->id); 932 933 $result['last_visit'] = $result2['last_visit'] ?? null; 934 $result['age'] = (isset($result3['age']) && $result3['age'] != date('Y') ? $result3['age'] : '--'); 935 936 return $result; 937 } 938 939 /** 940 * Get last 10 emails sent to the Customer. 941 * 942 * @return array|false|mysqli_result|PDOStatement|resource|null 943 */ 944 public function getLastEmails() 945 { 946 if (!$this->id) { 947 return []; 948 } 949 950 return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS(' 951 SELECT m.*, l.name as language 952 FROM `' . _DB_PREFIX_ . 'mail` m 953 LEFT JOIN `' . _DB_PREFIX_ . 'lang` l ON m.id_lang = l.id_lang 954 WHERE `recipient` = "' . pSQL($this->email) . '" 955 ORDER BY m.date_add DESC 956 LIMIT 10'); 957 } 958 959 /** 960 * Get last 10 Connections of the Customer. 961 * 962 * @return array|false|mysqli_result|PDOStatement|resource|null 963 */ 964 public function getLastConnections() 965 { 966 if (!$this->id) { 967 return []; 968 } 969 970 return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS( 971 ' 972 SELECT c.id_connections, c.date_add, COUNT(cp.id_page) AS pages, TIMEDIFF(MAX(cp.time_end), c.date_add) as time, http_referer,INET_NTOA(ip_address) as ipaddress 973 FROM `' . _DB_PREFIX_ . 'guest` g 974 LEFT JOIN `' . _DB_PREFIX_ . 'connections` c ON c.id_guest = g.id_guest 975 LEFT JOIN `' . _DB_PREFIX_ . 'connections_page` cp ON c.id_connections = cp.id_connections 976 WHERE g.`id_customer` = ' . (int) $this->id . ' 977 GROUP BY c.`id_connections` 978 ORDER BY c.date_add DESC 979 LIMIT 10' 980 ); 981 } 982 983 /** 984 * Check if Customer ID exists. 985 * 986 * @param int $idCustomer Customer ID 987 * 988 * @return int|null Customer ID if found 989 */ 990 public static function customerIdExistsStatic($idCustomer) 991 { 992 $cacheId = 'Customer::customerIdExistsStatic' . (int) $idCustomer; 993 if (!Cache::isStored($cacheId)) { 994 $result = (int) Db::getInstance()->getValue(' 995 SELECT `id_customer` 996 FROM ' . _DB_PREFIX_ . 'customer c 997 WHERE c.`id_customer` = ' . (int) $idCustomer); 998 Cache::store($cacheId, $result); 999 1000 return $result; 1001 } 1002 1003 return Cache::retrieve($cacheId); 1004 } 1005 1006 /** 1007 * Update customer groups associated to the object. 1008 * 1009 * @param array $list groups 1010 */ 1011 public function updateGroup($list) 1012 { 1013 Hook::exec('actionCustomerBeforeUpdateGroup', ['id_customer' => $this->id, 'groups' => $list]); 1014 if ($list && !empty($list)) { 1015 $this->cleanGroups(); 1016 $this->addGroups($list); 1017 } else { 1018 $this->addGroups([$this->id_default_group]); 1019 } 1020 } 1021 1022 /** 1023 * Remove this Customer ID from Customer Groups. 1024 * 1025 * @return bool Indicates whether the Customer ID has been successfully removed 1026 * from the Customer Group Db table 1027 */ 1028 public function cleanGroups() 1029 { 1030 return Db::getInstance()->delete('customer_group', 'id_customer = ' . (int) $this->id); 1031 } 1032 1033 /** 1034 * Add the Customer to the given Customer Groups. 1035 * 1036 * @param array $groups Customer Group IDs 1037 */ 1038 public function addGroups($groups) 1039 { 1040 Hook::exec('actionCustomerAddGroups', ['id_customer' => $this->id, 'groups' => $groups]); 1041 foreach ($groups as $group) { 1042 $row = ['id_customer' => (int) $this->id, 'id_group' => (int) $group]; 1043 Db::getInstance()->insert('customer_group', $row, false, true, Db::INSERT_IGNORE); 1044 } 1045 } 1046 1047 /** 1048 * Get Groups that have the given Customer ID. 1049 * 1050 * @param int $idCustomer Customer ID 1051 * 1052 * @return array|mixed 1053 */ 1054 public static function getGroupsStatic($idCustomer) 1055 { 1056 if (!Group::isFeatureActive()) { 1057 return [Configuration::get('PS_CUSTOMER_GROUP')]; 1058 } 1059 1060 if ($idCustomer == 0) { 1061 self::$_customer_groups[$idCustomer] = [(int) Configuration::get('PS_UNIDENTIFIED_GROUP')]; 1062 } 1063 1064 if (!isset(self::$_customer_groups[$idCustomer])) { 1065 self::$_customer_groups[$idCustomer] = []; 1066 $result = Db::getInstance()->executeS(' 1067 SELECT cg.`id_group` 1068 FROM ' . _DB_PREFIX_ . 'customer_group cg 1069 WHERE cg.`id_customer` = ' . (int) $idCustomer); 1070 foreach ($result as $group) { 1071 self::$_customer_groups[$idCustomer][] = (int) $group['id_group']; 1072 } 1073 } 1074 1075 return self::$_customer_groups[$idCustomer]; 1076 } 1077 1078 public function getGroups() 1079 { 1080 return Customer::getGroupsStatic((int) $this->id); 1081 } 1082 1083 /** 1084 * Get Products bought by this Customer. 1085 * 1086 * @return array|false|mysqli_result|PDOStatement|resource|null 1087 */ 1088 public function getBoughtProducts() 1089 { 1090 return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS(' 1091 SELECT * FROM `' . _DB_PREFIX_ . 'orders` o 1092 LEFT JOIN `' . _DB_PREFIX_ . 'order_detail` od ON o.id_order = od.id_order 1093 WHERE o.valid = 1 AND o.`id_customer` = ' . (int) $this->id); 1094 } 1095 1096 /** 1097 * Get Default Customer Group ID. 1098 * 1099 * @param int $idCustomer Customer ID 1100 * 1101 * @return mixed|string|null 1102 */ 1103 public static function getDefaultGroupId($idCustomer) 1104 { 1105 if (!Group::isFeatureActive()) { 1106 static $psCustomerGroup = null; 1107 if ($psCustomerGroup === null) { 1108 $psCustomerGroup = Configuration::get('PS_CUSTOMER_GROUP'); 1109 } 1110 1111 return $psCustomerGroup; 1112 } 1113 1114 if (!isset(self::$_defaultGroupId[(int) $idCustomer])) { 1115 self::$_defaultGroupId[(int) $idCustomer] = Db::getInstance()->getValue( 1116 ' 1117 SELECT `id_default_group` 1118 FROM `' . _DB_PREFIX_ . 'customer` 1119 WHERE `id_customer` = ' . (int) $idCustomer 1120 ); 1121 } 1122 1123 return self::$_defaultGroupId[(int) $idCustomer]; 1124 } 1125 1126 /** 1127 * Get current Country. 1128 * 1129 * @param int $idCustomer 1130 * @param Cart|null $cart 1131 * 1132 * @return int Country ID 1133 */ 1134 public static function getCurrentCountry($idCustomer, Cart $cart = null) 1135 { 1136 if (!$cart) { 1137 $cart = Context::getContext()->cart; 1138 } 1139 if (!$cart || !$cart->{Configuration::get('PS_TAX_ADDRESS_TYPE')}) { 1140 $idAddress = (int) Db::getInstance()->getValue( 1141 ' 1142 SELECT `id_address` 1143 FROM `' . _DB_PREFIX_ . 'address` 1144 WHERE `id_customer` = ' . (int) $idCustomer . ' 1145 AND `deleted` = 0 ORDER BY `id_address`' 1146 ); 1147 } else { 1148 $idAddress = $cart->{Configuration::get('PS_TAX_ADDRESS_TYPE')}; 1149 } 1150 $ids = Address::getCountryAndState($idAddress); 1151 1152 return (int) ($ids['id_country'] ?? Configuration::get('PS_COUNTRY_DEFAULT')); 1153 } 1154 1155 /** 1156 * Is the current Customer a Guest? 1157 * 1158 * @return bool Indicates whether the Customer is a Guest 1159 */ 1160 public function isGuest() 1161 { 1162 return (bool) $this->is_guest; 1163 } 1164 1165 /** 1166 * Transform the Guest to a Customer. 1167 * 1168 * @param int $idLang Language ID 1169 * @param string|null $password Password 1170 * 1171 * @return bool 1172 */ 1173 public function transformToCustomer($idLang, $password = null) 1174 { 1175 if (!$this->isGuest()) { 1176 return false; 1177 } 1178 if (empty($password)) { 1179 $password = Tools::passwdGen(8, 'RANDOM'); 1180 } 1181 if (!Validate::isPasswd($password)) { 1182 return false; 1183 } 1184 1185 $language = new Language($idLang); 1186 if (!Validate::isLoadedObject($language)) { 1187 $language = Context::getContext()->language; 1188 } 1189 1190 /** @var \PrestaShop\PrestaShop\Core\Crypto\Hashing $crypto */ 1191 $crypto = ServiceLocator::get('\\PrestaShop\\PrestaShop\\Core\\Crypto\\Hashing'); 1192 $this->is_guest = 0; 1193 $this->passwd = $crypto->hash($password); 1194 $this->cleanGroups(); 1195 $this->addGroups([Configuration::get('PS_CUSTOMER_GROUP')]); 1196 $this->id_default_group = Configuration::get('PS_CUSTOMER_GROUP'); 1197 $this->stampResetPasswordToken(); 1198 if ($this->update()) { 1199 $vars = [ 1200 '{firstname}' => $this->firstname, 1201 '{lastname}' => $this->lastname, 1202 '{email}' => $this->email, 1203 '{url}' => Context::getContext()->link->getPageLink( 1204 'password', 1205 true, 1206 null, 1207 sprintf( 1208 'token=%s&id_customer=%s&reset_token=%s', 1209 $this->secure_key, 1210 (int) $this->id, 1211 $this->reset_password_token 1212 ) 1213 ), 1214 ]; 1215 Mail::Send( 1216 (int) $idLang, 1217 'guest_to_customer', 1218 Context::getContext()->getTranslator()->trans( 1219 'Your guest account has been transformed into a customer account', 1220 [], 1221 'Emails.Subject', 1222 $language->locale 1223 ), 1224 $vars, 1225 $this->email, 1226 $this->firstname . ' ' . $this->lastname, 1227 null, 1228 null, 1229 null, 1230 null, 1231 _PS_MAIL_DIR_, 1232 false, 1233 (int) $this->id_shop 1234 ); 1235 1236 return true; 1237 } 1238 1239 return false; 1240 } 1241 1242 /** 1243 * Set password 1244 * (for webservice). 1245 * 1246 * @param string $passwd Password 1247 * 1248 * @return bool Indictes whether the password has been successfully set 1249 */ 1250 public function setWsPasswd($passwd) 1251 { 1252 /** @var \PrestaShop\PrestaShop\Core\Crypto\Hashing $crypto */ 1253 $crypto = ServiceLocator::get('\\PrestaShop\\PrestaShop\\Core\\Crypto\\Hashing'); 1254 if ($this->id == 0 || $this->passwd != $passwd) { 1255 $this->passwd = $crypto->hash($passwd); 1256 } 1257 1258 return true; 1259 } 1260 1261 /** 1262 * Check customer information and return customer validity. 1263 * 1264 * @since 1.5.0 1265 * 1266 * @param bool $withGuest 1267 * 1268 * @return bool customer validity 1269 */ 1270 public function isLogged($withGuest = false) 1271 { 1272 if (!$withGuest && $this->is_guest == 1) { 1273 return false; 1274 } 1275 1276 /* Customer is valid only if it can be load and if object password is the same as database one */ 1277 return 1278 $this->logged == 1 1279 && $this->id 1280 && Validate::isUnsignedId($this->id) 1281 && Customer::checkPassword($this->id, $this->passwd) 1282 && Context::getContext()->cookie->isSessionAlive() 1283 ; 1284 } 1285 1286 /** 1287 * Logout. 1288 * 1289 * @since 1.5.0 1290 */ 1291 public function logout() 1292 { 1293 Hook::exec('actionCustomerLogoutBefore', ['customer' => $this]); 1294 1295 if (isset(Context::getContext()->cookie)) { 1296 Context::getContext()->cookie->logout(); 1297 } 1298 1299 $this->logged = 0; 1300 1301 Hook::exec('actionCustomerLogoutAfter', ['customer' => $this]); 1302 } 1303 1304 /** 1305 * Soft logout, delete everything that links to the customer 1306 * but leave there affiliate's information. 1307 * 1308 * @since 1.5.0 1309 */ 1310 public function mylogout() 1311 { 1312 Hook::exec('actionCustomerLogoutBefore', ['customer' => $this]); 1313 1314 if (isset(Context::getContext()->cookie)) { 1315 Context::getContext()->cookie->mylogout(); 1316 } 1317 1318 $this->logged = 0; 1319 1320 Hook::exec('actionCustomerLogoutAfter', ['customer' => $this]); 1321 } 1322 1323 /** 1324 * Get last empty Cart for this Customer, when last cart is not empty return false. 1325 * 1326 * @param bool|true $withOrder 1327 * 1328 * @return bool|int 1329 */ 1330 public function getLastEmptyCart($withOrder = true) 1331 { 1332 $carts = Cart::getCustomerCarts((int) $this->id, $withOrder); 1333 if (!count($carts)) { 1334 return false; 1335 } 1336 $cart = array_shift($carts); 1337 $cart = new Cart((int) $cart['id_cart']); 1338 1339 return $cart->nbProducts() === 0 ? (int) $cart->id : false; 1340 } 1341 1342 /** 1343 * Validate controller. 1344 * 1345 * @param bool $htmlentities 1346 * 1347 * @return array 1348 */ 1349 public function validateController($htmlentities = true) 1350 { 1351 $errors = parent::validateController($htmlentities); 1352 /** @var \PrestaShop\PrestaShop\Core\Crypto\Hashing $crypto */ 1353 $crypto = ServiceLocator::get('\\PrestaShop\\PrestaShop\\Core\\Crypto\\Hashing'); 1354 if ($value = Tools::getValue('passwd')) { 1355 $this->passwd = $crypto->hash($value); 1356 } 1357 1358 return $errors; 1359 } 1360 1361 /** 1362 * Get outstanding amount. 1363 * 1364 * @return float Outstanding amount 1365 */ 1366 public function getOutstanding() 1367 { 1368 $query = new DbQuery(); 1369 $query->select('SUM(oi.total_paid_tax_incl)'); 1370 $query->from('order_invoice', 'oi'); 1371 $query->leftJoin('orders', 'o', 'oi.id_order = o.id_order'); 1372 $query->groupBy('o.id_customer'); 1373 $query->where('o.id_customer = ' . (int) $this->id); 1374 $totalPaid = (float) Db::getInstance()->getValue($query->build()); 1375 1376 $query = new DbQuery(); 1377 $query->select('SUM(op.amount)'); 1378 $query->from('order_payment', 'op'); 1379 $query->leftJoin('order_invoice_payment', 'oip', 'op.id_order_payment = oip.id_order_payment'); 1380 $query->leftJoin('orders', 'o', 'oip.id_order = o.id_order'); 1381 $query->groupBy('o.id_customer'); 1382 $query->where('o.id_customer = ' . (int) $this->id); 1383 $totalRest = (float) Db::getInstance()->getValue($query->build()); 1384 1385 return $totalPaid - $totalRest; 1386 } 1387 1388 /** 1389 * Get Customer Groups 1390 * (for webservice). 1391 * 1392 * @return array|false|mysqli_result|PDOStatement|resource|null 1393 */ 1394 public function getWsGroups() 1395 { 1396 return Db::getInstance()->executeS( 1397 ' 1398 SELECT cg.`id_group` as id 1399 FROM ' . _DB_PREFIX_ . 'customer_group cg 1400 ' . Shop::addSqlAssociation('group', 'cg') . ' 1401 WHERE cg.`id_customer` = ' . (int) $this->id 1402 ); 1403 } 1404 1405 /** 1406 * Set Customer Groups 1407 * (for webservice). 1408 * 1409 * @param $result 1410 * 1411 * @return bool 1412 */ 1413 public function setWsGroups($result) 1414 { 1415 $groups = []; 1416 foreach ($result as $row) { 1417 $groups[] = $row['id']; 1418 } 1419 $this->cleanGroups(); 1420 $this->addGroups($groups); 1421 1422 return true; 1423 } 1424 1425 /** 1426 * @see ObjectModel::getWebserviceObjectList() 1427 */ 1428 public function getWebserviceObjectList($sqlJoin, $sqlFilter, $sqlSort, $sqlLimit) 1429 { 1430 $sqlFilter .= Shop::addSqlRestriction(Shop::SHARE_CUSTOMER, 'main'); 1431 1432 return parent::getWebserviceObjectList($sqlJoin, $sqlFilter, $sqlSort, $sqlLimit); 1433 } 1434 1435 /** 1436 * Fill Reset password unique token with random sha1 and its validity date. For forgot password feature. 1437 */ 1438 public function stampResetPasswordToken() 1439 { 1440 $salt = $this->id . '-' . $this->secure_key; 1441 $this->reset_password_token = sha1(time() . $salt); 1442 $validity = (int) Configuration::get('PS_PASSWD_RESET_VALIDITY') ?: 1440; 1443 $this->reset_password_validity = date('Y-m-d H:i:s', strtotime('+' . $validity . ' minutes')); 1444 } 1445 1446 /** 1447 * Test if a reset password token is present and is recent enough to avoid creating a new one (in case of customer triggering the forgot password link too often). 1448 */ 1449 public function hasRecentResetPasswordToken() 1450 { 1451 if (!$this->reset_password_token || $this->reset_password_token == '') { 1452 return false; 1453 } 1454 1455 // TODO maybe use another 'recent' value for this test. For instance, equals password validity value. 1456 if (!$this->reset_password_validity || strtotime($this->reset_password_validity) < time()) { 1457 return false; 1458 } 1459 1460 return true; 1461 } 1462 1463 /** 1464 * Returns the valid reset password token if it validity date is > now(). 1465 */ 1466 public function getValidResetPasswordToken() 1467 { 1468 if (!$this->reset_password_token || $this->reset_password_token == '') { 1469 return false; 1470 } 1471 1472 if (!$this->reset_password_validity || strtotime($this->reset_password_validity) < time()) { 1473 return false; 1474 } 1475 1476 return $this->reset_password_token; 1477 } 1478 1479 /** 1480 * Delete reset password token data. 1481 */ 1482 public function removeResetPasswordToken() 1483 { 1484 $this->reset_password_token = null; 1485 $this->reset_password_validity = null; 1486 } 1487} 1488