1<?php
2
3/*
4 * This file is part of the TYPO3 CMS project.
5 *
6 * It is free software; you can redistribute it and/or modify it under
7 * the terms of the GNU General Public License, either version 2
8 * of the License, or any later version.
9 *
10 * For the full copyright and license information, please read the
11 * LICENSE.txt file that was distributed with this source code.
12 *
13 * The TYPO3 project - inspiring people to share!
14 */
15
16namespace TYPO3\CMS\Frontend\ContentObject\Menu;
17
18use TYPO3\CMS\Core\TypoScript\TypoScriptService;
19use TYPO3\CMS\Core\Utility\GeneralUtility;
20
21/**
22 * Extension class creating text based menus
23 */
24class TextMenuContentObject extends AbstractMenuContentObject
25{
26    /**
27     * Calls processItemStates() so that the common configuration for the menu items are resolved into individual configuration per item.
28     * Sets the result for the new "normal state" in $this->result
29     *
30     * @see AbstractMenuContentObject::processItemStates()
31     */
32    public function generate()
33    {
34        $itemConfiguration = [];
35        $splitCount = count($this->menuArr);
36        if ($splitCount) {
37            $itemConfiguration = $this->processItemStates($splitCount);
38        }
39        if (!empty($this->mconf['debugItemConf'])) {
40            echo '<h3>$itemConfiguration:</h3>';
41            debug($itemConfiguration);
42        }
43        $this->result = $itemConfiguration;
44    }
45
46    /**
47     * Traverses the ->result array of menu items configuration (made by ->generate()) and renders each item.
48     * During the execution of this function many internal methods prefixed "extProc_" from this class is called and
49     * many of these are for now dummy functions.
50     * An instance of ContentObjectRenderer is also made and for each menu item rendered it is loaded with
51     * the record for that page so that any stdWrap properties that applies will have the current menu items record available.
52     *
53     * @return string The HTML for the menu (returns result through $this->extProc_finish(); )
54     */
55    public function writeMenu()
56    {
57        if (empty($this->result)) {
58            return '';
59        }
60
61        $this->WMresult = '';
62        $this->WMmenuItems = count($this->result);
63        $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
64        $this->WMsubmenuObjSuffixes = $typoScriptService->explodeConfigurationForOptionSplit(['sOSuffix' => $this->mconf['submenuObjSuffixes'] ?? null], $this->WMmenuItems);
65        foreach ($this->result as $key => $val) {
66            $GLOBALS['TSFE']->register['count_HMENU_MENUOBJ']++;
67            $GLOBALS['TSFE']->register['count_MENUOBJ']++;
68            // Initialize the cObj with the page record of the menu item
69            $this->WMcObj->start($this->menuArr[$key], 'pages', $this->request);
70            $this->I = [];
71            $this->I['key'] = $key;
72            $this->I['val'] = $val;
73            $this->I['title'] = $this->getPageTitle(($this->menuArr[$key]['title'] ?? ''), ($this->menuArr[$key]['nav_title'] ?? ''));
74            $this->I['title.'] = $this->I['val']['stdWrap.'] ?? [];
75            $this->I['title'] = $this->WMcObj->stdWrapValue('title', $this->I ?? []);
76            $this->I['uid'] = $this->menuArr[$key]['uid'] ?? 0;
77            $this->I['mount_pid'] = $this->menuArr[$key]['mount_pid'] ?? 0;
78            $this->I['pid'] = $this->menuArr[$key]['pid'] ?? 0;
79            $this->I['spacer'] = $this->menuArr[$key]['isSpacer'];
80            // Set access key
81            if ($this->mconf['accessKey'] ?? false) {
82                $this->I['accessKey'] = $this->accessKey((string)($this->I['title'] ?? ''));
83            } else {
84                $this->I['accessKey'] = [];
85            }
86            // Make link tag
87            $this->I['val']['ATagParams'] = $this->WMcObj->getATagParams($this->I['val']);
88            $this->I['val']['additionalParams'] = $this->WMcObj->stdWrapValue('additionalParams', $this->I['val']);
89            $this->I['linkHREF'] = $this->link((int)$key, (string)($this->I['val']['altTarget'] ?? ''), ($this->mconf['forceTypeValue'] ?? ''));
90            if (empty($this->I['linkHREF'])) {
91                $this->I['val']['doNotLinkIt'] = 1;
92            }
93            // Title attribute of links:
94            $titleAttrValue = $this->WMcObj->stdWrapValue('ATagTitle', $this->I['val']);
95            $titleAttrValue .= $this->I['accessKey']['alt'] ?? '';
96            if ($titleAttrValue !== '') {
97                $this->I['linkHREF']['title'] = $titleAttrValue;
98            }
99
100            // stdWrap for doNotLinkIt
101            $this->I['val']['doNotLinkIt'] = $this->WMcObj->stdWrapValue('doNotLinkIt', $this->I['val']);
102            // Compile link tag
103            if (!$this->I['val']['doNotLinkIt']) {
104                $this->I['val']['doNotLinkIt'] = 0;
105            }
106            if (!$this->I['spacer'] && $this->I['val']['doNotLinkIt'] != 1) {
107                $this->setATagParts();
108            } else {
109                $this->I['A1'] = '';
110                $this->I['A2'] = '';
111            }
112            // ATagBeforeWrap processing:
113            if ($this->I['val']['ATagBeforeWrap'] ?? false) {
114                $wrapPartsBefore = explode('|', $this->I['val']['linkWrap'] ?? '');
115                $wrapPartsAfter = ['', ''];
116            } else {
117                $wrapPartsBefore = ['', ''];
118                $wrapPartsAfter = explode('|', $this->I['val']['linkWrap'] ?? '');
119            }
120            if (($this->I['val']['stdWrap2'] ?? false) || isset($this->I['val']['stdWrap2.'])) {
121                $stdWrap2 = (string)(isset($this->I['val']['stdWrap2.']) ? $this->WMcObj->stdWrap('|', $this->I['val']['stdWrap2.']) : '|');
122                $wrapPartsStdWrap = explode($this->I['val']['stdWrap2'] ?: '|', $stdWrap2);
123            } else {
124                $wrapPartsStdWrap = ['', ''];
125            }
126            // Make before, middle and after parts
127            $this->I['parts'] = [];
128            $this->I['parts']['before'] = $this->getBeforeAfter('before');
129            $this->I['parts']['stdWrap2_begin'] = $wrapPartsStdWrap[0];
130            // stdWrap for doNotShowLink
131            $this->I['val']['doNotShowLink'] = $this->WMcObj->stdWrapValue('doNotShowLink', $this->I['val']);
132            if (!$this->I['val']['doNotShowLink']) {
133                $this->I['parts']['notATagBeforeWrap_begin'] = $wrapPartsAfter[0] ?? '';
134                $this->I['parts']['ATag_begin'] = $this->I['A1'];
135                $this->I['parts']['ATagBeforeWrap_begin'] = $wrapPartsBefore[0] ?? '';
136                $this->I['parts']['title'] = $this->I['title'];
137                $this->I['parts']['ATagBeforeWrap_end'] = $wrapPartsBefore[1] ?? '';
138                $this->I['parts']['ATag_end'] = $this->I['A2'];
139                $this->I['parts']['notATagBeforeWrap_end'] = $wrapPartsAfter[1] ?? '';
140            }
141            $this->I['parts']['stdWrap2_end'] = $wrapPartsStdWrap[1];
142            $this->I['parts']['after'] = $this->getBeforeAfter('after');
143            // Passing I to a user function
144            if ($this->mconf['IProcFunc'] ?? false) {
145                $this->I = $this->userProcess('IProcFunc', $this->I);
146            }
147            // Merge parts + beforeAllWrap
148            $this->I['theItem'] = implode('', $this->I['parts']);
149            // allWrap:
150            $allWrap = $this->WMcObj->stdWrapValue('allWrap', $this->I['val']);
151            $this->I['theItem'] = $this->WMcObj->wrap($this->I['theItem'], $allWrap);
152            if ($this->I['val']['subst_elementUid'] ?? false) {
153                $this->I['theItem'] = str_replace('{elementUid}', (string)$this->I['uid'], $this->I['theItem']);
154            }
155            // allStdWrap:
156            if (is_array($this->I['val']['allStdWrap.'] ?? null)) {
157                $this->I['theItem'] = $this->WMcObj->stdWrap($this->I['theItem'], $this->I['val']['allStdWrap.']);
158            }
159            // Calling extra processing function
160            $this->extProc_afterLinking((int)$key);
161        }
162        return $this->extProc_finish();
163    }
164
165    /**
166     * Generates the before* and after* stdWrap for TMENUs
167     * Evaluates:
168     * - before.stdWrap*
169     * - beforeWrap
170     * - after.stdWrap*
171     * - afterWrap
172     *
173     * @param string $pref Can be "before" or "after" and determines which kind of stdWrap to process (basically this is the prefix of the TypoScript properties that are read from the ->I['val'] array
174     * @return string The resulting HTML
175     */
176    protected function getBeforeAfter($pref)
177    {
178        $processedPref = $this->WMcObj->stdWrapValue($pref, $this->I['val']);
179        if (isset($this->I['val'][$pref . 'Wrap'])) {
180            return $this->WMcObj->wrap($processedPref, $this->I['val'][$pref . 'Wrap']);
181        }
182        return $processedPref;
183    }
184
185    /**
186     * Called right after the creation of links for the menu item. This is also the last function call before the while-loop traversing menu items goes to the next item.
187     * This function MUST set $this->WMresult.=[HTML for menu item] to add the generated menu item to the internal accumulation of items.
188     *
189     * @param int $key Pointer to $this->menuArr[$key] where the current menu element record is found
190     * @see writeMenu()
191     */
192    protected function extProc_afterLinking($key)
193    {
194        $explicitSpacerRenderingEnabled = ($this->mconf['SPC'] ?? false);
195        $isSpacerPage = $this->I['spacer'] ?? false;
196        // If rendering of SPACERs is enabled, also allow rendering submenus with Spacers
197        if (!$isSpacerPage || $explicitSpacerRenderingEnabled) {
198            // Add part to the accumulated result + fetch submenus
199            $this->I['theItem'] .= $this->subMenu($this->I['uid'], $this->WMsubmenuObjSuffixes[$key]['sOSuffix'] ?? '');
200        }
201        $part = $this->WMcObj->stdWrapValue('wrapItemAndSub', $this->I['val']);
202        $this->WMresult .= $part ? $this->WMcObj->wrap($this->I['theItem'], $part) : $this->I['theItem'];
203    }
204
205    /**
206     * Called before the writeMenu() function returns (only if a menu was generated)
207     *
208     * @return string The total menu content should be returned by this function
209     * @see writeMenu()
210     */
211    protected function extProc_finish()
212    {
213        if (is_array($this->mconf['stdWrap.'] ?? null)) {
214            $this->WMresult = (string)$this->WMcObj->stdWrap($this->WMresult, $this->mconf['stdWrap.']);
215        }
216        return $this->WMcObj->wrap($this->WMresult, $this->mconf['wrap'] ?? '');
217    }
218}
219