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