xref: /plugin/combo/ComboStrap/SiteConfig.php (revision 94bd64624391aac031cc0626cae58a5718ca10d8)
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 = $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