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