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