1<?php
2/**
3 * Copyright (c) 2021. ComboStrap, Inc. and its affiliates. All Rights Reserved.
4 *
5 * This source code is licensed under the GPL license found in the
6 * COPYING  file in the root directory of this source tree.
7 *
8 * @license  GPL 3 (https://www.gnu.org/licenses/gpl-3.0.en.html)
9 * @author   ComboStrap <support@combostrap.com>
10 *
11 */
12
13namespace ComboStrap;
14
15
16use Exception;
17
18class Site
19{
20
21    const STRAP_TEMPLATE_NAME = "strap";
22
23    const SVG_LOGO_IDS = array(
24        ':wiki:logo.svg',
25        ':logo.svg'
26    );
27
28    const PNG_LOGO_IDS = array(
29        ':logo.png',
30        ':wiki:logo.png',
31        ':favicon-32×32.png',
32        ':favicon-16×16.png',
33        ':apple-touch-icon.png',
34        ':android-chrome-192x192.png'
35    );
36
37
38    /**
39     * @return Image[]
40     */
41    public static function getLogoImages(): array
42    {
43        $logosPaths = PluginUtility::mergeAttributes(self::PNG_LOGO_IDS, self::SVG_LOGO_IDS);
44        $logos = [];
45        foreach ($logosPaths as $logoPath) {
46            $dokuPath = DokuPath::createMediaPathFromId($logoPath);
47            if (FileSystems::exists($dokuPath)) {
48                try {
49                    $logos[] = Image::createImageFromPath($dokuPath);
50                } catch (Exception $e) {
51                    // The image is not valid
52                    LogUtility::msg("The logo ($logoPath) is not a valid image. {$e->getMessage()}");
53                }
54            }
55        }
56        return $logos;
57    }
58
59
60    /**
61     * @return string|null
62     */
63    public static function getLogoUrlAsSvg(): ?string
64    {
65
66
67        $url = null;
68        foreach (self::SVG_LOGO_IDS as $svgLogo) {
69
70            $svgLogoFN = mediaFN($svgLogo);
71            if (file_exists($svgLogoFN)) {
72                $url = ml($svgLogo, '', true, '', true);
73                break;
74            }
75        }
76        return $url;
77    }
78
79    public static function getLogoAsSvgImage(): ?ImageSvg
80    {
81        foreach (self::SVG_LOGO_IDS as $svgLogo) {
82
83            try {
84                $image = ImageSvg::createImageFromId($svgLogo);
85            } catch (ExceptionCombo $e) {
86                LogUtility::msg("The svg ($svgLogo) returns an error. {$e->getMessage()}");
87                continue;
88            }
89            if ($image->exists()) {
90                return $image;
91            }
92        }
93        return null;
94    }
95
96    public static function getLogoUrlAsPng()
97    {
98
99        $url = null;
100        foreach (self::PNG_LOGO_IDS as $svgLogo) {
101
102            $svgLogoFN = mediaFN($svgLogo);
103
104            if (file_exists($svgLogoFN)) {
105                $url = ml($svgLogo, '', true, '', true);
106                break;
107            };
108        }
109        return $url;
110    }
111
112    /**
113     * @return mixed
114     * @deprecated use {@link Site::getName()} instead
115     * https://www.dokuwiki.org/config:title
116     */
117    public static function getTitle()
118    {
119        global $conf;
120        return $conf['title'];
121    }
122
123    /**
124     * https://www.dokuwiki.org/config:title
125     */
126    public static function setName($name)
127    {
128        global $conf;
129        $conf['title'] = $name;
130    }
131
132    /**
133     * @param string $sep - the separator - generally ("-") but not always
134     * @return string
135     *
136     * Locale always canonicalizes to upper case.
137     */
138    public static function getLocale(string $sep = "-"): ?string
139    {
140
141        $locale = null;
142
143        $lang = self::getLang();
144        if ($lang != null) {
145            $country = self::getLanguageRegion();
146            if ($country != null) {
147                $locale = strtolower($lang) . $sep . strtoupper($country);
148            }
149        }
150
151        return $locale;
152    }
153
154    /**
155     *
156     * ISO 3166 alpha-2 country code
157     *
158     */
159    public static function getLanguageRegion()
160    {
161        $region = PluginUtility::getConfValue(Region::CONF_SITE_LANGUAGE_REGION);
162        if (!empty($region)) {
163            return $region;
164        } else {
165
166            if (extension_loaded("intl")) {
167                $locale = locale_get_default();
168                $localeParts = preg_split("/_/", $locale, 2);
169                if (sizeof($localeParts) === 2) {
170                    return $localeParts[1];
171                }
172            }
173
174            return null;
175        }
176
177    }
178
179    /**
180     * @return mixed|null
181     * Wrapper around  https://www.dokuwiki.org/config:lang
182     */
183    public static function getLang()
184    {
185
186        global $conf;
187        $lang = $conf['lang'];
188        return ($lang ?: null);
189    }
190
191    public static function getBaseUrl(): string
192    {
193
194        /**
195         * In a {@link PluginUtility::isDevOrTest()} dev environment,
196         * don't set the
197         * https://www.dokuwiki.org/config:baseurl
198         * to be able to test the metadata / social integration
199         * via a tunnel
200         *
201         * Same as {@link getBaseURL()} ??
202         * Same as {@link wl()} without nothing
203         */
204
205        return DOKU_URL;
206
207    }
208
209    public static function getTag()
210    {
211        global $conf;
212        $tag = $conf['tag'];
213        return ($tag ? $tag : null);
214    }
215
216    /**
217     * @return string - the name of the sidebar page
218     */
219    public static function getSidebarName()
220    {
221        global $conf;
222        return $conf["sidebar"];
223    }
224
225    public static function setTemplate($template)
226    {
227        global $conf;
228        $conf['template'] = $template;
229    }
230
231    public static function setCacheXhtmlOn()
232    {
233        // ensure the value is not -1, which disables caching
234        // https://www.dokuwiki.org/config:cachetime
235        global $conf;
236        $conf['cachetime'] = 60 * 60;
237    }
238
239    public static function debugIsOn()
240    {
241        global $conf;
242        return $conf['allowdebug'];
243    }
244
245    public static function setTemplateToStrap()
246    {
247        global $conf;
248        $conf['template'] = self::STRAP_TEMPLATE_NAME;
249    }
250
251    public static function setTemplateToDefault()
252    {
253        global $conf;
254        $conf['template'] = 'dokuwiki';
255    }
256
257    public static function setCacheDefault()
258    {
259        // The value is -1, which disables caching
260        // https://www.dokuwiki.org/config:cachetime
261        global $conf;
262        $conf['cachetime'] = -1;
263    }
264
265    public static function useHeadingAsTitle()
266    {
267        // https://www.dokuwiki.org/config:useheading
268        global $conf;
269        $conf['useheading'] = 1;
270    }
271
272    public static function useHeadingDefault()
273    {
274        // https://www.dokuwiki.org/config:useheading
275        global $conf;
276        $conf['useheading'] = 0;
277    }
278
279    public static function getTemplate()
280    {
281        global $conf;
282        return $conf['template'];
283
284    }
285
286    public static function isStrapTemplate()
287    {
288        global $conf;
289        return $conf['template'] == self::STRAP_TEMPLATE_NAME;
290    }
291
292    public static function getAjaxUrl(): string
293    {
294        return self::getBaseUrl() . "lib/exe/ajax.php";
295    }
296
297    public static function getPageDirectory()
298    {
299        global $conf;
300        /**
301         * Data dir is the pages dir (savedir is the data dir)
302         */
303        $pageDirectory = $conf['datadir'];
304        if ($pageDirectory === null) {
305            throw new ExceptionComboRuntime("The page directory ($pageDirectory) is null");
306        }
307        return LocalPath::createFromPath($pageDirectory);
308    }
309
310    public static function disableHeadingSectionEditing()
311    {
312        global $conf;
313        $conf['maxseclevel'] = 0;
314    }
315
316    public static function setBreadCrumbOn()
317    {
318        global $conf;
319        $conf['youarehere'] = 1;
320    }
321
322    public static function isHtmlRenderCacheOn(): bool
323    {
324        global $conf;
325        return $conf['cachetime'] !== -1;
326    }
327
328    public static function getDataDirectory(): LocalPath
329    {
330        global $conf;
331        $dataDirectory = $conf['savedir'];
332        if ($dataDirectory === null) {
333            throw new ExceptionComboRuntime("The data directory ($dataDirectory) is null");
334        }
335        return LocalPath::createFromPath($dataDirectory);
336    }
337
338    public static function isLowQualityProtectionEnable(): bool
339    {
340        return PluginUtility::getConfValue(LowQualityPage::CONF_LOW_QUALITY_PAGE_PROTECTION_ENABLE) === 1;
341    }
342
343    public static function getHomePageName()
344    {
345        global $conf;
346        return $conf["start"];
347    }
348
349    /**
350     * @return mixed - Application / Website name
351     */
352    public static function getName()
353    {
354        return self::getTitle();
355    }
356
357    public static function getTagLine()
358    {
359        global $conf;
360        return $conf['tagline'];
361    }
362
363    /**
364     * @return int|null
365     */
366    public static function getCacheTime(): ?int
367    {
368        global $conf;
369        $cacheTime = $conf['cachetime'];
370        if ($cacheTime === null) {
371            return null;
372        }
373        if (is_numeric($cacheTime)) {
374            return intval($cacheTime);
375        }
376        return null;
377    }
378
379    /**
380     * Absolute vs Relative URL
381     * https://www.dokuwiki.org/config:canonical
382     */
383    public static function shouldUrlBeAbsolute(): bool
384    {
385        global $conf;
386        $value = $conf['canonical'];
387        if ($value === 1) {
388            return true;
389        }
390        return false;
391    }
392
393    /**
394     * @param string $description
395     * Same as {@link Site::setDescription()}
396     */
397    public static function setTagLine(string $description)
398    {
399        global $conf;
400        $conf['tagline'] = $description;
401    }
402
403    /**
404     * @param string $description
405     *
406     */
407    public static function setDescription(string $description)
408    {
409        self::setTagLine($description);
410    }
411
412    public static function setPrimaryColor(string $primaryColorValue)
413    {
414        PluginUtility::setConf(ColorRgb::PRIMARY_COLOR_CONF, $primaryColorValue);
415    }
416
417    public static function getPrimaryColor($default = null): ?ColorRgb
418    {
419        $value = self::getPrimaryColorValue($default);
420        if (
421            $value === null ||
422            (trim($value) === "")) {
423            return null;
424        }
425        try {
426            return ColorRgb::createFromString($value);
427        } catch
428        (ExceptionCombo $e) {
429            LogUtility::msg("The primary color value configuration ($value) is not valid. Error: {$e->getMessage()}");
430            return null;
431        }
432    }
433
434    public static function getSecondaryColor($default = null): ?ColorRgb
435    {
436        $value = Site::getSecondaryColorValue($default);
437        if ($value === null) {
438            return null;
439        }
440        try {
441            return ColorRgb::createFromString($value);
442        } catch (ExceptionCombo $e) {
443            LogUtility::msg("The secondary color value configuration ($value) is not valid. Error: {$e->getMessage()}");
444            return null;
445        }
446    }
447
448    public static function setSecondaryColor(string $secondaryColorValue)
449    {
450        PluginUtility::setConf(ColorRgb::SECONDARY_COLOR_CONF, $secondaryColorValue);
451    }
452
453    public static function unsetPrimaryColor()
454    {
455        PluginUtility::setConf(ColorRgb::PRIMARY_COLOR_CONF, null);
456    }
457
458
459    public static function isBrandingColorInheritanceEnabled(): bool
460    {
461        return PluginUtility::getConfValue(ColorRgb::BRANDING_COLOR_INHERITANCE_ENABLE_CONF, ColorRgb::BRANDING_COLOR_INHERITANCE_ENABLE_CONF_DEFAULT) === 1;
462    }
463
464    public static function getRem(): int
465    {
466        $defaultRem = 16;
467        if (Site::getTemplate() === self::STRAP_TEMPLATE_NAME) {
468            $loaded = self::loadStrapUtilityTemplateIfPresentAndSameVersion();
469            if ($loaded) {
470                $value = TplUtility::getRem();
471                if ($value === null) {
472                    return $defaultRem;
473                }
474                try {
475                    return DataType::toInteger($value);
476                } catch (ExceptionCombo $e) {
477                    LogUtility::msg("The rem configuration value ($value) is not a integer. Error: {$e->getMessage()}");
478                }
479            }
480        }
481        return $defaultRem;
482    }
483
484    public static function enableBrandingColorInheritance()
485    {
486        PluginUtility::setConf(ColorRgb::BRANDING_COLOR_INHERITANCE_ENABLE_CONF, 1);
487    }
488
489    public static function setBrandingColorInheritanceToDefault()
490    {
491        PluginUtility::setConf(ColorRgb::BRANDING_COLOR_INHERITANCE_ENABLE_CONF, ColorRgb::BRANDING_COLOR_INHERITANCE_ENABLE_CONF_DEFAULT);
492    }
493
494    public static function getPrimaryColorForText(string $default = null): ?ColorRgb
495    {
496        $primaryColor = self::getPrimaryColor($default);
497        if ($primaryColor === null) {
498            return null;
499        }
500        try {
501            return $primaryColor
502                ->toHsl()
503                ->setSaturation(30)
504                ->setLightness(40)
505                ->toRgb()
506                ->toMinimumContrastRatioAgainstWhite();
507        } catch (ExceptionCombo $e) {
508            LogUtility::msg("Error while calculating the primary text color. {$e->getMessage()}");
509            return null;
510        }
511    }
512
513    /**
514     * More lightness than the text
515     * @return ColorRgb|null
516     */
517    public static function getPrimaryColorTextHover(): ?ColorRgb
518    {
519
520        $primaryColor = self::getPrimaryColor();
521        if ($primaryColor === null) {
522            return null;
523        }
524        try {
525            return $primaryColor
526                ->toHsl()
527                ->setSaturation(88)
528                ->setLightness(53)
529                ->toRgb()
530                ->toMinimumContrastRatioAgainstWhite();
531        } catch (ExceptionCombo $e) {
532            LogUtility::msg("Error while calculating the secondary text color. {$e->getMessage()}");
533            return null;
534        }
535
536    }
537
538
539    public static function getSecondarySlotNames(): array
540    {
541
542        try {
543            return [
544                Site::getSidebarName(),
545                Site::getHeaderSlotPageName(),
546                Site::getFooterSlotPageName(),
547                Site::getMainHeaderSlotName(),
548                Site::getMainFooterSlotName()
549            ];
550        } catch (ExceptionCombo $e) {
551            // We known at least this one
552            return [Site::getSidebarName()];
553        }
554
555
556    }
557
558
559    /**
560     * @throws ExceptionCombo if the strap template is not installed or could not be loaded
561     */
562    public static function getMainHeaderSlotName(): ?string
563    {
564        self::loadStrapUtilityTemplateIfPresentAndSameVersion();
565        return TplUtility::getMainHeaderSlotName();
566    }
567
568    /**
569     * Strap is loaded only if this is the same version
570     * to avoid function, class, or members that does not exist
571     * @throws ExceptionCombo if strap template utility class could not be loaded
572     */
573    public static function loadStrapUtilityTemplateIfPresentAndSameVersion(): void
574    {
575
576        if (class_exists("ComboStrap\TplUtility")) {
577            return;
578        }
579
580        $templateUtilityFile = __DIR__ . '/../../../tpl/strap/class/TplUtility.php';
581        if (file_exists($templateUtilityFile)) {
582            /**
583             * Check the version
584             */
585            $templateInfo = confToHash(__DIR__ . '/../../../tpl/strap/template.info.txt');
586            $templateVersion = $templateInfo['version'];
587            $comboVersion = PluginUtility::$INFO_PLUGIN['version'];
588            if ($templateVersion != $comboVersion) {
589                $strapName = "Strap";
590                $comboName = "Combo";
591                $strapLink = "<a href=\"https://www.dokuwiki.org/template:strap\">$strapName</a>";
592                $comboLink = "<a href=\"https://www.dokuwiki.org/plugin:combo\">$comboName</a>";
593                if ($comboVersion > $templateVersion) {
594                    $upgradeTarget = $strapName;
595                } else {
596                    $upgradeTarget = $comboName;
597                }
598                $upgradeLink = "<a href=\"" . wl() . "&do=admin&page=extension" . "\">upgrade <b>$upgradeTarget</b> via the extension manager</a>";
599                $message = "You should $upgradeLink to the latest version to get a fully functional experience. The version of $comboLink is ($comboVersion) while the version of $strapLink is ($templateVersion).";
600                LogUtility::msg($message);
601                throw new ExceptionCombo($message);
602            } else {
603                /** @noinspection PhpIncludeInspection */
604                require_once($templateUtilityFile);
605
606            }
607        }
608
609        if (Site::getTemplate() !== self::STRAP_TEMPLATE_NAME) {
610            $message = "The strap template is not installed";
611        } else {
612            $message = "The file ($templateUtilityFile) was not found";
613        }
614        throw new ExceptionCombo($message);
615
616    }
617
618    /**
619     * @throws ExceptionCombo
620     */
621    public static function getSideKickSlotPageName()
622    {
623
624        Site::loadStrapUtilityTemplateIfPresentAndSameVersion();
625        return TplUtility::getSideKickSlotPageName();
626
627    }
628
629    /**
630     * @throws ExceptionCombo
631     */
632    public static function getFooterSlotPageName()
633    {
634        Site::loadStrapUtilityTemplateIfPresentAndSameVersion();
635        return TplUtility::getFooterSlotPageName();
636    }
637
638    /**
639     * @throws ExceptionCombo
640     */
641    public static function getHeaderSlotPageName()
642    {
643        Site::loadStrapUtilityTemplateIfPresentAndSameVersion();
644        return TplUtility::getHeaderSlotPageName();
645    }
646
647    /**
648     * @throws ExceptionCombo
649     */
650    public static function setConfStrapTemplate($name, $value)
651    {
652        Site::loadStrapUtilityTemplateIfPresentAndSameVersion();
653        TplUtility::setConf($name, $value);
654
655    }
656
657    /**
658     * @throws ExceptionCombo
659     */
660    public static function getMainFooterSlotName(): string
661    {
662        self::loadStrapUtilityTemplateIfPresentAndSameVersion();
663        return TplUtility::getMainFooterSlotName();
664    }
665
666    public static function getPrimaryColorValue($default = null)
667    {
668        $value = PluginUtility::getConfValue(ColorRgb::PRIMARY_COLOR_CONF, $default);
669        if ($value !== null && trim($value) !== "") {
670            return $value;
671        }
672        if (PluginUtility::isTest()) {
673            // too much trouble
674            // the load of styles is not consistent
675            return null;
676        }
677        $styles = ColorRgb::getDokuWikiStyles();
678        return $styles["replacements"]["__theme_color__"];
679
680    }
681
682    public static function getSecondaryColorValue($default = null)
683    {
684        $value = PluginUtility::getConfValue(ColorRgb::SECONDARY_COLOR_CONF, $default);
685        if ($value === null || trim($value) === "") {
686            return null;
687        }
688        return $value;
689    }
690
691    public static function setCanonicalUrlType(string $value)
692    {
693        PluginUtility::setConf(PageUrlType::CONF_CANONICAL_URL_TYPE, $value);
694    }
695
696    public static function setCanonicalUrlTypeToDefault()
697    {
698        PluginUtility::setConf(PageUrlType::CONF_CANONICAL_URL_TYPE, null);
699    }
700
701    public static function isBrandingColorInheritanceFunctional(): bool
702    {
703        return self::isBrandingColorInheritanceEnabled() && Site::getPrimaryColorValue() !== null;
704    }
705
706    public static function getMediaDirectory(): LocalPath
707    {
708        global $conf;
709        $mediaDirectory = $conf['mediadir'];
710        if ($mediaDirectory === null) {
711            throw new ExceptionComboRuntime("The media directory ($mediaDirectory) is null");
712        }
713        return LocalPath::createFromPath($mediaDirectory);
714    }
715
716    public static function getCacheDirectory(): LocalPath
717    {
718        global $conf;
719        $cacheDirectory = $conf['cachedir'];
720        if ($cacheDirectory === null) {
721            throw new ExceptionComboRuntime("The cache directory ($cacheDirectory) is null");
722        }
723        return LocalPath::createFromPath($cacheDirectory);
724    }
725
726
727    public static function getComboHome(): LocalPath
728    {
729        return LocalPath::create(DOKU_PLUGIN . PluginUtility::PLUGIN_BASE_NAME);
730    }
731
732    public static function getComboImagesDirectory(): LocalPath
733    {
734        return self::getComboResourcesDirectory()->resolve("images");
735    }
736
737    public static function getComboResourcesDirectory(): LocalPath
738    {
739        return Site::getComboHome()->resolve("resources");
740    }
741
742    public static function getComboDictionaryDirectory(): LocalPath
743    {
744        return Site::getComboResourcesDirectory()->resolve("dictionary");
745    }
746
747    public static function getComboResourceSnippetDirectory(): LocalPath
748    {
749        return Site::getComboResourcesDirectory()->resolve("snippet");
750    }
751
752    public static function getLogoHtml(): ?string
753    {
754
755        $tagAttributes = TagAttributes::createEmpty("identity");
756        $tagAttributes->addComponentAttributeValue(Dimension::WIDTH_KEY, "72");
757        $tagAttributes->addComponentAttributeValue(Dimension::HEIGHT_KEY, "72");
758        $tagAttributes->addComponentAttributeValue(TagAttributes::TYPE_KEY, SvgDocument::ICON_TYPE);
759        $tagAttributes->addClassName("logo");
760
761
762        /**
763         * Logo
764         */
765        $logoImages = Site::getLogoImages();
766        foreach ($logoImages as $logoImage) {
767            $path = $logoImage->getPath();
768            $mediaLink = MediaLink::createMediaLinkFromPath($path, $tagAttributes)
769                ->setLazyLoad(false);
770            try {
771                return $mediaLink->renderMediaTag();
772            } catch (ExceptionCombo $e) {
773                LogUtility::msg("Error while rendering the logo $logoImage");
774            }
775        }
776
777        return null;
778    }
779
780
781}
782