1<?php
2
3namespace ComboStrap;
4
5use ComboStrap\Tag\BoxTag;
6use Doku_Renderer_xhtml;
7use syntax_plugin_combo_header;
8
9/**
10 * * Horizontal Card
11 * https://getbootstrap.com/docs/4.3/components/card/#horizontal
12 *
13 * https://material.io/components/cards
14 * [[https://getbootstrap.com/docs/5.0/components/card/|Bootstrap card]]
15 */
16class CardTag
17{
18
19    public const CANONICAL = CardTag::LOGICAL_TAG;
20    public const CARD_TAG = 'card';
21    public const CONF_ENABLE_SECTION_EDITING = "enableCardSectionEditing";
22    const TEASER_TAG = 'teaser';
23    const LOGICAL_TAG = self::CARD_TAG;
24
25
26    public static function handleEnter(TagAttributes $tagAttributes, \Doku_Handler $handler): array
27    {
28
29        /** A card without context */
30        $tagAttributes->addClassName("card");
31
32        /**
33         * Context
34         */
35        $callStack = CallStack::createFromHandler($handler);
36        $parent = $callStack->moveToParent();
37        $context = null;
38        if ($parent !== false) {
39            $context = $parent->getTagName();
40            if ($context === FragmentTag::FRAGMENT_TAG) {
41                $parent = $callStack->moveToParent();
42                if ($parent !== false) {
43                    $context = $parent->getTagName();
44                }
45            }
46        }
47
48        $returnedArray = array(
49            PluginUtility::CONTEXT => $context
50        );
51
52        $id = ExecutionContext::getActualOrCreateFromEnv()
53            ->getIdManager()
54            ->generateNewHtmlIdForComponent(CardTag::CARD_TAG);
55        $returnedArray[TagAttributes::ID_KEY] = $id;
56
57        return $returnedArray;
58    }
59
60    public static function handleExit(\Doku_Handler $handler, $pos, $match): array
61    {
62        $callStack = CallStack::createFromHandler($handler);
63
64        /**
65         * Check and add a scroll toggle if the
66         * card is constrained by height
67         */
68        Dimension::addScrollToggleOnClickIfNoControl($callStack);
69
70        // Processing
71        $callStack->moveToEnd();
72        $previousOpening = $callStack->moveToPreviousCorrespondingOpeningCall();
73        /**
74         * Do we have an illustrative image ?
75         *
76         * Because the image is considered an inline component
77         * We need to be careful to not wrap it into
78         * a paragraph (when the {@link syntax_plugin_combo_para::fromEolToParagraphUntilEndOfStack() process is kicking)
79         */
80        while ($actualCall = $callStack->next()) {
81
82            if ($actualCall->isUnMatchedEmptyCall()) {
83                continue;
84            }
85
86            $tagName = $actualCall->getTagName();
87            $imageTag = "image";
88            $tagImage = null;
89            if (in_array($tagName, Call::IMAGE_TAGS)) {
90                $tagImage = $tagName;
91                $tagName = $imageTag;
92            }
93            switch ($tagName) {
94                case $imageTag:
95                    $actualCall->addClassName("card-img-top");
96                    if ($tagImage !== PageImageTag::MARKUP) {
97                        $actualCall->setType(FetcherSvg::ILLUSTRATION_TYPE);
98                    }
99                    $actualCall->addAttribute(MediaMarkup::LINKING_KEY, MediaMarkup::LINKING_NOLINK_VALUE);
100                    if (!$actualCall->hasAttribute(Dimension::RATIO_ATTRIBUTE)) {
101                        $actualCall->addAttribute(Dimension::RATIO_ATTRIBUTE, "16:9");
102                    }
103                    $actualCall->setDisplay(Call::BlOCK_DISPLAY);
104                    // an image should stretch into the card
105                    $actualCall->addCssStyle("max-width", "100%");
106                    break 2;
107                case "eol":
108                    break;
109                default:
110                    break 2;
111
112            }
113
114        }
115        /**
116         * If there is an Header
117         * go to the end
118         */
119        if ($actualCall->getTagName() === syntax_plugin_combo_header::TAG && $actualCall->getState() === DOKU_LEXER_ENTER) {
120            while ($actualCall = $callStack->next()) {
121                if (
122                    $actualCall->getTagName() === syntax_plugin_combo_header::TAG
123                    && $actualCall->getState() === DOKU_LEXER_EXIT) {
124                    break;
125                }
126            }
127        }
128        /**
129         * Insert card-body
130         */
131        $bodyCall = self::createCardBodyEnterCall();
132        $insertBodyAfterThisCalls = PluginUtility::mergeAttributes(Call::IMAGE_TAGS, [syntax_plugin_combo_header::TAG]);
133        if (in_array($actualCall->getTagName(), $insertBodyAfterThisCalls)) {
134
135            $callStack->insertAfter($bodyCall);
136
137        } else {
138            /**
139             * Body was reached
140             */
141            $callStack->insertBefore($bodyCall);
142            /**
143             * Previous because the next function (EOL processing)
144             * should start from previous
145             */
146            $callStack->previous();
147        }
148
149        /**
150         * Process the body
151         */
152        $callStack->insertEolIfNextCallIsNotEolOrBlock();
153        $callStack->processEolToEndStack([TagAttributes::CLASS_KEY => "card-text"]);
154
155        /**
156         * Insert the card body exit
157         */
158        $callStack->insertBefore(
159            Call::createComboCall(
160                BoxTag::TAG,
161                DOKU_LEXER_EXIT,
162                [BoxTag::HTML_TAG_ATTRIBUTE => "div"],
163                null,
164                null,
165                null,
166                null,
167                \syntax_plugin_combo_xmlblocktag::TAG
168            )
169        );
170
171
172        /**
173         * File Section editing
174         */
175        if (SiteConfig::getConfValue(CardTag::CONF_ENABLE_SECTION_EDITING, 1)) {
176            /**
177             * +1 to go at the line ?
178             */
179            $endPosition = $pos + strlen($match) + 1;
180            $position = $previousOpening->getFirstMatchedCharacterPosition();
181            $id = $previousOpening->getIdOrDefault();
182            $editButtonCall = EditButton::create("Edit Card $id")
183                ->setStartPosition($position)
184                ->setEndPosition($endPosition)
185                ->toComboCallComboFormat();
186            $callStack->moveToEnd();
187            $callStack->insertBefore($editButtonCall);
188
189        }
190
191        return array(PluginUtility::CONTEXT => $previousOpening->getContext());
192    }
193
194    public static function renderEnterXhtml(TagAttributes $attributes, Doku_Renderer_xhtml $renderer, array $data): string
195    {
196        /**
197         * Add the CSS
198         */
199        $snippetManager = PluginUtility::getSnippetManager();
200        $snippetManager->attachCssInternalStyleSheet(CardTag::CARD_TAG);
201
202
203        $context = $data[PluginUtility::CONTEXT];
204        if ($context === MasonryTag::MASONRY_TAG) {
205            MasonryTag::addColIfBootstrap5AndCardColumns($renderer, $context);
206        }
207
208        /**
209         * Card
210         */
211        return $attributes->toHtmlEnterTag("div");
212    }
213
214    public static function handleExitXhtml(array $data, Doku_Renderer_xhtml $renderer)
215    {
216        /**
217         * End card
218         */
219        $renderer->doc .= "</div>" . DOKU_LF;
220
221        /**
222         * End Masonry column if any
223         * {@link MasonryTag::addColIfBootstrap5AndCardColumns()}
224         */
225        $context = $data[PluginUtility::CONTEXT];
226        if ($context === MasonryTag::MASONRY_TAG) {
227            MasonryTag::endColIfBootstrap5AnCardColumns($renderer, $context);
228        }
229    }
230
231    public static function createCardBodyExitCall(): Call
232    {
233        return Call::createComboCall(
234            BoxTag::TAG,
235            DOKU_LEXER_EXIT,
236            [],
237            null,
238            null,
239            null,
240            null,
241            \syntax_plugin_combo_xmlblocktag::TAG
242        );
243    }
244
245    public static function createCardBodyEnterCall($context = null): Call
246    {
247        return Call::createComboCall(
248            BoxTag::TAG,
249            DOKU_LEXER_ENTER,
250            [
251                BoxTag::HTML_TAG_ATTRIBUTE => "div",
252                BoxTag::LOGICAL_TAG_ATTRIBUTE => 'card-body',
253                TagAttributes::CLASS_KEY => 'card-body',
254            ],
255            $context,
256            null,
257            null,
258            null,
259            \syntax_plugin_combo_xmlblocktag::TAG
260        );
261    }
262}
263