1<?php
2
3
4namespace ComboStrap\Meta\Field;
5
6
7use ComboStrap\ExceptionNotFound;
8use ComboStrap\ExecutionContext;
9use ComboStrap\FileSystems;
10use ComboStrap\LogUtility;
11use ComboStrap\MarkupPath;
12use ComboStrap\Meta\Api\Metadata;
13use ComboStrap\Meta\Api\MetadataText;
14use ComboStrap\MetaManagerForm;
15use ComboStrap\TemplateEngine;
16use ComboStrap\Site;
17use ComboStrap\SlotSystem;
18use ComboStrap\Tag\BarTag;
19
20class PageTemplateName extends MetadataText
21{
22
23
24    public const PROPERTY_NAME = "template";
25    public const PROPERTY_NAME_OLD = "layout";
26    public const HOLY_TEMPLATE_VALUE = "holy";
27    public const MEDIUM_TEMPLATE_VALUE = "medium";
28    public const LANDING_TEMPLATE_VALUE = "landing";
29    public const INDEX_TEMPLATE_VALUE = "index";
30    public const HAMBURGER_TEMPLATE_VALUE = "hamburger";
31    public const BLANK_TEMPLATE_VALUE = "blank";
32
33    /**
34     * Not public, used in test to overwrite it to {@link PageTemplateName::BLANK_TEMPLATE_VALUE}
35     * to speed up test
36     */
37    const CONF_DEFAULT_NAME = "defaultLayoutName";
38
39
40    /**
41     * App page
42     */
43    const APP_PREFIX = "app-";
44    const APP_EDIT = self::APP_PREFIX . ExecutionContext::EDIT_ACTION;
45    const APP_LOGIN = self::APP_PREFIX . ExecutionContext::LOGIN_ACTION;
46    const APP_SEARCH = self::APP_PREFIX . ExecutionContext::SEARCH_ACTION;
47    const APP_REGISTER = self::APP_PREFIX . ExecutionContext::REGISTER_ACTION;
48    const APP_RESEND_PWD = self::APP_PREFIX . ExecutionContext::RESEND_PWD_ACTION;
49    const APP_REVISIONS = self::APP_PREFIX . ExecutionContext::REVISIONS_ACTION;
50    const APP_DIFF = self::APP_PREFIX . ExecutionContext::DIFF_ACTION;
51    const APP_INDEX = self::APP_PREFIX . ExecutionContext::INDEX_ACTION;
52    const APP_PROFILE = self::APP_PREFIX . ExecutionContext::PROFILE_ACTION;
53
54    /**
55     * @deprecated for {@link self::MEDIUM_TEMPLATE_VALUE}
56     * changed to medium (median has too much mathematics connotation)
57     * medium: halfway between two extremes
58     */
59    const MEDIAN_OLD_TEMPLATE = "median";
60    const HOLY_MEDIUM_LAYOUT = "holy-medium";
61    const INDEX_MEDIUM_LAYOUT = "index-medium";
62
63
64    public static function createFromPage(MarkupPath $page): PageTemplateName
65    {
66        return (new PageTemplateName())
67            ->setResource($page);
68    }
69
70    static public function getTab(): string
71    {
72        return MetaManagerForm::TAB_PAGE_VALUE;
73    }
74
75    static public function getDescription(): string
76    {
77        return "A template applies a layout on your page";
78    }
79
80    static public function getLabel(): string
81    {
82        return "Template";
83    }
84
85    public function getPossibleValues(): ?array
86    {
87        try {
88            $templateNames = [];
89            $directories = TemplateEngine::createFromContext()
90                ->getTemplateSearchDirectories();
91            foreach ($directories as $directory) {
92                $files = FileSystems::getChildrenLeaf($directory);
93                foreach ($files as $file) {
94                    $lastNameWithoutExtension = $file->getLastNameWithoutExtension();
95                    if (strpos($lastNameWithoutExtension, self::APP_PREFIX) === 0) {
96                        continue;
97                    }
98                    if ($file->getExtension() === TemplateEngine::EXTENSION_HBS) {
99
100                        $templateNames[] = $lastNameWithoutExtension;
101                    }
102                }
103            }
104            sort($templateNames);
105            return $templateNames;
106        } catch (ExceptionNotFound $e) {
107            LogUtility::error("No template could be found", self::CANONICAL, $e);
108            return [];
109        }
110    }
111
112
113    static public function getName(): string
114    {
115        return self::PROPERTY_NAME;
116    }
117
118    static public function getPersistenceType(): string
119    {
120        return Metadata::PERSISTENT_METADATA;
121    }
122
123    static public function isMutable(): bool
124    {
125        return true;
126    }
127
128    /**
129     * @return string
130     */
131    public function getDefaultValue(): string
132    {
133        /**
134         * @var MarkupPath $page
135         */
136        $page = $this->getResource();
137
138        /**
139         * Slot first
140         * because they are also root item page
141         */
142        try {
143            switch ($page->getPathObject()->getLastNameWithoutExtension()) {
144                case SlotSystem::getSidebarName():
145                case SlotSystem::getMainHeaderSlotName():
146                case SlotSystem::getMainFooterSlotName():
147                case SlotSystem::getMainSideSlotName():
148                    return self::INDEX_MEDIUM_LAYOUT;
149                case SlotSystem::getPageHeaderSlotName():
150                case SlotSystem::getPageFooterSlotName():
151                    /**
152                     * Header and footer contains bar
153                     * {@link \syntax_plugin_combo_menubar menubar} or
154                     * {@link \syntax_plugin_combo_bar}
155                     * They therefore should not be constrained
156                     * Landing page is perfect
157                     */
158                    return self::LANDING_TEMPLATE_VALUE;
159            }
160        } catch (ExceptionNotFound $e) {
161            // No last name not installed
162        }
163
164
165        if ($page->isRootHomePage()) {
166            /**
167             * Ultimattely a {@link self::LANDING_TEMPLATE_VALUE}
168             * but for that the user needs to add {@link BarTag}
169             *
170             */
171            return self::HAMBURGER_TEMPLATE_VALUE;
172        }
173        if ($page->isRootItemPage()) {
174            /**
175             * Home/Root item does not really belongs to the same
176             * namespace, we don't show therefore a sidebar
177             */
178            return self::INDEX_MEDIUM_LAYOUT;
179        }
180
181
182        /**
183         * Default by namespace
184         *
185         * Calculate the possible template
186         * prefix in order
187         */
188        try {
189            $parentNames = $page->getPathObject()->getParent()->getNames();
190            $templatePrefixes = [];
191            $hierarchicalName = '';
192            foreach ($parentNames as $name) {
193                if (empty($hierarchicalName)) {
194                    $hierarchicalName .= $name;
195                } else {
196                    $hierarchicalName .= "-$name";
197                }
198                $templatePrefixes[] = $name;
199                if ($hierarchicalName !== $name) {
200                    $templatePrefixes[] = $hierarchicalName;
201                }
202            }
203            $templatePrefixes = array_reverse($templatePrefixes);
204        } catch (ExceptionNotFound $e) {
205            // no parent, root
206            $templatePrefixes = [];
207        }
208
209        $pageTemplateEngine = TemplateEngine::createFromContext();
210
211
212        /**
213         * Index pages
214         */
215        if ($page->isIndexPage()) {
216            foreach ($templatePrefixes as $templatePrefix) {
217                $templateName = "$templatePrefix-index";
218                if ($pageTemplateEngine->templateExists($templateName)) {
219                    return $templateName;
220                }
221            }
222            return self::INDEX_TEMPLATE_VALUE;
223        }
224
225        /**
226         * Item page
227         */
228        foreach ($templatePrefixes as $templatePrefix) {
229            $templateName = "$templatePrefix-item";
230            if ($pageTemplateEngine->templateExists($templateName)) {
231                return $templateName;
232            }
233        }
234
235        return ExecutionContext::getActualOrCreateFromEnv()->getConfig()->getDefaultLayoutName();
236
237
238    }
239
240    static public function getCanonical(): string
241    {
242        return self::PROPERTY_NAME;
243    }
244
245
246    /**
247     * @return string
248     */
249    public function getValueOrDefault(): string
250    {
251
252        try {
253            $value = $this->getValue();
254            if ($value === "") {
255                return $this->getDefaultValue();
256            }
257            return $value;
258        } catch (ExceptionNotFound $e) {
259            return $this->getDefaultValue();
260        }
261
262
263    }
264
265    /** @noinspection PhpMissingReturnTypeInspection */
266    public function buildFromReadStore()
267    {
268
269        $metaDataStore = $this->getReadStore();
270        $value = $metaDataStore->getFromName(self::PROPERTY_NAME);
271        if ($value === null) {
272            $value = $metaDataStore->getFromName(self::PROPERTY_NAME_OLD);
273        }
274        if ($value === self::MEDIAN_OLD_TEMPLATE) {
275            $value = self::MEDIUM_TEMPLATE_VALUE;
276        }
277        parent::setFromStoreValueWithoutException($value);
278        return $this;
279    }
280
281    public function sendToWriteStore(): Metadata
282    {
283
284        parent::sendToWriteStore();
285        $writeStore = $this->getWriteStore();
286        $value = $writeStore->getFromName(self::PROPERTY_NAME_OLD);
287        if ($value !== null) {
288            // delete the old value
289            $writeStore->setFromPersistentName(self::PROPERTY_NAME_OLD, null);
290        }
291
292        return $this;
293    }
294
295    public static function getOldPersistentNames(): array
296    {
297        return [self::PROPERTY_NAME_OLD];
298    }
299
300
301    static public function isOnForm(): bool
302    {
303        return true;
304    }
305}
306