vendor/pimcore/pimcore/lib/Tool.php line 300

Open in your IDE?
  1. <?php
  2. /**
  3. * Pimcore
  4. *
  5. * This source file is available under two different licenses:
  6. * - GNU General Public License version 3 (GPLv3)
  7. * - Pimcore Commercial License (PCL)
  8. * Full copyright and license information is available in
  9. * LICENSE.md which is distributed with this source code.
  10. *
  11. * @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
  12. * @license http://www.pimcore.org/license GPLv3 and PCL
  13. */
  14. namespace Pimcore;
  15. use GuzzleHttp\RequestOptions;
  16. use Pimcore\Http\RequestHelper;
  17. use Pimcore\Localization\LocaleServiceInterface;
  18. use Pimcore\Model\Element;
  19. use Symfony\Component\HttpFoundation\Request;
  20. final class Tool
  21. {
  22. /**
  23. * Sets the current request to use when resolving request at early
  24. * stages (before container is loaded)
  25. *
  26. * @var Request
  27. */
  28. private static $currentRequest;
  29. /**
  30. * @var array
  31. */
  32. protected static $notFoundClassNames = [];
  33. /**
  34. * @var array
  35. */
  36. protected static $validLanguages = [];
  37. /**
  38. * @var null
  39. */
  40. protected static $isFrontend = null;
  41. /**
  42. * Sets the current request to operate on
  43. *
  44. * @param Request|null $request
  45. *
  46. * @internal
  47. */
  48. public static function setCurrentRequest(Request $request = null)
  49. {
  50. self::$currentRequest = $request;
  51. }
  52. /**
  53. * Checks, if the given language is configured in pimcore's system
  54. * settings at "Localization & Internationalization (i18n/l10n)".
  55. * Returns true, if the language is valid or no language is
  56. * configured at all, false otherwise.
  57. *
  58. * @static
  59. *
  60. * @param string $language
  61. *
  62. * @return bool
  63. */
  64. public static function isValidLanguage($language)
  65. {
  66. $language = (string) $language; // cast to string
  67. $languages = self::getValidLanguages();
  68. // if not configured, every language is valid
  69. if (!$languages) {
  70. return true;
  71. }
  72. if (in_array($language, $languages)) {
  73. return true;
  74. }
  75. return false;
  76. }
  77. /**
  78. * Returns an array of language codes that configured for this system
  79. * in pimcore's system settings at "Localization & Internationalization (i18n/l10n)".
  80. * An empty array is returned if no languages are configured.
  81. *
  82. * @static
  83. *
  84. * @return string[]
  85. */
  86. public static function getValidLanguages()
  87. {
  88. if (empty(self::$validLanguages)) {
  89. $config = Config::getSystemConfiguration('general');
  90. if (empty($config['valid_languages'])) {
  91. return [];
  92. }
  93. $validLanguages = str_replace(' ', '', (string)$config['valid_languages']);
  94. $languages = explode(',', $validLanguages);
  95. if (!is_array($languages)) {
  96. $languages = [];
  97. }
  98. self::$validLanguages = $languages;
  99. }
  100. return self::$validLanguages;
  101. }
  102. /**
  103. * @internal
  104. *
  105. * @param string $language
  106. *
  107. * @return array
  108. */
  109. public static function getFallbackLanguagesFor($language)
  110. {
  111. $languages = [];
  112. $config = Config::getSystemConfiguration('general');
  113. if (!empty($config['fallback_languages'][$language])) {
  114. $fallbackLanguages = explode(',', $config['fallback_languages'][$language]);
  115. foreach ($fallbackLanguages as $l) {
  116. if (self::isValidLanguage($l)) {
  117. $languages[] = trim($l);
  118. }
  119. }
  120. }
  121. return $languages;
  122. }
  123. /**
  124. * Returns the default language for this system. If no default is set,
  125. * returns the first language, or null, if no languages are configured
  126. * at all.
  127. *
  128. * @return null|string
  129. */
  130. public static function getDefaultLanguage()
  131. {
  132. $config = Config::getSystemConfiguration('general');
  133. $defaultLanguage = $config['default_language'] ?? null;
  134. $languages = self::getValidLanguages();
  135. if (!empty($languages) && in_array($defaultLanguage, $languages)) {
  136. return $defaultLanguage;
  137. } elseif (!empty($languages)) {
  138. return $languages[0];
  139. }
  140. return null;
  141. }
  142. /**
  143. * @return array|mixed
  144. *
  145. * @throws \Exception
  146. */
  147. public static function getSupportedLocales()
  148. {
  149. $localeService = \Pimcore::getContainer()->get(LocaleServiceInterface::class);
  150. $locale = $localeService->findLocale();
  151. $cacheKey = 'system_supported_locales_' . strtolower((string) $locale);
  152. if (!$languageOptions = Cache::load($cacheKey)) {
  153. $languages = $localeService->getLocaleList();
  154. $languageOptions = [];
  155. foreach ($languages as $code) {
  156. $translation = \Locale::getDisplayLanguage($code, $locale);
  157. $displayRegion = \Locale::getDisplayRegion($code, $locale);
  158. if ($displayRegion) {
  159. $translation .= ' (' . $displayRegion . ')';
  160. }
  161. if (!$translation) {
  162. $translation = $code;
  163. }
  164. $languageOptions[$code] = $translation;
  165. }
  166. asort($languageOptions);
  167. Cache::save($languageOptions, $cacheKey, ['system']);
  168. }
  169. return $languageOptions;
  170. }
  171. /**
  172. * @internal
  173. *
  174. * @param string $language
  175. * @param bool $absolutePath
  176. *
  177. * @return string
  178. */
  179. public static function getLanguageFlagFile($language, $absolutePath = true)
  180. {
  181. $basePath = '/bundles/pimcoreadmin/img/flags';
  182. $iconFsBasePath = PIMCORE_WEB_ROOT . $basePath;
  183. if ($absolutePath === true) {
  184. $basePath = PIMCORE_WEB_ROOT . $basePath;
  185. }
  186. $code = strtolower($language);
  187. $code = str_replace('_', '-', $code);
  188. $countryCode = null;
  189. $fallbackLanguageCode = null;
  190. $parts = explode('-', $code);
  191. if (count($parts) > 1) {
  192. $countryCode = array_pop($parts);
  193. $fallbackLanguageCode = $parts[0];
  194. }
  195. $languageFsPath = $iconFsBasePath . '/languages/' . $code . '.svg';
  196. $countryFsPath = $iconFsBasePath . '/countries/' . $countryCode . '.svg';
  197. $fallbackFsLanguagePath = $iconFsBasePath . '/languages/' . $fallbackLanguageCode . '.svg';
  198. $iconPath = ($absolutePath === true ? $iconFsBasePath : $basePath) . '/countries/_unknown.svg';
  199. $languageCountryMapping = [
  200. 'aa' => 'er', 'af' => 'za', 'am' => 'et', 'as' => 'in', 'ast' => 'es', 'asa' => 'tz',
  201. 'az' => 'az', 'bas' => 'cm', 'eu' => 'es', 'be' => 'by', 'bem' => 'zm', 'bez' => 'tz', 'bg' => 'bg',
  202. 'bm' => 'ml', 'bn' => 'bd', 'br' => 'fr', 'brx' => 'in', 'bs' => 'ba', 'cs' => 'cz', 'da' => 'dk',
  203. 'de' => 'de', 'dz' => 'bt', 'el' => 'gr', 'en' => 'gb', 'es' => 'es', 'et' => 'ee', 'fi' => 'fi',
  204. 'fo' => 'fo', 'fr' => 'fr', 'ga' => 'ie', 'gv' => 'im', 'he' => 'il', 'hi' => 'in', 'hr' => 'hr',
  205. 'hu' => 'hu', 'hy' => 'am', 'id' => 'id', 'ig' => 'ng', 'is' => 'is', 'it' => 'it', 'ja' => 'jp',
  206. 'ka' => 'ge', 'os' => 'ge', 'kea' => 'cv', 'kk' => 'kz', 'kl' => 'gl', 'km' => 'kh', 'ko' => 'kr',
  207. 'lg' => 'ug', 'lo' => 'la', 'lt' => 'lt', 'mg' => 'mg', 'mk' => 'mk', 'mn' => 'mn', 'ms' => 'my',
  208. 'mt' => 'mt', 'my' => 'mm', 'nb' => 'no', 'ne' => 'np', 'nl' => 'nl', 'nn' => 'no', 'pl' => 'pl',
  209. 'pt' => 'pt', 'ro' => 'ro', 'ru' => 'ru', 'sg' => 'cf', 'sk' => 'sk', 'sl' => 'si', 'sq' => 'al',
  210. 'sr' => 'rs', 'sv' => 'se', 'swc' => 'cd', 'th' => 'th', 'to' => 'to', 'tr' => 'tr', 'tzm' => 'ma',
  211. 'uk' => 'ua', 'uz' => 'uz', 'vi' => 'vn', 'zh' => 'cn', 'gd' => 'gb-sct', 'gd-gb' => 'gb-sct',
  212. 'cy' => 'gb-wls', 'cy-gb' => 'gb-wls', 'fy' => 'nl', 'xh' => 'za', 'yo' => 'bj', 'zu' => 'za',
  213. 'ta' => 'lk', 'te' => 'in', 'ss' => 'za', 'sw' => 'ke', 'so' => 'so', 'si' => 'lk', 'ii' => 'cn',
  214. 'zh-hans' => 'cn', 'zh-hant' => 'cn', 'sn' => 'zw', 'rm' => 'ch', 'pa' => 'in', 'fa' => 'ir', 'lv' => 'lv', 'gl' => 'es',
  215. 'fil' => 'ph',
  216. ];
  217. if (array_key_exists($code, $languageCountryMapping)) {
  218. $iconPath = $basePath . '/countries/' . $languageCountryMapping[$code] . '.svg';
  219. } elseif (file_exists($languageFsPath)) {
  220. $iconPath = $basePath . '/languages/' . $code . '.svg';
  221. } elseif ($countryCode && file_exists($countryFsPath)) {
  222. $iconPath = $basePath . '/countries/' . $countryCode . '.svg';
  223. } elseif ($fallbackLanguageCode && file_exists($fallbackFsLanguagePath)) {
  224. $iconPath = $basePath . '/languages/' . $fallbackLanguageCode . '.svg';
  225. }
  226. return $iconPath;
  227. }
  228. /**
  229. * @param Request|null $request
  230. *
  231. * @return null|Request
  232. */
  233. private static function resolveRequest(Request $request = null)
  234. {
  235. if (null === $request) {
  236. // do an extra check for the container as we might be in a state where no container is set yet
  237. if (\Pimcore::hasContainer()) {
  238. $request = \Pimcore::getContainer()->get('request_stack')->getMasterRequest();
  239. } else {
  240. if (null !== self::$currentRequest) {
  241. return self::$currentRequest;
  242. }
  243. }
  244. }
  245. return $request;
  246. }
  247. /**
  248. * @param Request|null $request
  249. *
  250. * @return bool
  251. */
  252. public static function isFrontend(Request $request = null): bool
  253. {
  254. if (null === $request) {
  255. $request = \Pimcore::getContainer()->get('request_stack')->getMasterRequest();
  256. }
  257. if (null === $request) {
  258. return false;
  259. }
  260. return \Pimcore::getContainer()
  261. ->get(RequestHelper::class)
  262. ->isFrontendRequest($request);
  263. }
  264. /**
  265. * eg. editmode, preview, version preview, always when it is a "frontend-request", but called out of the admin
  266. *
  267. * @param Request|null $request
  268. *
  269. * @return bool
  270. */
  271. public static function isFrontendRequestByAdmin(Request $request = null)
  272. {
  273. $request = self::resolveRequest($request);
  274. if (null === $request) {
  275. return false;
  276. }
  277. return \Pimcore::getContainer()
  278. ->get(RequestHelper::class)
  279. ->isFrontendRequestByAdmin($request);
  280. }
  281. /**
  282. * Verify element request (eg. editmode, preview, version preview) called within admin, with permissions.
  283. *
  284. * @param Request $request
  285. * @param Element\ElementInterface $element
  286. *
  287. * @return bool
  288. */
  289. public static function isElementRequestByAdmin(Request $request, Element\ElementInterface $element)
  290. {
  291. if (!self::isFrontendRequestByAdmin($request)) {
  292. return false;
  293. }
  294. $user = Tool\Authentication::authenticateSession($request);
  295. return $user && $element->isAllowed('view', $user);
  296. }
  297. /**
  298. * @internal
  299. *
  300. * @param Request|null $request
  301. *
  302. * @return bool
  303. */
  304. public static function useFrontendOutputFilters(Request $request = null)
  305. {
  306. $request = self::resolveRequest($request);
  307. if (null === $request) {
  308. return false;
  309. }
  310. if (!self::isFrontend($request)) {
  311. return false;
  312. }
  313. if (self::isFrontendRequestByAdmin($request)) {
  314. return false;
  315. }
  316. $requestKeys = array_merge(
  317. array_keys($request->query->all()),
  318. array_keys($request->request->all())
  319. );
  320. // check for manually disabled ?pimcore_outputfilters_disabled=true
  321. if (in_array('pimcore_outputfilters_disabled', $requestKeys) && \Pimcore::inDebugMode()) {
  322. return false;
  323. }
  324. return true;
  325. }
  326. /**
  327. * @internal
  328. *
  329. * @param Request|null $request
  330. *
  331. * @return null|string
  332. */
  333. public static function getHostname(Request $request = null)
  334. {
  335. $request = self::resolveRequest($request);
  336. if (null === $request) {
  337. return null;
  338. }
  339. return $request->getHost();
  340. }
  341. /**
  342. * @internal
  343. *
  344. * @return string
  345. */
  346. public static function getRequestScheme(Request $request = null)
  347. {
  348. $request = self::resolveRequest($request);
  349. if (null === $request) {
  350. return 'http';
  351. }
  352. return $request->getScheme();
  353. }
  354. /**
  355. * Returns the host URL
  356. *
  357. * @param string|null $useProtocol use a specific protocol
  358. * @param Request|null $request
  359. *
  360. * @return string
  361. */
  362. public static function getHostUrl($useProtocol = null, Request $request = null)
  363. {
  364. $request = self::resolveRequest($request);
  365. $protocol = 'http';
  366. $hostname = '';
  367. $port = '';
  368. if (null !== $request) {
  369. $protocol = $request->getScheme();
  370. $hostname = $request->getHost();
  371. if (!in_array($request->getPort(), [443, 80])) {
  372. $port = ':' . $request->getPort();
  373. }
  374. }
  375. // get it from System settings
  376. if (!$hostname || $hostname == 'localhost') {
  377. $systemConfig = Config::getSystemConfiguration('general');
  378. $hostname = $systemConfig['domain'] ?? null;
  379. if (!$hostname) {
  380. Logger::warn('Couldn\'t determine HTTP Host. No Domain set in "Settings" -> "System" -> "Website" -> "Domain"');
  381. return '';
  382. }
  383. }
  384. if ($useProtocol) {
  385. $protocol = $useProtocol;
  386. }
  387. return $protocol . '://' . $hostname . $port;
  388. }
  389. /**
  390. * @internal
  391. *
  392. * @param Request|null $request
  393. *
  394. * @return string|null
  395. */
  396. public static function getClientIp(Request $request = null)
  397. {
  398. $request = self::resolveRequest($request);
  399. if ($request) {
  400. return $request->getClientIp();
  401. }
  402. // fallback to $_SERVER variables
  403. if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
  404. $ip = $_SERVER['HTTP_CLIENT_IP'];
  405. } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
  406. $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
  407. } elseif (!empty($_SERVER['REMOTE_ADDR'])) {
  408. $ip = $_SERVER['REMOTE_ADDR'];
  409. } else {
  410. return null;
  411. }
  412. $ips = explode(',', $ip);
  413. $ip = trim(array_shift($ips));
  414. return $ip;
  415. }
  416. /**
  417. * @internal
  418. *
  419. * @param Request|null $request
  420. *
  421. * @return null|string
  422. */
  423. public static function getAnonymizedClientIp(Request $request = null)
  424. {
  425. $request = self::resolveRequest($request);
  426. if (null === $request) {
  427. return null;
  428. }
  429. return \Pimcore::getContainer()
  430. ->get(RequestHelper::class)
  431. ->getAnonymizedClientIp($request);
  432. }
  433. /**
  434. * @internal
  435. *
  436. * @return array|bool
  437. */
  438. public static function getCustomViewConfig()
  439. {
  440. $configFile = \Pimcore\Config::locateConfigFile('customviews.php');
  441. if (!is_file($configFile)) {
  442. $cvData = false;
  443. } else {
  444. $confArray = include($configFile);
  445. $cvData = [];
  446. foreach ($confArray['views'] as $tmp) {
  447. if (isset($tmp['name'])) {
  448. $tmp['showroot'] = !empty($tmp['showroot']);
  449. if (!is_array($tmp['classes'] ?? [])) {
  450. $flipArray = [];
  451. $tempClasses = explode(',', $tmp['classes']);
  452. foreach ($tempClasses as $tempClass) {
  453. $flipArray[$tempClass] = null;
  454. }
  455. $tmp['classes'] = $flipArray;
  456. }
  457. if (!empty($tmp['hidden'])) {
  458. continue;
  459. }
  460. $cvData[] = $tmp;
  461. }
  462. }
  463. }
  464. return $cvData;
  465. }
  466. /**
  467. * @param array|string|null $recipients
  468. * @param string|null $subject
  469. *
  470. * @return Mail
  471. *
  472. * @throws \Exception
  473. */
  474. public static function getMail($recipients = null, $subject = null)
  475. {
  476. $mail = new Mail();
  477. if ($recipients) {
  478. if (is_string($recipients)) {
  479. $mail->addTo($recipients);
  480. } elseif (is_array($recipients)) {
  481. foreach ($recipients as $recipient) {
  482. $mail->addTo($recipient);
  483. }
  484. }
  485. }
  486. if ($subject) {
  487. $mail->setSubject($subject);
  488. }
  489. return $mail;
  490. }
  491. /**
  492. * @param string $url
  493. * @param array $paramsGet
  494. * @param array $paramsPost
  495. * @param array $options
  496. *
  497. * @return bool|string
  498. */
  499. public static function getHttpData($url, $paramsGet = [], $paramsPost = [], $options = [])
  500. {
  501. $client = \Pimcore::getContainer()->get('pimcore.http_client');
  502. $requestType = 'GET';
  503. if (!isset($options['timeout'])) {
  504. $options['timeout'] = 5;
  505. }
  506. if (is_array($paramsGet) && count($paramsGet) > 0) {
  507. //need to insert get params from url to $paramsGet because otherwise the would be ignored
  508. $urlParts = parse_url($url);
  509. $urlParams = [];
  510. parse_str($urlParts['query'], $urlParams);
  511. if ($urlParams) {
  512. $paramsGet = array_merge($urlParams, $paramsGet);
  513. }
  514. $options[RequestOptions::QUERY] = $paramsGet;
  515. }
  516. if (is_array($paramsPost) && count($paramsPost) > 0) {
  517. $options[RequestOptions::FORM_PARAMS] = $paramsPost;
  518. $requestType = 'POST';
  519. }
  520. try {
  521. $response = $client->request($requestType, $url, $options);
  522. if ($response->getStatusCode() < 300) {
  523. return (string)$response->getBody();
  524. }
  525. } catch (\Exception $e) {
  526. }
  527. return false;
  528. }
  529. /**
  530. * @internal
  531. *
  532. * @param string $class
  533. *
  534. * @return bool
  535. */
  536. public static function classExists($class)
  537. {
  538. return self::classInterfaceExists($class, 'class');
  539. }
  540. /**
  541. * @internal
  542. *
  543. * @param string $class
  544. *
  545. * @return bool
  546. */
  547. public static function interfaceExists($class)
  548. {
  549. return self::classInterfaceExists($class, 'interface');
  550. }
  551. /**
  552. * @internal
  553. *
  554. * @param string $class
  555. *
  556. * @return bool
  557. */
  558. public static function traitExists($class)
  559. {
  560. return self::classInterfaceExists($class, 'trait');
  561. }
  562. /**
  563. * @param string $class
  564. * @param string $type (e.g. 'class', 'interface', 'trait')
  565. *
  566. * @return bool
  567. */
  568. private static function classInterfaceExists($class, $type)
  569. {
  570. $functionName = $type . '_exists';
  571. // if the class is already loaded we can skip right here
  572. if ($functionName($class, false)) {
  573. return true;
  574. }
  575. $class = '\\' . ltrim($class, '\\');
  576. // let's test if we have seens this class already before
  577. if (isset(self::$notFoundClassNames[$class])) {
  578. return false;
  579. }
  580. // we need to set a custom error handler here for the time being
  581. // unfortunately suppressNotFoundWarnings() doesn't work all the time, it has something to do with the calls in
  582. // Pimcore\Tool::ClassMapAutoloader(), but don't know what actual conditions causes this problem.
  583. // but to be save we log the errors into the debug.log, so if anything else happens we can see it there
  584. // the normal warning is e.g. Warning: include_once(Path/To/Class.php): failed to open stream: No such file or directory in ...
  585. set_error_handler(function ($errno, $errstr, $errfile, $errline) {
  586. //Logger::debug(implode(" ", [$errno, $errstr, $errfile, $errline]));
  587. });
  588. $exists = $functionName($class);
  589. restore_error_handler();
  590. if (!$exists) {
  591. self::$notFoundClassNames[$class] = true; // value doesn't matter, key lookups are faster ;-)
  592. }
  593. return $exists;
  594. }
  595. /**
  596. * @internal
  597. *
  598. * @return array
  599. */
  600. public static function getCachedSymfonyEnvironments(): array
  601. {
  602. $dirs = glob(PIMCORE_SYMFONY_CACHE_DIRECTORY . '/*', GLOB_ONLYDIR);
  603. if (($key = array_search(PIMCORE_CACHE_DIRECTORY, $dirs)) !== false) {
  604. unset($dirs[$key]);
  605. }
  606. $dirs = array_map('basename', $dirs);
  607. return array_values($dirs);
  608. }
  609. /**
  610. * @internal
  611. *
  612. * @param string $message
  613. */
  614. public static function exitWithError($message)
  615. {
  616. while (@ob_end_flush());
  617. if (php_sapi_name() != 'cli') {
  618. header('HTTP/1.1 503 Service Temporarily Unavailable');
  619. }
  620. die($message);
  621. }
  622. }