1 <?php
2 
3 namespace ComboStrap;
4 
5 
6 use ComboStrap\Meta\Field\PageTemplateName;
7 use dokuwiki\Extension\PluginTrait;
8 use syntax_plugin_combo_headingwiki;
9 
10 class 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