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