1<?php 2 3namespace ComboStrap; 4 5 6use ComboStrap\Meta\Field\PageTemplateName; 7use dokuwiki\Extension\PluginTrait; 8 9class SiteConfig 10{ 11 const LOG_EXCEPTION_LEVEL = 'log-exception-level'; 12 13 /** 14 * A configuration to enable the theme/template system 15 */ 16 public const CONF_ENABLE_THEME_SYSTEM = "combo-conf-001"; 17 public const CONF_ENABLE_THEME_SYSTEM_DEFAULT = 1; 18 19 /** 20 * The default font-size for the pages 21 */ 22 const REM_CONF = "combo-conf-002"; 23 const REM_CANONICAL = "rfs"; 24 25 /** 26 * The maximum size to be embedded 27 * Above this size limit they are fetched 28 * 29 * 2kb is too small for icon. 30 * For instance, the et:twitter is 2,600b 31 */ 32 public const HTML_MAX_KB_SIZE_FOR_INLINE_ELEMENT = "combo-conf-003"; 33 public const HTML_MAX_KB_SIZE_FOR_INLINE_ELEMENT_DEFAULT = 4; 34 /** 35 * Private configuration used in test 36 * When set to true, all javascript snippet will be inlined 37 */ 38 public const HTML_ALWAYS_INLINE_LOCAL_JAVASCRIPT = "combo-conf-004"; 39 const CANONICAL = "site-config"; 40 const GLOBAL_SCOPE = null; 41 /** 42 * The default name 43 */ 44 public const CONF_DEFAULT_INDEX_NAME = "start"; 45 46 47 /** 48 * @var WikiPath the {@link self::getContextPath()} when no context could be determined 49 */ 50 private WikiPath $defaultContextPath; 51 52 /** 53 * @var array - the configuration value to restore 54 * 55 * Note we can't capture the whole global $conf 56 * because the configuration are loaded at runtime via {@link PluginTrait::loadConfig()} 57 * 58 * Meaning that the configuration environment at the start is not fully loaded 59 * and does not represent the environment totally 60 * 61 * We capture then the change and restore them at the end 62 */ 63 private array $configurationValuesToRestore = []; 64 private ExecutionContext $executionContext; 65 private array $interWikis; 66 67 /** 68 * @param ExecutionContext $executionContext 69 */ 70 public function __construct(ExecutionContext $executionContext) 71 { 72 $this->executionContext = $executionContext; 73 } 74 75 /** 76 * TODO: Default: Note that the config of plugin are loaded 77 * via {@link PluginTrait::loadConfig()} 78 * when {@link PluginTrait::getConf()} is used 79 * Therefore whenever possible, for now {@link PluginTrait::getConf()} 80 * should be used otherwise, there is no default 81 * Or best, the default should be also in the code 82 * 83 */ 84 public static function getConfValue($confName, $defaultValue = null, ?string $namespace = PluginUtility::PLUGIN_BASE_NAME) 85 { 86 global $conf; 87 if ($namespace !== null) { 88 89 $value = $conf['plugin'][$namespace][$confName]; 90 } else { 91 $value = $conf[$confName]; 92 } 93 if(DataType::isBoolean($value)){ 94 /** 95 * Because the next line 96 * `trim($value) === ""` 97 * is true for a false value 98 */ 99 return $value; 100 } 101 if ($value === null || trim($value) === "") { 102 return $defaultValue; 103 } 104 return $value; 105 } 106 107 108 /** 109 * @param string $key 110 * @param $value 111 * @param string|null $pluginNamespace - null for the global namespace 112 * @return $this 113 */ 114 public function setConf(string $key, $value, ?string $pluginNamespace = PluginUtility::PLUGIN_BASE_NAME): SiteConfig 115 { 116 /** 117 * Environment within dokuwiki is a global variable 118 * 119 * We set it the global variable 120 * 121 * but we capture it {@link ExecutionContext::$capturedConf} 122 * to restore it when the execution context os {@link ExecutionContext::close()} 123 */ 124 $globalKey = "$pluginNamespace:$key"; 125 if (!isset($this->configurationValuesToRestore[$globalKey])) { 126 $oldValue = self::getConfValue($key, $value, $pluginNamespace); 127 $this->configurationValuesToRestore[$globalKey] = $oldValue; 128 } 129 Site::setConf($key, $value, $pluginNamespace); 130 return $this; 131 } 132 133 /** 134 * Restore the configuration 135 * as it was when php started 136 * @return void 137 */ 138 public function restoreConfigState() 139 { 140 141 foreach ($this->configurationValuesToRestore as $guid => $value) { 142 [$plugin, $confKey] = explode(":", $guid); 143 Site::setConf($confKey, $value, $plugin); 144 } 145 } 146 147 public function setDisableThemeSystem(): SiteConfig 148 { 149 $this->setConf(self::CONF_ENABLE_THEME_SYSTEM, 0); 150 return $this; 151 } 152 153 public function isThemeSystemEnabled(): bool 154 { 155 return $this->getBooleanValue(self::CONF_ENABLE_THEME_SYSTEM, self::CONF_ENABLE_THEME_SYSTEM_DEFAULT); 156 } 157 158 public function getValue(string $key, ?string $default = null, ?string $scope = PluginUtility::PLUGIN_BASE_NAME) 159 { 160 return self::getConfValue($key, $default, $scope); 161 } 162 163 /** 164 * @param string $key 165 * @param int $default - the default value (1=true,0=false in the dokuwiki config system) 166 * @return bool 167 */ 168 public function getBooleanValue(string $key, int $default): bool 169 { 170 $value = $this->getValue($key, $default); 171 /** 172 * Boolean in config is normally the value 1 173 */ 174 return DataType::toBoolean($value); 175 } 176 177 public function setCacheXhtmlOn() 178 { 179 // ensure the value is not -1, which disables caching 180 // https://www.dokuwiki.org/config:cachetime 181 182 $this->setConf('cachetime', 60 * 60, null); 183 return $this; 184 } 185 186 public function setConsoleOn(): SiteConfig 187 { 188 $this->setConf('console', 1); 189 return $this; 190 } 191 192 public function isConsoleOn(): bool 193 { 194 return $this->getBooleanValue('console', 0); 195 } 196 197 public function getExecutionContext(): ExecutionContext 198 { 199 return $this->executionContext; 200 } 201 202 public function setConsoleOff(): SiteConfig 203 { 204 $this->setConf('console', 0); 205 return $this; 206 } 207 208 public function setLogExceptionToError(): SiteConfig 209 { 210 $this->setLogExceptionLevel(LogUtility::LVL_MSG_ERROR); 211 return $this; 212 } 213 214 public function setDisableLogException(): SiteConfig 215 { 216 $this->setLogExceptionLevel(LogUtility::LVL_MSG_ABOVE_ERROR); 217 return $this; 218 } 219 220 public function setLogExceptionLevel(int $level): SiteConfig 221 { 222 $this->setConf(self::LOG_EXCEPTION_LEVEL, $level); 223 return $this; 224 } 225 226 public function getLogExceptionLevel(): int 227 { 228 return $this->getValue(self::LOG_EXCEPTION_LEVEL, LogUtility::DEFAULT_THROW_LEVEL); 229 } 230 231 /** 232 * @throws ExceptionNotFound 233 */ 234 public function getRemFontSize(): int 235 { 236 237 $value = $this->getValue(self::REM_CONF); 238 if ($value === null) { 239 throw new ExceptionNotFound("No rem sized defined"); 240 } 241 try { 242 return DataType::toInteger($value); 243 } catch (ExceptionCompile $e) { 244 $message = "The rem configuration value ($value) is not a integer. Error: {$e->getMessage()}"; 245 LogUtility::msg($message); 246 throw new ExceptionNotFound($message); 247 } 248 249 } 250 251 public function setDefaultContextPath(WikiPath $contextPath) 252 { 253 $this->defaultContextPath = $contextPath; 254 if (FileSystems::isDirectory($this->defaultContextPath)) { 255 /** 256 * Not a directory. 257 * 258 * If the link or path is the empty path, the path is not the directory 259 * but the actual markup 260 */ 261 throw new ExceptionRuntimeInternal("The path ($contextPath) should not be a namespace path"); 262 } 263 return $this; 264 } 265 266 /** 267 * @return WikiPath - the default context path is if not set the root page 268 */ 269 public function getDefaultContextPath(): WikiPath 270 { 271 if (isset($this->defaultContextPath)) { 272 return $this->defaultContextPath; 273 } 274 // in a admin or dynamic rendering 275 // dokuwiki may have set a $ID 276 global $ID; 277 if (isset($ID)) { 278 return WikiPath::createMarkupPathFromId($ID); 279 } 280 return WikiPath::createRootNamespacePathOnMarkupDrive()->resolve(Site::getIndexPageName() . "." . WikiPath::MARKUP_DEFAULT_TXT_EXTENSION); 281 } 282 283 public function getHtmlMaxInlineResourceSize() 284 { 285 try { 286 return DataType::toInteger($this->getValue(SiteConfig::HTML_MAX_KB_SIZE_FOR_INLINE_ELEMENT, self::HTML_MAX_KB_SIZE_FOR_INLINE_ELEMENT_DEFAULT)) * 1024; 287 } catch (ExceptionBadArgument $e) { 288 LogUtility::internalError("Max in line size error.", self::CANONICAL, $e); 289 return self::HTML_MAX_KB_SIZE_FOR_INLINE_ELEMENT_DEFAULT * 1024; 290 } 291 } 292 293 public function setHtmlMaxInlineResourceSize(int $kbSize): SiteConfig 294 { 295 $this->setConf(SiteConfig::HTML_MAX_KB_SIZE_FOR_INLINE_ELEMENT, $kbSize); 296 return $this; 297 } 298 299 public function setDisableHeadingSectionEditing(): SiteConfig 300 { 301 $this->setConf('maxseclevel', 0, null); 302 return $this; 303 } 304 305 public function setHtmlEnableAlwaysInlineLocalJavascript(): SiteConfig 306 { 307 $this->setConf(self::HTML_ALWAYS_INLINE_LOCAL_JAVASCRIPT, 1); 308 return $this; 309 } 310 311 public function setHtmlDisableAlwaysInlineLocalJavascript(): SiteConfig 312 { 313 $this->setConf(self::HTML_ALWAYS_INLINE_LOCAL_JAVASCRIPT, 0); 314 return $this; 315 } 316 317 public function isLocalJavascriptAlwaysInlined(): bool 318 { 319 return $this->getBooleanValue(self::HTML_ALWAYS_INLINE_LOCAL_JAVASCRIPT, 0); 320 } 321 322 323 public function disableLazyLoad(): SiteConfig 324 { 325 return $this->setConf(SvgImageLink::CONF_LAZY_LOAD_ENABLE, 0) 326 ->setConf(LazyLoad::CONF_RASTER_ENABLE, 0); 327 328 } 329 330 public function setUseHeadingAsTitle(): SiteConfig 331 { 332 return $this->setConf('useheading', 1, self::GLOBAL_SCOPE); 333 } 334 335 public function setEnableSectionEditing(): SiteConfig 336 { 337 return $this->setConf('maxseclevel', 999, self::GLOBAL_SCOPE); 338 } 339 340 public function isSectionEditingEnabled(): bool 341 { 342 return $this->getTocMaxLevel() > 0; 343 } 344 345 public function getTocMaxLevel(): int 346 { 347 $value = $this->getValue('maxseclevel', null, self::GLOBAL_SCOPE); 348 try { 349 return DataType::toInteger($value); 350 } catch (ExceptionBadArgument $e) { 351 LogUtility::internalError("Unable to the the maxseclevel as integer. Error: {$e->getMessage()}", Toc::CANONICAL); 352 return 0; 353 } 354 } 355 356 public function setTocMinHeading(int $int): SiteConfig 357 { 358 return $this->setConf('tocminheads', $int, self::GLOBAL_SCOPE); 359 } 360 361 public function getIndexPageName() 362 { 363 return $this->getValue("start", self::CONF_DEFAULT_INDEX_NAME, self::GLOBAL_SCOPE); 364 } 365 366 public function getAuthorizedUrlSchemes(): ?array 367 { 368 if (isset($this->authorizedUrlSchemes)) { 369 return $this->authorizedUrlSchemes; 370 } 371 $this->authorizedUrlSchemes = getSchemes(); 372 $this->authorizedUrlSchemes[] = "whatsapp"; 373 $this->authorizedUrlSchemes[] = "mailto"; 374 return $this->authorizedUrlSchemes; 375 } 376 377 public function getInterWikis(): array 378 { 379 $this->loadInterWikiIfNeeded(); 380 return $this->interWikis; 381 } 382 383 public function addInterWiki(string $name, string $value): SiteConfig 384 { 385 $this->loadInterWikiIfNeeded(); 386 $this->interWikis[$name] = $value; 387 return $this; 388 } 389 390 private function loadInterWikiIfNeeded(): void 391 { 392 if (isset($this->interWikis)) { 393 return; 394 } 395 $this->interWikis = getInterwiki(); 396 } 397 398 public function setTocTopLevel(int $int): SiteConfig 399 { 400 return $this->setConf('toptoclevel', $int, self::GLOBAL_SCOPE); 401 } 402 403 public function getMetaDataDirectory(): LocalPath 404 { 405 $metadataDirectory = $this->getValue('metadir', null, self::GLOBAL_SCOPE); 406 if ($metadataDirectory === null) { 407 throw new ExceptionRuntime("The meta directory configuration value ('metadir') is null"); 408 } 409 return LocalPath::createFromPathString($metadataDirectory); 410 } 411 412 public function setCanonicalUrlType(string $value): SiteConfig 413 { 414 return $this->setConf(PageUrlType::CONF_CANONICAL_URL_TYPE, $value); 415 } 416 417 public function setEnableTheming(): SiteConfig 418 { 419 $this->setConf(SiteConfig::CONF_ENABLE_THEME_SYSTEM, 1); 420 return $this; 421 } 422 423 public function getTheme(): string 424 { 425 return $this->getValue(TemplateEngine::CONF_THEME, TemplateEngine::CONF_THEME_DEFAULT); 426 } 427 428 /** 429 * Note: in test to speed the test execution, 430 * the default is set to {@link PageTemplateName::BLANK_TEMPLATE_VALUE} 431 */ 432 public function getDefaultLayoutName() 433 { 434 return $this->getValue(PageTemplateName::CONF_DEFAULT_NAME, PageTemplateName::HOLY_TEMPLATE_VALUE); 435 } 436 437 public function setEnableThemeSystem(): SiteConfig 438 { 439 // this is the default but yeah 440 $this->setConf(self::CONF_ENABLE_THEME_SYSTEM, 1); 441 return $this; 442 } 443 444 /** 445 * DokuRewrite 446 * `doku.php/id/...` 447 * https://www.dokuwiki.org/config:userewrite 448 * @return $this 449 */ 450 public function setUrlRewriteToDoku(): SiteConfig 451 { 452 $this->setConf('userewrite', '2', self::GLOBAL_SCOPE); 453 return $this; 454 } 455 456 /** 457 * Web server rewrite (Apache rewrite (htaccess), Nginx) 458 * https://www.dokuwiki.org/config:userewrite 459 * @return $this 460 */ 461 public function setUrlRewriteToWebServer(): SiteConfig 462 { 463 $this->setConf('userewrite', '1', self::GLOBAL_SCOPE); 464 return $this; 465 } 466 467 public function getRemFontSizeOrDefault(): int 468 { 469 try { 470 return $this->getRemFontSize(); 471 } catch (ExceptionNotFound $e) { 472 return 16; 473 } 474 } 475 476 public function getDataDirectory(): LocalPath 477 { 478 global $conf; 479 $dataDirectory = $conf['savedir']; 480 if ($dataDirectory === null) { 481 throw new ExceptionRuntime("The data directory ($dataDirectory) is null"); 482 } 483 return LocalPath::createFromPathString($dataDirectory); 484 } 485 486 public function setTheme(string $themeName): SiteConfig 487 { 488 $this->setConf(TemplateEngine::CONF_THEME, $themeName); 489 return $this; 490 } 491 492 public function getPageHeaderSlotName() 493 { 494 return $this->getValue(TemplateSlot::CONF_PAGE_HEADER_NAME, TemplateSlot::CONF_PAGE_HEADER_NAME_DEFAULT); 495 } 496 497 public function setConfDokuWiki(string $key, $value): SiteConfig 498 { 499 return $this->setConf($key,$value, self::GLOBAL_SCOPE); 500 } 501 502 /** 503 * @throws ExceptionNotFound 504 */ 505 public function getPrimaryColor(): ColorRgb 506 { 507 $value = Site::getPrimaryColorValue(); 508 if ( 509 $value === null || 510 (trim($value) === "")) { 511 throw new ExceptionNotFound(); 512 } 513 try { 514 return ColorRgb::createFromString($value); 515 } catch (ExceptionCompile $e) { 516 LogUtility::msg("The primary color value configuration ($value) is not valid. Error: {$e->getMessage()}"); 517 throw new ExceptionNotFound(); 518 } 519 } 520 521 public function setPrimaryColor(string $primaryColorValue): SiteConfig 522 { 523 self::setConf(BrandingColors::PRIMARY_COLOR_CONF, $primaryColorValue); 524 return $this; 525 } 526 527 public function getPrimaryColorOrDefault(string $defaultColor): ColorRgb 528 { 529 try { 530 return $this->getPrimaryColor(); 531 } catch (ExceptionNotFound $e) { 532 try { 533 return ColorRgb::createFromString($defaultColor); 534 } catch (ExceptionBadArgument $e) { 535 LogUtility::internalError("The default color $defaultColor is not a color string.",self::CANONICAL, $e); 536 return ColorRgb::getDefaultPrimary(); 537 } 538 } 539 } 540 541 public function isBrandingColorInheritanceEnabled(): bool 542 { 543 return $this->getValue(BrandingColors::BRANDING_COLOR_INHERITANCE_ENABLE_CONF, BrandingColors::BRANDING_COLOR_INHERITANCE_ENABLE_CONF_DEFAULT) === 1; 544 } 545 546 /** 547 * @throws ExceptionNotFound 548 */ 549 public function getSecondaryColor(): ColorRgb 550 { 551 $secondaryColor = Site::getSecondaryColor(); 552 if($secondaryColor===null){ 553 throw new ExceptionNotFound(); 554 } 555 return $secondaryColor; 556 } 557 558 public function isXhtmlCacheOn(): bool 559 { 560 global $conf; 561 return $conf['cachetime'] !== -1; 562 } 563 564 565} 566