137748cd8SNickeau<?php 237748cd8SNickeau/** 337748cd8SNickeau * Copyright (c) 2021. ComboStrap, Inc. and its affiliates. All Rights Reserved. 437748cd8SNickeau * 537748cd8SNickeau * This source code is licensed under the GPL license found in the 637748cd8SNickeau * COPYING file in the root directory of this source tree. 737748cd8SNickeau * 837748cd8SNickeau * @license GPL 3 (https://www.gnu.org/licenses/gpl-3.0.en.html) 937748cd8SNickeau * @author ComboStrap <support@combostrap.com> 1037748cd8SNickeau * 1137748cd8SNickeau */ 1237748cd8SNickeau 1337748cd8SNickeaunamespace ComboStrap; 1437748cd8SNickeau 1504fd306cSNickeauuse ComboStrap\Web\Url; 1604fd306cSNickeau 1737748cd8SNickeauclass Bootstrap 1837748cd8SNickeau{ 1904fd306cSNickeau const DEFAULT_STYLESHEET_NAME = "bootstrap"; 2004fd306cSNickeau const TAG = self::CANONICAL; 2104fd306cSNickeau public const DEFAULT_MAJOR = 5; 2204fd306cSNickeau public const VERSION_501 = self::DEFAULT_MAJOR . ".0.1"; 2304fd306cSNickeau public const DEFAULT_BOOTSTRAP_4_VERSION = "4.5.0"; 2404fd306cSNickeau public const DEFAULT_BOOTSTRAP_5_VERSION = self::VERSION_501; 2504fd306cSNickeau public const DEFAULT_BOOTSTRAP_STYLESHEET_4_VERSION = self::DEFAULT_BOOTSTRAP_4_VERSION . " - " . self::DEFAULT_STYLESHEET_NAME; 2604fd306cSNickeau public const DEFAULT_BOOTSTRAP_STYLESHEET_5_VERSION = self::DEFAULT_BOOTSTRAP_5_VERSION . " - " . self::DEFAULT_STYLESHEET_NAME; 2737748cd8SNickeau 2804fd306cSNickeau private Snippet $jquerySnippet; 2904fd306cSNickeau private Snippet $jsSnippet; 3004fd306cSNickeau private Snippet $popperSnippet; 3104fd306cSNickeau private Snippet $cssSnippet; 3237748cd8SNickeau 3304fd306cSNickeau /** 3404fd306cSNickeau * @param string $qualifiedVersion - the bootstrap version separated by the stylesheet 3504fd306cSNickeau */ 3604fd306cSNickeau public function __construct(string $qualifiedVersion) 3704fd306cSNickeau { 3804fd306cSNickeau $bootstrapStyleSheetArray = explode(Bootstrap::BOOTSTRAP_VERSION_STYLESHEET_SEPARATOR, $qualifiedVersion); 3904fd306cSNickeau $this->version = $bootstrapStyleSheetArray[0]; 4004fd306cSNickeau /** 4104fd306cSNickeau * In case the input is just the major version 4204fd306cSNickeau */ 4304fd306cSNickeau switch ($this->version) { 4404fd306cSNickeau case "4": 4504fd306cSNickeau $this->version = self::DEFAULT_BOOTSTRAP_4_VERSION; 4604fd306cSNickeau break; 4704fd306cSNickeau case "5": 4804fd306cSNickeau $this->version = self::DEFAULT_BOOTSTRAP_5_VERSION; 4904fd306cSNickeau break; 5004fd306cSNickeau } 5104fd306cSNickeau 5204fd306cSNickeau /** 5304fd306cSNickeau * Stylesheet 5404fd306cSNickeau */ 5504fd306cSNickeau if (isset($bootstrapStyleSheetArray[1])) { 5604fd306cSNickeau $this->styleSheetName = $bootstrapStyleSheetArray[1]; 5704fd306cSNickeau } else { 5804fd306cSNickeau $this->styleSheetName = self::DEFAULT_STYLESHEET_NAME; 5904fd306cSNickeau } 6004fd306cSNickeau 6104fd306cSNickeau $this->build(); 6204fd306cSNickeau 6304fd306cSNickeau } 6404fd306cSNickeau 6504fd306cSNickeau 6604fd306cSNickeau const BootStrapFiveMajorVersion = 5; 6704fd306cSNickeau const BootStrapFourMajorVersion = 4; 6837748cd8SNickeau const CANONICAL = "bootstrap"; 6904fd306cSNickeau /** 7004fd306cSNickeau * Stylesheet and Boostrap should have the same version 7104fd306cSNickeau * This conf is a mix between the version and the stylesheet 7204fd306cSNickeau * 7304fd306cSNickeau * majorVersion.0.0 - stylesheetname 7404fd306cSNickeau */ 7504fd306cSNickeau public const CONF_BOOTSTRAP_VERSION_STYLESHEET = "bootstrapVersionStylesheet"; 7604fd306cSNickeau /** 7704fd306cSNickeau * The separator in {@link Bootstrap::CONF_BOOTSTRAP_VERSION_STYLESHEET} 7804fd306cSNickeau */ 7904fd306cSNickeau public const BOOTSTRAP_VERSION_STYLESHEET_SEPARATOR = " - "; 8004fd306cSNickeau public const DEFAULT_BOOTSTRAP_VERSION_STYLESHEET = "5.0.1" . Bootstrap::BOOTSTRAP_VERSION_STYLESHEET_SEPARATOR . "bootstrap"; 8104fd306cSNickeau 8204fd306cSNickeau /** 8304fd306cSNickeau * @var mixed|string 8404fd306cSNickeau */ 8504fd306cSNickeau private $version; 8604fd306cSNickeau /** 8704fd306cSNickeau * @var string - the stylesheet name 8804fd306cSNickeau */ 8904fd306cSNickeau private $styleSheetName; 9037748cd8SNickeau 9137748cd8SNickeau public static function getDataNamespace() 9237748cd8SNickeau { 9337748cd8SNickeau $dataToggleNamespace = ""; 9437748cd8SNickeau if (self::getBootStrapMajorVersion() == self::BootStrapFiveMajorVersion) { 9537748cd8SNickeau $dataToggleNamespace = "-bs"; 9637748cd8SNickeau } 9737748cd8SNickeau return $dataToggleNamespace; 9837748cd8SNickeau } 9937748cd8SNickeau 10004fd306cSNickeau public function getMajorVersion(): int 10137748cd8SNickeau { 10204fd306cSNickeau $bootstrapMajorVersion = $this->getVersion()[0]; 1034cadd4f8SNickeau try { 10404fd306cSNickeau return DataType::toInteger($bootstrapMajorVersion); 10504fd306cSNickeau } catch (ExceptionBadArgument $e) { 10604fd306cSNickeau LogUtility::internalError("The bootstrap major version ($bootstrapMajorVersion) is not an integer, default taken"); 10704fd306cSNickeau return self::DEFAULT_MAJOR; 1084cadd4f8SNickeau } 10904fd306cSNickeau 11037748cd8SNickeau } 11104fd306cSNickeau 11204fd306cSNickeau /** 11304fd306cSNickeau * Utility function that returns the major version 11404fd306cSNickeau * because this is really common in code 11504fd306cSNickeau * @return int - the major version 11604fd306cSNickeau */ 11704fd306cSNickeau public static function getBootStrapMajorVersion(): int 11804fd306cSNickeau { 11904fd306cSNickeau return Bootstrap::getFromContext()->getMajorVersion(); 12004fd306cSNickeau } 12137748cd8SNickeau 12237748cd8SNickeau 12304fd306cSNickeau public function getVersion(): string 12404fd306cSNickeau { 12504fd306cSNickeau return $this->version; 12604fd306cSNickeau } 12704fd306cSNickeau 12804fd306cSNickeau public static function createFromQualifiedVersion(string $boostrapVersion): Bootstrap 12904fd306cSNickeau { 13004fd306cSNickeau return new Bootstrap($boostrapVersion); 13104fd306cSNickeau } 13204fd306cSNickeau 13304fd306cSNickeau 13404fd306cSNickeau public function getStyleSheetName(): string 13504fd306cSNickeau { 13604fd306cSNickeau return $this->styleSheetName; 13704fd306cSNickeau } 13804fd306cSNickeau 13904fd306cSNickeau /** 14004fd306cSNickeau * 14104fd306cSNickeau * @return array - an array of stylesheet tag 14204fd306cSNickeau */ 14304fd306cSNickeau public static function getStyleSheetMetas(): array 14404fd306cSNickeau { 14504fd306cSNickeau 14604fd306cSNickeau /** 14704fd306cSNickeau * Standard stylesheet 14804fd306cSNickeau */ 14904fd306cSNickeau $stylesheetsFile = WikiPath::createComboResource(':library:bootstrap:bootstrapStylesheet.json'); 15004fd306cSNickeau try { 15104fd306cSNickeau $styleSheets = Json::createFromPath($stylesheetsFile)->toArray(); 15204fd306cSNickeau } catch (ExceptionNotFound|ExceptionBadSyntax $e) { 15304fd306cSNickeau LogUtility::internalError("An error has occurred reading the file ($stylesheetsFile). Error:{$e->getMessage()}", self::CANONICAL); 15404fd306cSNickeau return []; 15504fd306cSNickeau } 15604fd306cSNickeau 15704fd306cSNickeau /** 15804fd306cSNickeau * User defined stylesheet 15904fd306cSNickeau */ 16004fd306cSNickeau $localStyleSheetsFile = WikiPath::createComboResource(':library:bootstrap:bootstrapLocal.json'); 16104fd306cSNickeau try { 16204fd306cSNickeau $localStyleSheets = Json::createFromPath($localStyleSheetsFile)->toArray(); 16304fd306cSNickeau foreach ($localStyleSheets as $bootstrapVersion => &$localStyleSheetData) { 16404fd306cSNickeau $actualStyleSheet = $styleSheets[$bootstrapVersion]; 16504fd306cSNickeau if (isset($actualStyleSheet)) { 16604fd306cSNickeau $styleSheets[$bootstrapVersion] = array_merge($actualStyleSheet, $localStyleSheetData); 16704fd306cSNickeau } else { 16804fd306cSNickeau $styleSheets[$bootstrapVersion] = $localStyleSheetData; 16904fd306cSNickeau } 17004fd306cSNickeau } 17104fd306cSNickeau } catch (ExceptionBadSyntax|ExceptionNotFound $e) { 17204fd306cSNickeau // user file does not exists and that's okay 17304fd306cSNickeau } 17404fd306cSNickeau return $styleSheets; 17504fd306cSNickeau 17604fd306cSNickeau 17704fd306cSNickeau } 17804fd306cSNickeau 17904fd306cSNickeau 18004fd306cSNickeau /** 18104fd306cSNickeau * @return array - A list of all available stylesheets 18204fd306cSNickeau * This function is used to build the configuration as a list of files 18304fd306cSNickeau */ 18404fd306cSNickeau public static function getQualifiedVersions(): array 18504fd306cSNickeau { 18604fd306cSNickeau $cssVersionsMetas = Bootstrap::getStyleSheetMetas(); 18704fd306cSNickeau $listVersionStylesheetMeta = array(); 18804fd306cSNickeau foreach ($cssVersionsMetas as $bootstrapVersion => $cssVersionMeta) { 18904fd306cSNickeau foreach ($cssVersionMeta as $fileName => $values) { 19004fd306cSNickeau $listVersionStylesheetMeta[] = $bootstrapVersion . Bootstrap::BOOTSTRAP_VERSION_STYLESHEET_SEPARATOR . $fileName; 19104fd306cSNickeau } 19204fd306cSNickeau } 19304fd306cSNickeau return $listVersionStylesheetMeta; 19404fd306cSNickeau } 19504fd306cSNickeau 19604fd306cSNickeau 19704fd306cSNickeau /** 19804fd306cSNickeau * @return Bootstrap 19904fd306cSNickeau */ 20004fd306cSNickeau public static function getFromContext(): Bootstrap 20104fd306cSNickeau { 20204fd306cSNickeau $executionContext = ExecutionContext::getActualOrCreateFromEnv(); 20304fd306cSNickeau try { 20404fd306cSNickeau return $executionContext->getRuntimeObject(self::CANONICAL); 20504fd306cSNickeau } catch (ExceptionNotFound $e) { 20604fd306cSNickeau $bootstrapStyleSheetVersion = ExecutionContext::getActualOrCreateFromEnv() 20704fd306cSNickeau ->getConfValue(Bootstrap::CONF_BOOTSTRAP_VERSION_STYLESHEET, Bootstrap::DEFAULT_BOOTSTRAP_VERSION_STYLESHEET); 20804fd306cSNickeau $bootstrap = new Bootstrap($bootstrapStyleSheetVersion); 20904fd306cSNickeau $executionContext->setRuntimeObject(self::CANONICAL, $bootstrap); 21004fd306cSNickeau return $bootstrap; 21104fd306cSNickeau } 21204fd306cSNickeau 21304fd306cSNickeau } 21404fd306cSNickeau 21504fd306cSNickeau 21604fd306cSNickeau /** 21704fd306cSNickeau * @throws ExceptionNotFound 21804fd306cSNickeau */ 21904fd306cSNickeau public function getCssSnippet(): Snippet 22004fd306cSNickeau { 22104fd306cSNickeau if (isset($this->cssSnippet)) { 22204fd306cSNickeau return $this->cssSnippet; 22304fd306cSNickeau } 22404fd306cSNickeau throw new ExceptionNotFound("No css snippet"); 22504fd306cSNickeau } 22604fd306cSNickeau 22704fd306cSNickeau /** 22804fd306cSNickeau * @return Snippet[] the js snippets in order 22904fd306cSNickeau */ 23004fd306cSNickeau public function getJsSnippets(): array 23104fd306cSNickeau { 23204fd306cSNickeau 23304fd306cSNickeau /** 23404fd306cSNickeau * The javascript snippet order is important 23504fd306cSNickeau */ 23604fd306cSNickeau $snippets = []; 23704fd306cSNickeau try { 23804fd306cSNickeau $snippets[] = $this->getJquerySnippet(); 23904fd306cSNickeau } catch (ExceptionNotFound $e) { 24004fd306cSNickeau // error already send at build time 24104fd306cSNickeau // or just not present 24204fd306cSNickeau } 24304fd306cSNickeau try { 24404fd306cSNickeau $snippets[] = $this->getPopperSnippet(); 24504fd306cSNickeau } catch (ExceptionNotFound $e) { 24604fd306cSNickeau // error already send at build time 24704fd306cSNickeau } 24804fd306cSNickeau try { 24904fd306cSNickeau $snippets[] = $this->getBootstrapJsSnippet(); 25004fd306cSNickeau } catch (ExceptionNotFound $e) { 25104fd306cSNickeau // error already send at build time 25204fd306cSNickeau } 25304fd306cSNickeau return $snippets; 25404fd306cSNickeau 25504fd306cSNickeau } 25604fd306cSNickeau 25704fd306cSNickeau /** 25804fd306cSNickeau * 25904fd306cSNickeau */ 26004fd306cSNickeau private function build(): void 26104fd306cSNickeau { 26204fd306cSNickeau 26304fd306cSNickeau 26404fd306cSNickeau $version = $this->getVersion(); 26504fd306cSNickeau 26604fd306cSNickeau // Javascript 26704fd306cSNickeau $bootstrapJsonFile = WikiPath::createComboResource(Snippet::LIBRARY_BASE . ":bootstrap:bootstrapJavascript.json"); 26804fd306cSNickeau try { 26904fd306cSNickeau $bootstrapJsonMetas = Json::createFromPath($bootstrapJsonFile)->toArray(); 27004fd306cSNickeau } catch (ExceptionBadSyntax|ExceptionNotFound $e) { 27104fd306cSNickeau // should not happen, no need to advertise it 27204fd306cSNickeau throw new ExceptionRuntimeInternal("Unable to read the file {$bootstrapJsonFile} as json.", self::CANONICAL, 1, $e); 27304fd306cSNickeau } 27404fd306cSNickeau if (!isset($bootstrapJsonMetas[$version])) { 27504fd306cSNickeau throw new ExceptionRuntimeInternal("The bootstrap version ($version) could not be found in the file $bootstrapJsonFile"); 27604fd306cSNickeau } 27704fd306cSNickeau $bootstrapMetas = $bootstrapJsonMetas[$version]; 27804fd306cSNickeau 27904fd306cSNickeau // Css 28004fd306cSNickeau $bootstrapMetas["stylesheet"] = $this->getStyleSheetMeta(); 28104fd306cSNickeau 28204fd306cSNickeau 28304fd306cSNickeau foreach ($bootstrapMetas as $key => $script) { 28404fd306cSNickeau $fileNameWithExtension = $script["file"]; 28504fd306cSNickeau $file = LocalPath::createFromPathString($fileNameWithExtension); 28604fd306cSNickeau 28704fd306cSNickeau $path = WikiPath::createComboResource(":library:bootstrap:$version:$fileNameWithExtension"); 28804fd306cSNickeau $snippet = Snippet::createSnippet($path) 28904fd306cSNickeau ->setComponentId(self::TAG); 290*70bbd7f1Sgerardnico $url = $script["url"] ?? null; 29104fd306cSNickeau if (!empty($url)) { 29204fd306cSNickeau try { 29304fd306cSNickeau $url = Url::createFromString($url); 29404fd306cSNickeau $snippet->setRemoteUrl($url); 29504fd306cSNickeau if (isset($script['integrity'])) { 29604fd306cSNickeau $snippet->setIntegrity($script['integrity']); 29704fd306cSNickeau } 29804fd306cSNickeau } catch (ExceptionBadArgument|ExceptionBadSyntax $e) { 29904fd306cSNickeau LogUtility::internalError("The url ($url) for the bootstrap metadata ($fileNameWithExtension) from the bootstrap dictionary is not valid. Error:{$e->getMessage()}", self::CANONICAL); 30004fd306cSNickeau } 30104fd306cSNickeau } 30204fd306cSNickeau 30304fd306cSNickeau try { 30404fd306cSNickeau $extension = $file->getExtension(); 30504fd306cSNickeau } catch (ExceptionNotFound $e) { 30604fd306cSNickeau LogUtility::internalError("No extension was found on the file metadata ($fileNameWithExtension) from the bootstrap dictionary", self::CANONICAL); 30704fd306cSNickeau continue; 30804fd306cSNickeau } 30904fd306cSNickeau switch ($extension) { 31004fd306cSNickeau case Snippet::EXTENSION_JS: 31104fd306cSNickeau $snippet->setCritical(false); 31204fd306cSNickeau switch ($key) { 31304fd306cSNickeau case "jquery": 31404fd306cSNickeau $this->jquerySnippet = $snippet; 31504fd306cSNickeau break; 31604fd306cSNickeau case "js": 31704fd306cSNickeau $this->jsSnippet = $snippet; 31804fd306cSNickeau break; 31904fd306cSNickeau case "popper": 32004fd306cSNickeau $this->popperSnippet = $snippet; 32104fd306cSNickeau break; 32204fd306cSNickeau default: 32304fd306cSNickeau LogUtility::internalError("The snippet key ($key) is unknown for bootstrap", self::CANONICAL); 32404fd306cSNickeau break; 32504fd306cSNickeau } 32604fd306cSNickeau break; 32704fd306cSNickeau case Snippet::EXTENSION_CSS: 32804fd306cSNickeau switch ($key) { 32904fd306cSNickeau case "stylesheet": 33004fd306cSNickeau $this->cssSnippet = $snippet; 33104fd306cSNickeau break; 33204fd306cSNickeau default: 33304fd306cSNickeau LogUtility::internalError("The snippet key ($key) is unknown for bootstrap"); 33404fd306cSNickeau break; 33504fd306cSNickeau } 33604fd306cSNickeau } 33704fd306cSNickeau } 33804fd306cSNickeau 33904fd306cSNickeau 34004fd306cSNickeau } 34104fd306cSNickeau 34204fd306cSNickeau public function getSnippets(): array 34304fd306cSNickeau { 34404fd306cSNickeau 34504fd306cSNickeau $snippets = []; 34604fd306cSNickeau try { 34704fd306cSNickeau $snippets[] = $this->getCssSnippet(); 34804fd306cSNickeau } catch (ExceptionNotFound $e) { 34904fd306cSNickeau // error already send at build time 35004fd306cSNickeau } 35104fd306cSNickeau 35204fd306cSNickeau /** 35304fd306cSNickeau * The javascript snippet 35404fd306cSNickeau */ 35504fd306cSNickeau return array_merge($snippets, $this->getJsSnippets()); 35604fd306cSNickeau } 35704fd306cSNickeau 35804fd306cSNickeau /** 35904fd306cSNickeau * @throws ExceptionNotFound 36004fd306cSNickeau */ 36104fd306cSNickeau public function getPopperSnippet(): Snippet 36204fd306cSNickeau { 36304fd306cSNickeau if (isset($this->popperSnippet)) { 36404fd306cSNickeau return $this->popperSnippet; 36504fd306cSNickeau } 36604fd306cSNickeau throw new ExceptionNotFound("No popper snippet"); 36704fd306cSNickeau } 36804fd306cSNickeau 36904fd306cSNickeau /** 37004fd306cSNickeau * @throws ExceptionNotFound 37104fd306cSNickeau */ 37204fd306cSNickeau public function getBootstrapJsSnippet(): Snippet 37304fd306cSNickeau { 37404fd306cSNickeau if (isset($this->jsSnippet)) { 37504fd306cSNickeau return $this->jsSnippet; 37604fd306cSNickeau } 37704fd306cSNickeau throw new ExceptionNotFound("No js snippet"); 37804fd306cSNickeau } 37904fd306cSNickeau 38004fd306cSNickeau /** 38104fd306cSNickeau * @throws ExceptionNotFound 38204fd306cSNickeau */ 38304fd306cSNickeau private function getJquerySnippet(): Snippet 38404fd306cSNickeau { 38504fd306cSNickeau if (isset($this->jquerySnippet)) { 38604fd306cSNickeau return $this->jquerySnippet; 38704fd306cSNickeau } 38804fd306cSNickeau throw new ExceptionNotFound("No jquery snippet"); 38904fd306cSNickeau } 39004fd306cSNickeau 39104fd306cSNickeau /** 39204fd306cSNickeau * @return array - the stylesheet meta (file, url, ...) for the version 39304fd306cSNickeau */ 39404fd306cSNickeau private function getStyleSheetMeta(): array 39504fd306cSNickeau { 39604fd306cSNickeau 39704fd306cSNickeau $styleSheets = self::getStyleSheetMetas(); 39804fd306cSNickeau 39904fd306cSNickeau $version = $this->getVersion(); 40004fd306cSNickeau if (!isset($styleSheets[$version])) { 40104fd306cSNickeau LogUtility::internalError("The bootstrap version ($version) could not be found"); 40204fd306cSNickeau return []; 40304fd306cSNickeau } 40404fd306cSNickeau $styleSheetsForVersion = $styleSheets[$version]; 40504fd306cSNickeau 40604fd306cSNickeau $styleSheetName = $this->getStyleSheetName(); 40704fd306cSNickeau if (!isset($styleSheetsForVersion[$styleSheetName])) { 40804fd306cSNickeau LogUtility::internalError("The bootstrap stylesheet ($styleSheetName) could not be found for the version ($version) in the distribution or custom configuration files"); 40904fd306cSNickeau return []; 41004fd306cSNickeau } 41104fd306cSNickeau $styleSheetForVersionAndName = $styleSheetsForVersion[$styleSheetName]; 41204fd306cSNickeau 41304fd306cSNickeau /** 41404fd306cSNickeau * Select Rtl or Ltr 41504fd306cSNickeau * Stylesheet name may have another level 41604fd306cSNickeau * with direction property of the language 41704fd306cSNickeau * 41804fd306cSNickeau * Bootstrap needs another stylesheet 41904fd306cSNickeau * See https://getbootstrap.com/docs/5.0/getting-started/rtl/ 42004fd306cSNickeau */ 42104fd306cSNickeau try { 42204fd306cSNickeau $direction = Lang::createFromRequestedMarkup()->getDirection(); 42304fd306cSNickeau } catch (ExceptionNotFound $e) { 42404fd306cSNickeau $direction = Site::getLangObject()->getDirection(); 42504fd306cSNickeau } 42604fd306cSNickeau if (isset($styleSheetForVersionAndName[$direction])) { 42704fd306cSNickeau return $styleSheetForVersionAndName[$direction]; 42804fd306cSNickeau } 42904fd306cSNickeau if (!isset($styleSheetForVersionAndName['file'])) { 43004fd306cSNickeau LogUtility::internalError('The file stylesheet attribute is unknown (' . DataType::toString($styleSheetForVersionAndName) . ')'); 43104fd306cSNickeau } 43204fd306cSNickeau return $styleSheetForVersionAndName; 43337748cd8SNickeau } 43437748cd8SNickeau} 435