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 */ 26 27use PrestaShop\PrestaShop\Adapter\ContainerFinder; 28use PrestaShop\PrestaShop\Adapter\Module\Repository\ModuleRepository; 29use PrestaShop\PrestaShop\Adapter\SymfonyContainer; 30use PrestaShop\PrestaShop\Core\Exception\ContainerNotFoundException; 31use PrestaShop\PrestaShop\Core\Localization\CLDR\ComputingPrecision; 32use PrestaShop\PrestaShop\Core\Localization\Locale; 33use PrestaShopBundle\Install\Language as InstallLanguage; 34use PrestaShopBundle\Translation\TranslatorComponent as Translator; 35use PrestaShopBundle\Translation\TranslatorLanguageLoader; 36use Symfony\Component\DependencyInjection\ContainerBuilder; 37use Symfony\Component\Filesystem\Filesystem; 38use Symfony\Component\Finder\Finder; 39use Symfony\Component\HttpFoundation\Session\SessionInterface; 40 41/** 42 * Class ContextCore. 43 * 44 * @since 1.5.0.1 45 */ 46class ContextCore 47{ 48 /** @var Context */ 49 protected static $instance; 50 51 /** @var Cart */ 52 public $cart; 53 54 /** @var Customer */ 55 public $customer; 56 57 /** @var Cookie */ 58 public $cookie; 59 60 /** @var SessionInterface|null */ 61 public $session; 62 63 /** @var Link */ 64 public $link; 65 66 /** @var Country */ 67 public $country; 68 69 /** @var Employee|null */ 70 public $employee; 71 72 /** @var AdminController|FrontController */ 73 public $controller; 74 75 /** @var string */ 76 public $override_controller_name_for_translations; 77 78 /** @var Language|InstallLanguage */ 79 public $language; 80 81 /** @var Currency|null */ 82 public $currency; 83 84 /** 85 * Current locale instance. 86 * 87 * @var Locale|null 88 */ 89 public $currentLocale; 90 91 /** @var Tab */ 92 public $tab; 93 94 /** @var Shop */ 95 public $shop; 96 97 /** @var Smarty */ 98 public $smarty; 99 100 /** @var \Mobile_Detect */ 101 public $mobile_detect; 102 103 /** @var int */ 104 public $mode; 105 106 /** @var ContainerBuilder */ 107 public $container; 108 109 /** @var Translator */ 110 protected $translator = null; 111 112 /** @var int */ 113 protected $priceComputingPrecision = null; 114 115 /** 116 * Mobile device of the customer. 117 * 118 * @var bool|null 119 */ 120 protected $mobile_device = null; 121 122 /** @var bool|null */ 123 protected $is_mobile = null; 124 125 /** @var bool|null */ 126 protected $is_tablet = null; 127 128 /** @var int */ 129 const DEVICE_COMPUTER = 1; 130 131 /** @var int */ 132 const DEVICE_TABLET = 2; 133 134 /** @var int */ 135 const DEVICE_MOBILE = 4; 136 137 /** @var int */ 138 const MODE_STD = 1; 139 140 /** @var int */ 141 const MODE_STD_CONTRIB = 2; 142 143 /** @var int */ 144 const MODE_HOST_CONTRIB = 4; 145 146 /** @var int */ 147 const MODE_HOST = 8; 148 149 /** 150 * Sets Mobile_Detect tool object. 151 * 152 * @return Mobile_Detect 153 */ 154 public function getMobileDetect() 155 { 156 if ($this->mobile_detect === null) { 157 $this->mobile_detect = new Mobile_Detect(); 158 } 159 160 return $this->mobile_detect; 161 } 162 163 /** 164 * Checks if visitor's device is a mobile device. 165 * 166 * @return bool 167 */ 168 public function isMobile() 169 { 170 if ($this->is_mobile === null) { 171 $mobileDetect = $this->getMobileDetect(); 172 $this->is_mobile = $mobileDetect->isMobile(); 173 } 174 175 return $this->is_mobile; 176 } 177 178 /** 179 * Checks if visitor's device is a tablet device. 180 * 181 * @return bool 182 */ 183 public function isTablet() 184 { 185 if ($this->is_tablet === null) { 186 $mobileDetect = $this->getMobileDetect(); 187 $this->is_tablet = $mobileDetect->isTablet(); 188 } 189 190 return $this->is_tablet; 191 } 192 193 /** 194 * Sets mobile_device context variable. 195 * 196 * @return bool 197 */ 198 public function getMobileDevice() 199 { 200 if ($this->mobile_device === null) { 201 $this->mobile_device = false; 202 if ($this->checkMobileContext()) { 203 if (isset(Context::getContext()->cookie->no_mobile) && Context::getContext()->cookie->no_mobile == false && (int) Configuration::get('PS_ALLOW_MOBILE_DEVICE') != 0) { 204 $this->mobile_device = true; 205 } else { 206 switch ((int) Configuration::get('PS_ALLOW_MOBILE_DEVICE')) { 207 case 1: // Only for mobile device 208 if ($this->isMobile() && !$this->isTablet()) { 209 $this->mobile_device = true; 210 } 211 212 break; 213 case 2: // Only for touchpads 214 if ($this->isTablet() && !$this->isMobile()) { 215 $this->mobile_device = true; 216 } 217 218 break; 219 case 3: // For touchpad or mobile devices 220 if ($this->isMobile() || $this->isTablet()) { 221 $this->mobile_device = true; 222 } 223 224 break; 225 } 226 } 227 } 228 } 229 230 return $this->mobile_device; 231 } 232 233 /** 234 * Returns mobile device type. 235 * 236 * @return int 237 */ 238 public function getDevice() 239 { 240 static $device = null; 241 242 if ($device === null) { 243 if ($this->isTablet()) { 244 $device = Context::DEVICE_TABLET; 245 } elseif ($this->isMobile()) { 246 $device = Context::DEVICE_MOBILE; 247 } else { 248 $device = Context::DEVICE_COMPUTER; 249 } 250 } 251 252 return $device; 253 } 254 255 /** 256 * @return Locale|null 257 */ 258 public function getCurrentLocale() 259 { 260 return $this->currentLocale; 261 } 262 263 /** 264 * Checks if mobile context is possible. 265 * 266 * @return bool 267 * 268 * @throws PrestaShopException 269 */ 270 protected function checkMobileContext() 271 { 272 // Check mobile context 273 if (Tools::isSubmit('no_mobile_theme')) { 274 Context::getContext()->cookie->no_mobile = true; 275 if (Context::getContext()->cookie->id_guest) { 276 $guest = new Guest(Context::getContext()->cookie->id_guest); 277 $guest->mobile_theme = false; 278 $guest->update(); 279 } 280 } elseif (Tools::isSubmit('mobile_theme_ok')) { 281 Context::getContext()->cookie->no_mobile = false; 282 if (Context::getContext()->cookie->id_guest) { 283 $guest = new Guest(Context::getContext()->cookie->id_guest); 284 $guest->mobile_theme = true; 285 $guest->update(); 286 } 287 } 288 289 return isset($_SERVER['HTTP_USER_AGENT'], Context::getContext()->cookie) 290 && (bool) Configuration::get('PS_ALLOW_MOBILE_DEVICE') 291 && @filemtime(_PS_THEME_MOBILE_DIR_) 292 && !Context::getContext()->cookie->no_mobile; 293 } 294 295 /** 296 * Get a singleton instance of Context object. 297 * 298 * @return Context|null 299 */ 300 public static function getContext() 301 { 302 if (!isset(self::$instance)) { 303 self::$instance = new Context(); 304 } 305 306 return self::$instance; 307 } 308 309 /** 310 * @param $testInstance Context 311 * Unit testing purpose only 312 */ 313 public static function setInstanceForTesting($testInstance) 314 { 315 self::$instance = $testInstance; 316 } 317 318 /** 319 * Unit testing purpose only. 320 */ 321 public static function deleteTestingInstance() 322 { 323 self::$instance = null; 324 } 325 326 /** 327 * Clone current context object. 328 * 329 * @return Context 330 */ 331 public function cloneContext() 332 { 333 return clone $this; 334 } 335 336 /** 337 * Update context after customer login. 338 * 339 * @param Customer $customer Created customer 340 */ 341 public function updateCustomer(Customer $customer) 342 { 343 $this->customer = $customer; 344 $this->cookie->id_customer = (int) $customer->id; 345 $this->cookie->customer_lastname = $customer->lastname; 346 $this->cookie->customer_firstname = $customer->firstname; 347 $this->cookie->passwd = $customer->passwd; 348 $this->cookie->logged = 1; 349 $customer->logged = 1; 350 $this->cookie->email = $customer->email; 351 $this->cookie->is_guest = $customer->isGuest(); 352 353 if (Configuration::get('PS_CART_FOLLOWING') && (empty($this->cookie->id_cart) || Cart::getNbProducts($this->cookie->id_cart) == 0) && $idCart = (int) Cart::lastNoneOrderedCart($this->customer->id)) { 354 $this->cart = new Cart($idCart); 355 $this->cart->secure_key = $customer->secure_key; 356 } else { 357 $idCarrier = (int) $this->cart->id_carrier; 358 $this->cart->secure_key = $customer->secure_key; 359 $this->cart->id_carrier = 0; 360 $this->cart->setDeliveryOption(null); 361 $this->cart->updateAddressId($this->cart->id_address_delivery, (int) Address::getFirstCustomerAddressId((int) ($customer->id))); 362 $this->cart->id_address_delivery = (int) Address::getFirstCustomerAddressId((int) ($customer->id)); 363 $this->cart->id_address_invoice = (int) Address::getFirstCustomerAddressId((int) ($customer->id)); 364 } 365 $this->cart->id_customer = (int) $customer->id; 366 367 if (isset($idCarrier) && $idCarrier) { 368 $deliveryOption = [$this->cart->id_address_delivery => $idCarrier . ',']; 369 $this->cart->setDeliveryOption($deliveryOption); 370 } 371 372 $this->cart->save(); 373 $this->cookie->id_cart = (int) $this->cart->id; 374 $this->cookie->write(); 375 $this->cart->autosetProductAddress(); 376 377 $this->cookie->registerSession(new CustomerSession()); 378 } 379 380 /** 381 * Returns a translator depending on service container availability and if the method 382 * is called by the installer or not. 383 * 384 * @param bool $isInstaller Set to true if the method is called by the installer 385 * 386 * @return Translator 387 */ 388 public function getTranslator($isInstaller = false) 389 { 390 if (null !== $this->translator && $this->language->locale === $this->translator->getLocale()) { 391 return $this->translator; 392 } 393 394 $sfContainer = SymfonyContainer::getInstance(); 395 396 if ($isInstaller || null === $sfContainer) { 397 // symfony's container isn't available in front office, so we load and configure the translator component 398 $this->translator = $this->getTranslatorFromLocale($this->language->locale); 399 } else { 400 $this->translator = $sfContainer->get('translator'); 401 // We need to set the locale here because in legacy BO pages, the translator is used 402 // before the TranslatorListener does its job of setting the locale according to the Request object 403 $this->translator->setLocale($this->language->locale); 404 } 405 406 return $this->translator; 407 } 408 409 /** 410 * Returns a new instance of Translator for the provided locale code. 411 * 412 * @param string $locale IETF language tag (eg. "en-US") 413 * 414 * @return Translator 415 */ 416 public function getTranslatorFromLocale($locale) 417 { 418 $cacheDir = _PS_CACHE_DIR_ . 'translations'; 419 $translator = new Translator($locale, null, $cacheDir, false); 420 421 // In case we have at least 1 translated message, we return the current translator. 422 // If some translations are missing, clear cache 423 if ($locale === '' || null === $locale || count($translator->getCatalogue($locale)->all())) { 424 return $translator; 425 } 426 427 // However, in some case, even empty catalog were stored in the cache and then used as-is. 428 // For this one, we drop the cache and try to regenerate it. 429 if (is_dir($cacheDir)) { 430 $cache_file = Finder::create() 431 ->files() 432 ->in($cacheDir) 433 ->depth('==0') 434 ->name('*.' . $locale . '.*'); 435 (new Filesystem())->remove($cache_file); 436 } 437 438 $translator->clearLanguage($locale); 439 440 $adminContext = defined('_PS_ADMIN_DIR_'); 441 // Do not load DB translations when $this->language is InstallLanguage 442 // because it means that we're looking for the installer translations, so we're not yet connected to the DB 443 $withDB = !$this->language instanceof InstallLanguage; 444 $theme = $this->shop !== null ? $this->shop->theme : null; 445 446 try { 447 $containerFinder = new ContainerFinder($this); 448 $containerFinder->getContainer()->get('prestashop.translation.translator_language_loader') 449 ->setIsAdminContext($adminContext) 450 ->loadLanguage($translator, $locale, $withDB, $theme); 451 } catch (ContainerNotFoundException $exception) { 452 // If a container is still not found, instantiate manually the translator loader 453 // This will happen in the Front as we have legacy controllers, the Sf container won't be available. 454 // As we get the translator in the controller's constructor and the container is built in the init method, we won't find it here 455 (new TranslatorLanguageLoader(new ModuleRepository())) 456 ->setIsAdminContext($adminContext) 457 ->loadLanguage($translator, $locale, $withDB, $theme); 458 } 459 460 return $translator; 461 } 462 463 /** 464 * @return array 465 */ 466 protected function getTranslationResourcesDirectories() 467 { 468 $locations = [_PS_ROOT_DIR_ . '/app/Resources/translations']; 469 470 if (null !== $this->shop) { 471 $activeThemeLocation = _PS_ROOT_DIR_ . '/themes/' . $this->shop->theme_name . '/translations'; 472 if (is_dir($activeThemeLocation)) { 473 $locations[] = $activeThemeLocation; 474 } 475 } 476 477 return $locations; 478 } 479 480 /** 481 * Returns the computing precision according to the current currency 482 * 483 * @return int 484 */ 485 public function getComputingPrecision() 486 { 487 if ($this->priceComputingPrecision === null) { 488 $computingPrecision = new ComputingPrecision(); 489 $this->priceComputingPrecision = $computingPrecision->getPrecision($this->currency->precision); 490 } 491 492 return $this->priceComputingPrecision; 493 } 494} 495