137748cd8SNickeau<?php 237748cd8SNickeau/** 337748cd8SNickeau * Copyright (c) 2021. ComboStrap, Inc. and its affiliates. All Rights Reserved. 437748cd8SNickeau * 537748cd8SNickeau * This source code is licensed under the GPL license found in the 637748cd8SNickeau * COPYING file in the root directory of this source tree. 737748cd8SNickeau * 837748cd8SNickeau * @license GPL 3 (https://www.gnu.org/licenses/gpl-3.0.en.html) 937748cd8SNickeau * @author ComboStrap <support@combostrap.com> 1037748cd8SNickeau * 1137748cd8SNickeau */ 1237748cd8SNickeau 1337748cd8SNickeaunamespace ComboStrap; 1437748cd8SNickeau 1537748cd8SNickeau 1637748cd8SNickeauuse Doku_Handler; 1737748cd8SNickeauuse dokuwiki\Extension\SyntaxPlugin; 1837748cd8SNickeauuse dokuwiki\Parsing\Parser; 1937748cd8SNickeauuse syntax_plugin_combo_media; 2037748cd8SNickeau 2137748cd8SNickeau/** 2237748cd8SNickeau * Class CallStack 2337748cd8SNickeau * @package ComboStrap 2437748cd8SNickeau * 2537748cd8SNickeau * This is a class that manipulate the call stack. 2637748cd8SNickeau * 2737748cd8SNickeau * A call stack is composed of call (ie array) 2837748cd8SNickeau * A tag is a call that has a state {@link DOKU_LEXER_ENTER} or {@link DOKU_LEXER_SPECIAL} 2937748cd8SNickeau * An opening call is a call with the {@link DOKU_LEXER_ENTER} 3037748cd8SNickeau * An closing call is a call with the {@link DOKU_LEXER_EXIT} 3137748cd8SNickeau * 3237748cd8SNickeau * You can move on the stack with the function: 3337748cd8SNickeau * * {@link CallStack::next()} 3437748cd8SNickeau * * {@link CallStack::previous()} 3537748cd8SNickeau * * `MoveTo`. example: {@link CallStack::moveToPreviousCorrespondingOpeningCall()} 3637748cd8SNickeau * 3737748cd8SNickeau * 3837748cd8SNickeau */ 3937748cd8SNickeauclass CallStack 4037748cd8SNickeau{ 4137748cd8SNickeau 4237748cd8SNickeau const TAG_STATE = [DOKU_LEXER_SPECIAL, DOKU_LEXER_ENTER]; 4337748cd8SNickeau 4437748cd8SNickeau const CANONICAL = "support"; 4537748cd8SNickeau 4637748cd8SNickeau /** 4737748cd8SNickeau * The type of callstack 4837748cd8SNickeau * * main is the normal 4937748cd8SNickeau * * writer is when there is a temporary call stack from the writer 5037748cd8SNickeau */ 5137748cd8SNickeau const CALLSTACK_WRITER = "writer"; 5237748cd8SNickeau const CALLSTACK_MAIN = "main"; 5337748cd8SNickeau public const MESSAGE_PREFIX_CALLSTACK_NOT_CONFORM = "Your DokuWiki installation is too old or a writer plugin does not conform"; 5437748cd8SNickeau 5537748cd8SNickeau private $handler; 5637748cd8SNickeau 5737748cd8SNickeau /** 5837748cd8SNickeau * The max key of the calls 5937748cd8SNickeau * @var int|null 6037748cd8SNickeau */ 6137748cd8SNickeau private $maxIndex = 0; 6237748cd8SNickeau 6337748cd8SNickeau /** 6437748cd8SNickeau * @var array the call stack 6537748cd8SNickeau */ 6637748cd8SNickeau private $callStack = []; 6737748cd8SNickeau 6837748cd8SNickeau /** 6937748cd8SNickeau * A pointer to keep the information 7037748cd8SNickeau * if we have gone to far in the stack 7137748cd8SNickeau * (because you lost the fact that you are outside 7237748cd8SNickeau * the boundary, ie you can do a {@link \prev}` after that a {@link \next} return false 7337748cd8SNickeau * @var bool 7437748cd8SNickeau * If true, we are at the offset: end of th array + 1 7537748cd8SNickeau */ 7637748cd8SNickeau private $endWasReached = false; 7737748cd8SNickeau /** 7837748cd8SNickeau * If true, we are at the offset: start of th array - 1 7937748cd8SNickeau * You can use {@link CallStack::next()} 8037748cd8SNickeau * @var bool 8137748cd8SNickeau */ 8237748cd8SNickeau private $startWasReached = false; 8337748cd8SNickeau 8437748cd8SNickeau /** 8537748cd8SNickeau * @var string the type of callstack 8637748cd8SNickeau */ 8737748cd8SNickeau private $callStackType = "unknown"; 8837748cd8SNickeau 8937748cd8SNickeau /** 9037748cd8SNickeau * A callstack is a pointer implementation to manipulate 9137748cd8SNickeau * the {@link Doku_Handler::$calls call stack of the handler} 9237748cd8SNickeau * 9337748cd8SNickeau * When you create a callstack object, the pointer 9437748cd8SNickeau * is located at the end. 9537748cd8SNickeau * 9637748cd8SNickeau * If you want to reset the pointer, you need 9737748cd8SNickeau * to call the {@link CallStack::closeAndResetPointer()} function 9837748cd8SNickeau * 9937748cd8SNickeau * @param \Doku_Handler 10037748cd8SNickeau */ 10137748cd8SNickeau public function __construct(&$handler) 10237748cd8SNickeau { 10337748cd8SNickeau $this->handler = $handler; 10437748cd8SNickeau 10537748cd8SNickeau /** 10637748cd8SNickeau * A temporary Call stack is created in the writer 10737748cd8SNickeau * for list, table, blockquote 10837748cd8SNickeau * 10937748cd8SNickeau * But third party plugin can overwrite the writer 11037748cd8SNickeau * to capture the call 11137748cd8SNickeau * 11237748cd8SNickeau * See the 11337748cd8SNickeau * https://www.dokuwiki.org/devel:parser#handler_token_methods 11437748cd8SNickeau * for an example with a list component 11537748cd8SNickeau * 11637748cd8SNickeau */ 11737748cd8SNickeau $headErrorMessage = self::MESSAGE_PREFIX_CALLSTACK_NOT_CONFORM; 11837748cd8SNickeau if (!method_exists($handler, 'getCallWriter')) { 11937748cd8SNickeau $class = get_class($handler); 12037748cd8SNickeau LogUtility::msg("$headErrorMessage. The handler ($class) provided cannot manipulate the callstack (ie the function getCallWriter does not exist).", LogUtility::LVL_MSG_ERROR); 12137748cd8SNickeau return; 12237748cd8SNickeau } 12337748cd8SNickeau $callWriter = $handler->getCallWriter(); 12437748cd8SNickeau 12537748cd8SNickeau /** 12637748cd8SNickeau * Check the calls property 12737748cd8SNickeau */ 12837748cd8SNickeau $callWriterClass = get_class($callWriter); 12937748cd8SNickeau $callsPropertyFromCallWriterExists = true; 13037748cd8SNickeau try { 13137748cd8SNickeau $rp = new \ReflectionProperty($callWriterClass, "calls"); 13237748cd8SNickeau if ($rp->isPrivate()) { 13337748cd8SNickeau LogUtility::msg("$headErrorMessage. The call writer ($callWriterClass) provided cannot manipulate the callstack (ie the calls of the call writer are private).", LogUtility::LVL_MSG_ERROR); 13437748cd8SNickeau return; 13537748cd8SNickeau } 13637748cd8SNickeau } catch (\ReflectionException $e) { 13737748cd8SNickeau $callsPropertyFromCallWriterExists = false; 13837748cd8SNickeau } 13937748cd8SNickeau 14037748cd8SNickeau /** 14137748cd8SNickeau * The calls 14237748cd8SNickeau */ 14337748cd8SNickeau if ($callsPropertyFromCallWriterExists) { 14437748cd8SNickeau 14537748cd8SNickeau $writerCalls = &$callWriter->calls; 14637748cd8SNickeau $this->callStack = &$writerCalls; 14737748cd8SNickeau $this->callStackType = self::CALLSTACK_WRITER; 14837748cd8SNickeau 14937748cd8SNickeau } else { 15037748cd8SNickeau 15137748cd8SNickeau /** 15237748cd8SNickeau * Check the calls property of the handler 15337748cd8SNickeau */ 15437748cd8SNickeau $handlerClass = get_class($handler); 15537748cd8SNickeau try { 15637748cd8SNickeau $rp = new \ReflectionProperty($handlerClass, "calls"); 15737748cd8SNickeau if ($rp->isPrivate()) { 15837748cd8SNickeau LogUtility::msg("$headErrorMessage. The handler ($handlerClass) provided cannot manipulate the callstack (ie the calls of the handler are private).", LogUtility::LVL_MSG_ERROR); 15937748cd8SNickeau return; 16037748cd8SNickeau } 16137748cd8SNickeau } catch (\ReflectionException $e) { 16237748cd8SNickeau LogUtility::msg("$headErrorMessage. The handler ($handlerClass) provided cannot manipulate the callstack (ie the handler does not have any calls property).", LogUtility::LVL_MSG_ERROR); 16337748cd8SNickeau return; 16437748cd8SNickeau } 16537748cd8SNickeau 16637748cd8SNickeau /** 16737748cd8SNickeau * Initiate the callstack 16837748cd8SNickeau */ 16937748cd8SNickeau $this->callStack = &$handler->calls; 17037748cd8SNickeau $this->callStackType = self::CALLSTACK_MAIN; 17137748cd8SNickeau 17237748cd8SNickeau } 17337748cd8SNickeau 17437748cd8SNickeau $this->maxIndex = ArrayUtility::array_key_last($this->callStack); 17537748cd8SNickeau $this->moveToEnd(); 17637748cd8SNickeau 17737748cd8SNickeau 17837748cd8SNickeau } 17937748cd8SNickeau 18037748cd8SNickeau public 18137748cd8SNickeau static function createFromMarkup($marki) 18237748cd8SNickeau { 18337748cd8SNickeau 18437748cd8SNickeau $modes = p_get_parsermodes(); 18537748cd8SNickeau $handler = new Doku_Handler(); 18637748cd8SNickeau $parser = new Parser($handler); 18737748cd8SNickeau 18837748cd8SNickeau //add modes to parser 18937748cd8SNickeau foreach ($modes as $mode) { 19037748cd8SNickeau $parser->addMode($mode['mode'], $mode['obj']); 19137748cd8SNickeau } 19237748cd8SNickeau $parser->parse($marki); 19337748cd8SNickeau return self::createFromHandler($handler); 19437748cd8SNickeau 19537748cd8SNickeau } 19637748cd8SNickeau 19737748cd8SNickeau /** 19837748cd8SNickeau * Reset the pointer 19937748cd8SNickeau */ 20037748cd8SNickeau public 20137748cd8SNickeau function closeAndResetPointer() 20237748cd8SNickeau { 20337748cd8SNickeau reset($this->callStack); 20437748cd8SNickeau } 20537748cd8SNickeau 20637748cd8SNickeau /** 20737748cd8SNickeau * Delete from the call stack 20837748cd8SNickeau * @param $calls 20937748cd8SNickeau * @param $start 21037748cd8SNickeau * @param $end 21137748cd8SNickeau */ 21237748cd8SNickeau public 21337748cd8SNickeau static function deleteCalls(&$calls, $start, $end) 21437748cd8SNickeau { 21537748cd8SNickeau for ($i = $start; $i <= $end; $i++) { 21637748cd8SNickeau unset($calls[$i]); 21737748cd8SNickeau } 21837748cd8SNickeau } 21937748cd8SNickeau 22037748cd8SNickeau /** 22137748cd8SNickeau * @param array $calls 22237748cd8SNickeau * @param integer $position 22337748cd8SNickeau * @param array $callStackToInsert 22437748cd8SNickeau */ 22537748cd8SNickeau public 22637748cd8SNickeau static function insertCallStackUpWards(&$calls, $position, $callStackToInsert) 22737748cd8SNickeau { 22837748cd8SNickeau 22937748cd8SNickeau array_splice($calls, $position, 0, $callStackToInsert); 23037748cd8SNickeau 23137748cd8SNickeau } 23237748cd8SNickeau 23337748cd8SNickeau /** 23437748cd8SNickeau * A callstack pointer based implementation 23537748cd8SNickeau * that starts at the end 23637748cd8SNickeau * @param Doku_Handler $handler 23737748cd8SNickeau * @return CallStack 23837748cd8SNickeau */ 23937748cd8SNickeau public 24037748cd8SNickeau static function createFromHandler(&$handler) 24137748cd8SNickeau { 24237748cd8SNickeau return new CallStack($handler); 24337748cd8SNickeau } 24437748cd8SNickeau 24537748cd8SNickeau 24637748cd8SNickeau /** 24737748cd8SNickeau * Process the EOL call to the end of stack 24837748cd8SNickeau * replacing them with paragraph call 24937748cd8SNickeau * 25037748cd8SNickeau * A sort of {@link Block::process()} but only from a tag 25137748cd8SNickeau * to the end of the current stack 25237748cd8SNickeau * 25337748cd8SNickeau * This function is used basically in the {@link DOKU_LEXER_EXIT} 25437748cd8SNickeau * state of {@link SyntaxPlugin::handle()} to create paragraph 25537748cd8SNickeau * with the class given as parameter 25637748cd8SNickeau * 25737748cd8SNickeau * @param $attributes - the attributes in an callstack array form passed to the paragraph 25837748cd8SNickeau */ 25937748cd8SNickeau public 26037748cd8SNickeau function processEolToEndStack($attributes = []) 26137748cd8SNickeau { 26237748cd8SNickeau 26337748cd8SNickeau \syntax_plugin_combo_para::fromEolToParagraphUntilEndOfStack($this, $attributes); 26437748cd8SNickeau 26537748cd8SNickeau } 26637748cd8SNickeau 26737748cd8SNickeau /** 26837748cd8SNickeau * Delete the call where the pointer is 26937748cd8SNickeau * And go to the previous position 27037748cd8SNickeau * 27137748cd8SNickeau * This function can be used in a next loop 27237748cd8SNickeau * 27337748cd8SNickeau * @return Call the deleted call 27437748cd8SNickeau */ 27537748cd8SNickeau public 276*1fa8c418SNickeau function deleteActualCallAndPrevious(): ?Call 27737748cd8SNickeau { 27837748cd8SNickeau 27937748cd8SNickeau $actualCall = $this->getActualCall(); 28037748cd8SNickeau 28137748cd8SNickeau $offset = $this->getActualOffset(); 28237748cd8SNickeau array_splice($this->callStack, $offset, 1, []); 28337748cd8SNickeau 28437748cd8SNickeau /** 28537748cd8SNickeau * Move to the next element (array splice reset the pointer) 28637748cd8SNickeau * if there is a eol as, we delete it 28737748cd8SNickeau * otherwise we may end up with two eol 28837748cd8SNickeau * and this is an empty paragraph 28937748cd8SNickeau */ 29037748cd8SNickeau $this->moveToOffset($offset); 29137748cd8SNickeau if (!$this->isPointerAtEnd()) { 29237748cd8SNickeau if ($this->getActualCall()->getTagName() == 'eol') { 29337748cd8SNickeau array_splice($this->callStack, $offset, 1, []); 29437748cd8SNickeau } 29537748cd8SNickeau } 29637748cd8SNickeau 29737748cd8SNickeau /** 29837748cd8SNickeau * Move to the previous element 29937748cd8SNickeau */ 30037748cd8SNickeau $this->moveToOffset($offset - 1); 30137748cd8SNickeau 30237748cd8SNickeau return $actualCall; 30337748cd8SNickeau 30437748cd8SNickeau } 30537748cd8SNickeau 30637748cd8SNickeau /** 30737748cd8SNickeau * @return Call - get a reference to the actual call 30837748cd8SNickeau * This function returns a {@link Call call} object 30937748cd8SNickeau * by reference, meaning that every update will also modify the element 31037748cd8SNickeau * in the stack 31137748cd8SNickeau */ 31237748cd8SNickeau public 31337748cd8SNickeau function getActualCall() 31437748cd8SNickeau { 31537748cd8SNickeau if ($this->endWasReached) { 31637748cd8SNickeau LogUtility::msg("The actual call cannot be ask because the end of the stack was reached", LogUtility::LVL_MSG_ERROR, self::CANONICAL); 31737748cd8SNickeau return null; 31837748cd8SNickeau } 31937748cd8SNickeau if ($this->startWasReached) { 32037748cd8SNickeau LogUtility::msg("The actual call cannot be ask because the start of the stack was reached", LogUtility::LVL_MSG_ERROR, self::CANONICAL); 32137748cd8SNickeau return null; 32237748cd8SNickeau } 32337748cd8SNickeau $actualCallKey = key($this->callStack); 32437748cd8SNickeau $actualCallArray = &$this->callStack[$actualCallKey]; 32537748cd8SNickeau return new Call($actualCallArray, $actualCallKey); 32637748cd8SNickeau 32737748cd8SNickeau } 32837748cd8SNickeau 32937748cd8SNickeau /** 33037748cd8SNickeau * put the pointer one position further 33137748cd8SNickeau * false if at the end 33237748cd8SNickeau * @return false|Call 33337748cd8SNickeau */ 33437748cd8SNickeau public 33537748cd8SNickeau function next() 33637748cd8SNickeau { 33737748cd8SNickeau if ($this->startWasReached) { 33837748cd8SNickeau $this->startWasReached = false; 33937748cd8SNickeau $result = reset($this->callStack); 34037748cd8SNickeau if ($result === false) { 34137748cd8SNickeau return false; 34237748cd8SNickeau } else { 34337748cd8SNickeau return $this->getActualCall(); 34437748cd8SNickeau } 34537748cd8SNickeau } else { 34637748cd8SNickeau $next = next($this->callStack); 34737748cd8SNickeau if ($next === false) { 34837748cd8SNickeau $this->endWasReached = true; 34937748cd8SNickeau return $next; 35037748cd8SNickeau } else { 35137748cd8SNickeau return $this->getActualCall(); 35237748cd8SNickeau } 35337748cd8SNickeau } 35437748cd8SNickeau 35537748cd8SNickeau } 35637748cd8SNickeau 35737748cd8SNickeau /** 35837748cd8SNickeau * 35937748cd8SNickeau * From an exit call, move the corresponding Opening call 36037748cd8SNickeau * 36137748cd8SNickeau * This is used mostly in {@link SyntaxPlugin::handle()} from a {@link DOKU_LEXER_EXIT} 36237748cd8SNickeau * to retrieve the {@link DOKU_LEXER_ENTER} call 36337748cd8SNickeau * 36437748cd8SNickeau * @return bool|Call 36537748cd8SNickeau */ 36637748cd8SNickeau public 36737748cd8SNickeau function moveToPreviousCorrespondingOpeningCall() 36837748cd8SNickeau { 36937748cd8SNickeau 37037748cd8SNickeau /** 37137748cd8SNickeau * Edgde case 37237748cd8SNickeau */ 37337748cd8SNickeau if(empty($this->callStack)){ 37437748cd8SNickeau return false; 37537748cd8SNickeau } 37637748cd8SNickeau 37737748cd8SNickeau if (!$this->endWasReached) { 37837748cd8SNickeau $actualCall = $this->getActualCall(); 37937748cd8SNickeau $actualState = $actualCall->getState(); 38037748cd8SNickeau if ($actualState != DOKU_LEXER_EXIT) { 38137748cd8SNickeau /** 38237748cd8SNickeau * Check if we are at the end of the stack 38337748cd8SNickeau */ 38437748cd8SNickeau LogUtility::msg("You are not at the end of stack and you are not on a opening tag, you can't ask for the opening tag." . $actualState, LogUtility::LVL_MSG_ERROR, "support"); 38537748cd8SNickeau return false; 38637748cd8SNickeau } 38737748cd8SNickeau } 38837748cd8SNickeau $level = 0; 38937748cd8SNickeau while ($actualCall = $this->previous()) { 39037748cd8SNickeau 39137748cd8SNickeau $state = $actualCall->getState(); 39237748cd8SNickeau switch ($state) { 39337748cd8SNickeau case DOKU_LEXER_ENTER: 39437748cd8SNickeau $level++; 39537748cd8SNickeau break; 39637748cd8SNickeau case DOKU_LEXER_EXIT: 39737748cd8SNickeau $level--; 39837748cd8SNickeau break; 39937748cd8SNickeau } 40037748cd8SNickeau if ($level > 0) { 40137748cd8SNickeau break; 40237748cd8SNickeau } 40337748cd8SNickeau 40437748cd8SNickeau } 40537748cd8SNickeau if ($level > 0) { 40637748cd8SNickeau return $actualCall; 40737748cd8SNickeau } else { 40837748cd8SNickeau return false; 40937748cd8SNickeau } 41037748cd8SNickeau } 41137748cd8SNickeau 41237748cd8SNickeau 41337748cd8SNickeau /** 41437748cd8SNickeau * @return Call|false the previous call or false if there is no more previous call 41537748cd8SNickeau */ 41637748cd8SNickeau public 41737748cd8SNickeau function previous() 41837748cd8SNickeau { 41937748cd8SNickeau if ($this->endWasReached) { 42037748cd8SNickeau $this->endWasReached = false; 42137748cd8SNickeau $return = end($this->callStack); 42237748cd8SNickeau if ($return == false) { 42337748cd8SNickeau // empty array (first call on the stack) 42437748cd8SNickeau return false; 42537748cd8SNickeau } else { 42637748cd8SNickeau return $this->getActualCall(); 42737748cd8SNickeau } 42837748cd8SNickeau } else { 42937748cd8SNickeau $prev = prev($this->callStack); 43037748cd8SNickeau if ($prev === false) { 43137748cd8SNickeau $this->startWasReached = true; 43237748cd8SNickeau return $prev; 43337748cd8SNickeau } else { 43437748cd8SNickeau return $this->getActualCall(); 43537748cd8SNickeau } 43637748cd8SNickeau } 43737748cd8SNickeau 43837748cd8SNickeau } 43937748cd8SNickeau 44037748cd8SNickeau /** 44137748cd8SNickeau * Return the first enter or special child call (ie a tag) 44237748cd8SNickeau * @return Call|false 44337748cd8SNickeau */ 44437748cd8SNickeau public 44537748cd8SNickeau function moveToFirstChildTag() 44637748cd8SNickeau { 44737748cd8SNickeau $found = false; 44837748cd8SNickeau while ($this->next()) { 44937748cd8SNickeau 45037748cd8SNickeau $actualCall = $this->getActualCall(); 45137748cd8SNickeau $state = $actualCall->getState(); 45237748cd8SNickeau switch ($state) { 45337748cd8SNickeau case DOKU_LEXER_ENTER: 45437748cd8SNickeau case DOKU_LEXER_SPECIAL: 45537748cd8SNickeau $found = true; 45637748cd8SNickeau break 2; 45737748cd8SNickeau case DOKU_LEXER_EXIT: 45837748cd8SNickeau break 2; 45937748cd8SNickeau } 46037748cd8SNickeau 46137748cd8SNickeau } 46237748cd8SNickeau if ($found) { 46337748cd8SNickeau return $this->getActualCall(); 46437748cd8SNickeau } else { 46537748cd8SNickeau return false; 46637748cd8SNickeau } 46737748cd8SNickeau 46837748cd8SNickeau 46937748cd8SNickeau } 47037748cd8SNickeau 47137748cd8SNickeau /** 47237748cd8SNickeau * The end is the one after the last element 47337748cd8SNickeau */ 47437748cd8SNickeau public 47537748cd8SNickeau function moveToEnd() 47637748cd8SNickeau { 47737748cd8SNickeau if ($this->startWasReached) { 47837748cd8SNickeau $this->startWasReached = false; 47937748cd8SNickeau } 48037748cd8SNickeau end($this->callStack); 48137748cd8SNickeau $this->next(); 48237748cd8SNickeau } 48337748cd8SNickeau 48437748cd8SNickeau /** 48537748cd8SNickeau * On the same level 48637748cd8SNickeau */ 48737748cd8SNickeau public 48837748cd8SNickeau function moveToNextSiblingTag() 48937748cd8SNickeau { 49037748cd8SNickeau 49137748cd8SNickeau /** 49237748cd8SNickeau * Edgde case 49337748cd8SNickeau */ 49437748cd8SNickeau if(empty($this->callStack)){ 49537748cd8SNickeau return false; 49637748cd8SNickeau } 49737748cd8SNickeau 49837748cd8SNickeau $actualCall = $this->getActualCall(); 49937748cd8SNickeau $actualState = $actualCall->getState(); 50037748cd8SNickeau if (!in_array($actualState, CallStack::TAG_STATE)) { 50137748cd8SNickeau LogUtility::msg("A next sibling can be asked only from a tag call. The state is " . $actualState, LogUtility::LVL_MSG_ERROR, "support"); 50237748cd8SNickeau return false; 50337748cd8SNickeau } 50437748cd8SNickeau $level = 0; 50537748cd8SNickeau while ($this->next()) { 50637748cd8SNickeau 50737748cd8SNickeau $actualCall = $this->getActualCall(); 50837748cd8SNickeau $state = $actualCall->getState(); 50937748cd8SNickeau switch ($state) { 51037748cd8SNickeau case DOKU_LEXER_ENTER: 51137748cd8SNickeau case DOKU_LEXER_SPECIAL: 51237748cd8SNickeau $level++; 51337748cd8SNickeau break; 51437748cd8SNickeau case DOKU_LEXER_EXIT: 51537748cd8SNickeau $level--; 51637748cd8SNickeau break; 51737748cd8SNickeau } 51837748cd8SNickeau 51937748cd8SNickeau if ($level == 0 && in_array($state, self::TAG_STATE)) { 52037748cd8SNickeau break; 52137748cd8SNickeau } 52237748cd8SNickeau } 52337748cd8SNickeau if ($level == 0 && !$this->endWasReached) { 52437748cd8SNickeau return $this->getActualCall(); 52537748cd8SNickeau } else { 52637748cd8SNickeau return false; 52737748cd8SNickeau } 52837748cd8SNickeau } 52937748cd8SNickeau 53037748cd8SNickeau /** 53137748cd8SNickeau * @param Call $call 53237748cd8SNickeau * @return Call the inserted call 53337748cd8SNickeau */ 53437748cd8SNickeau public 535*1fa8c418SNickeau function insertBefore(Call $call): Call 53637748cd8SNickeau { 53737748cd8SNickeau if ($this->endWasReached) { 53837748cd8SNickeau 53937748cd8SNickeau $this->callStack[] = $call->toCallArray(); 54037748cd8SNickeau 54137748cd8SNickeau } else { 54237748cd8SNickeau 54337748cd8SNickeau $offset = $this->getActualOffset(); 54437748cd8SNickeau array_splice($this->callStack, $offset, 0, [$call->toCallArray()]); 54537748cd8SNickeau // array splice reset the pointer 54637748cd8SNickeau // we move it to the actual element (ie the key is offset +1) 54737748cd8SNickeau $this->moveToOffset($offset + 1); 54837748cd8SNickeau 54937748cd8SNickeau } 55037748cd8SNickeau return $call; 55137748cd8SNickeau } 55237748cd8SNickeau 55337748cd8SNickeau /** 55437748cd8SNickeau * Move pointer by offset 55537748cd8SNickeau * @param $offset 55637748cd8SNickeau */ 55737748cd8SNickeau private 55837748cd8SNickeau function moveToOffset($offset) 55937748cd8SNickeau { 56037748cd8SNickeau $this->resetPointer(); 56137748cd8SNickeau for ($i = 0; $i < $offset; $i++) { 56237748cd8SNickeau $result = $this->next(); 56337748cd8SNickeau if ($result === false) { 56437748cd8SNickeau break; 56537748cd8SNickeau } 56637748cd8SNickeau } 56737748cd8SNickeau } 56837748cd8SNickeau 56937748cd8SNickeau /** 57037748cd8SNickeau * Move pointer by key 57137748cd8SNickeau * @param $targetKey 57237748cd8SNickeau */ 57337748cd8SNickeau private 57437748cd8SNickeau function moveToKey($targetKey) 57537748cd8SNickeau { 57637748cd8SNickeau $this->resetPointer(); 57737748cd8SNickeau for ($i = 0; $i < $targetKey; $i++) { 57837748cd8SNickeau next($this->callStack); 57937748cd8SNickeau } 58037748cd8SNickeau $actualKey = key($this->callStack); 58137748cd8SNickeau if ($actualKey != $targetKey) { 58237748cd8SNickeau LogUtility::msg("The target key ($targetKey) is not equal to the actual key ($actualKey). The moveToKey was not successful"); 58337748cd8SNickeau } 58437748cd8SNickeau } 58537748cd8SNickeau 58637748cd8SNickeau /** 58737748cd8SNickeau * Insert After. The pointer stays at the current state. 58837748cd8SNickeau * If you don't need to process the call that you just 58937748cd8SNickeau * inserted, you may want to call {@link CallStack::next()} 59037748cd8SNickeau * @param Call $call 59137748cd8SNickeau */ 59237748cd8SNickeau public 59337748cd8SNickeau function insertAfter($call) 59437748cd8SNickeau { 59537748cd8SNickeau $actualKey = key($this->callStack); 59637748cd8SNickeau if ($actualKey == null) { 59737748cd8SNickeau if ($this->endWasReached == true) { 59837748cd8SNickeau $this->callStack[] = $call->toCallArray(); 59937748cd8SNickeau } else { 60037748cd8SNickeau LogUtility::msg("Callstack: Actual key is null, we can't insert after null"); 60137748cd8SNickeau } 60237748cd8SNickeau } else { 60337748cd8SNickeau $offset = array_search($actualKey, array_keys($this->callStack), true); 60437748cd8SNickeau array_splice($this->callStack, $offset + 1, 0, [$call->toCallArray()]); 60537748cd8SNickeau // array splice reset the pointer 60637748cd8SNickeau // we move it to the actual element 60737748cd8SNickeau $this->moveToKey($actualKey); 60837748cd8SNickeau } 60937748cd8SNickeau } 61037748cd8SNickeau 61137748cd8SNickeau public 61237748cd8SNickeau function getActualKey() 61337748cd8SNickeau { 61437748cd8SNickeau return key($this->callStack); 61537748cd8SNickeau } 61637748cd8SNickeau 61737748cd8SNickeau /** 61837748cd8SNickeau * Insert an EOL call if the next call is not an EOL 61937748cd8SNickeau * This is to enforce an new paragraph 62037748cd8SNickeau */ 62137748cd8SNickeau public 62237748cd8SNickeau function insertEolIfNextCallIsNotEolOrBlock() 62337748cd8SNickeau { 62437748cd8SNickeau if (!$this->isPointerAtEnd()) { 62537748cd8SNickeau $nextCall = $this->next(); 62637748cd8SNickeau if ($nextCall != false) { 62737748cd8SNickeau if ($nextCall->getTagName() != "eol" && $nextCall->getDisplay() != "block") { 62837748cd8SNickeau $this->insertBefore( 62937748cd8SNickeau Call::createNativeCall("eol") 63037748cd8SNickeau ); 63137748cd8SNickeau // move on the eol 63237748cd8SNickeau $this->previous(); 63337748cd8SNickeau } 63437748cd8SNickeau // move back 63537748cd8SNickeau $this->previous(); 63637748cd8SNickeau } 63737748cd8SNickeau } 63837748cd8SNickeau } 63937748cd8SNickeau 64037748cd8SNickeau private 64137748cd8SNickeau function isPointerAtEnd() 64237748cd8SNickeau { 64337748cd8SNickeau return $this->endWasReached; 64437748cd8SNickeau } 64537748cd8SNickeau 64637748cd8SNickeau public 64737748cd8SNickeau function &getHandler() 64837748cd8SNickeau { 64937748cd8SNickeau return $this->handler; 65037748cd8SNickeau } 65137748cd8SNickeau 65237748cd8SNickeau /** 65337748cd8SNickeau * Return The offset (not the key): 65437748cd8SNickeau * * starting at 0 for the first element 65537748cd8SNickeau * * 1 for the second ... 65637748cd8SNickeau * 65737748cd8SNickeau * @return false|int|string 65837748cd8SNickeau */ 65937748cd8SNickeau private 66037748cd8SNickeau function getActualOffset() 66137748cd8SNickeau { 66237748cd8SNickeau $actualKey = key($this->callStack); 66337748cd8SNickeau return array_search($actualKey, array_keys($this->callStack), true); 66437748cd8SNickeau } 66537748cd8SNickeau 66637748cd8SNickeau private 66737748cd8SNickeau function resetPointer() 66837748cd8SNickeau { 66937748cd8SNickeau reset($this->callStack); 67037748cd8SNickeau $this->endWasReached = false; 67137748cd8SNickeau } 67237748cd8SNickeau 67337748cd8SNickeau public 67437748cd8SNickeau function moveToStart() 67537748cd8SNickeau { 67637748cd8SNickeau $this->resetPointer(); 67737748cd8SNickeau $this->previous(); 67837748cd8SNickeau } 67937748cd8SNickeau 68037748cd8SNickeau /** 68137748cd8SNickeau * @return Call|false the parent call or false if there is no parent 68237748cd8SNickeau * If you are on an {@link DOKU_LEXER_EXIT} state, you should 68337748cd8SNickeau * call first the {@link CallStack::moveToPreviousCorrespondingOpeningCall()} 68437748cd8SNickeau */ 68537748cd8SNickeau public function moveToParent() 68637748cd8SNickeau { 68737748cd8SNickeau 68837748cd8SNickeau /** 68937748cd8SNickeau * Case when we start from the exit state element 69037748cd8SNickeau * We go first to the opening tag 69137748cd8SNickeau * because the algorithm is level based. 69237748cd8SNickeau * 69337748cd8SNickeau * When the end is reached, there is no call 69437748cd8SNickeau * (this not the {@link end php end} but one further 69537748cd8SNickeau */ 69637748cd8SNickeau if (!$this->endWasReached && !$this->startWasReached && $this->getActualCall()->getState() == DOKU_LEXER_EXIT) { 69737748cd8SNickeau 69837748cd8SNickeau $this->moveToPreviousCorrespondingOpeningCall(); 69937748cd8SNickeau 70037748cd8SNickeau } 70137748cd8SNickeau 70237748cd8SNickeau 70337748cd8SNickeau /** 70437748cd8SNickeau * We are in a parent when the tree level is negative 70537748cd8SNickeau */ 70637748cd8SNickeau $treeLevel = 0; 70737748cd8SNickeau while ($actualCall = $this->previous()) { 70837748cd8SNickeau 70937748cd8SNickeau /** 71037748cd8SNickeau * Add 71137748cd8SNickeau * would become a parent on its enter state 71237748cd8SNickeau */ 71337748cd8SNickeau $actualCallState = $actualCall->getState(); 71437748cd8SNickeau switch ($actualCallState) { 71537748cd8SNickeau case DOKU_LEXER_ENTER: 71637748cd8SNickeau $treeLevel = $treeLevel - 1; 71737748cd8SNickeau break; 71837748cd8SNickeau case DOKU_LEXER_EXIT: 71937748cd8SNickeau /** 72037748cd8SNickeau * When the tag has a sibling with an exit tag 72137748cd8SNickeau */ 72237748cd8SNickeau $treeLevel = $treeLevel + 1; 72337748cd8SNickeau break; 72437748cd8SNickeau } 72537748cd8SNickeau 72637748cd8SNickeau /** 72737748cd8SNickeau * The breaking statement 72837748cd8SNickeau */ 72937748cd8SNickeau if ($treeLevel < 0) { 73037748cd8SNickeau break; 73137748cd8SNickeau } 73237748cd8SNickeau 73337748cd8SNickeau } 73437748cd8SNickeau return $actualCall; 73537748cd8SNickeau 73637748cd8SNickeau 73737748cd8SNickeau } 73837748cd8SNickeau 73937748cd8SNickeau /** 74037748cd8SNickeau * Delete the anchor link to the image (ie the lightbox) 74137748cd8SNickeau * 74237748cd8SNickeau * This is used in navigation and for instance 74337748cd8SNickeau * in heading 74437748cd8SNickeau */ 74537748cd8SNickeau public function processNoLinkOnImageToEndStack() 74637748cd8SNickeau { 74737748cd8SNickeau while ($this->next()) { 74837748cd8SNickeau $actualCall = $this->getActualCall(); 74937748cd8SNickeau if ($actualCall->getTagName() == syntax_plugin_combo_media::TAG) { 75037748cd8SNickeau $actualCall->addAttribute(MediaLink::LINKING_KEY, MediaLink::LINKING_NOLINK_VALUE); 75137748cd8SNickeau } 75237748cd8SNickeau } 75337748cd8SNickeau } 75437748cd8SNickeau 75537748cd8SNickeau /** 75637748cd8SNickeau * Append instructions to the callstack (ie at the end) 75737748cd8SNickeau * @param array $instructions 75837748cd8SNickeau */ 75937748cd8SNickeau public function appendInstructionsFromNativeArray($instructions) 76037748cd8SNickeau { 76137748cd8SNickeau array_splice($this->callStack, count($this->callStack), 0, $instructions); 76237748cd8SNickeau } 76337748cd8SNickeau 76437748cd8SNickeau /** 76537748cd8SNickeau * @param Call $call 76637748cd8SNickeau */ 76737748cd8SNickeau public function appendCallAtTheEnd($call) 76837748cd8SNickeau { 76937748cd8SNickeau $this->callStack[] = $call->toCallArray(); 77037748cd8SNickeau } 77137748cd8SNickeau 77237748cd8SNickeau public function moveToPreviousSiblingTag() 77337748cd8SNickeau { 77437748cd8SNickeau /** 77537748cd8SNickeau * Edge case 77637748cd8SNickeau */ 77737748cd8SNickeau if(empty($this->callStack)){ 77837748cd8SNickeau return false; 77937748cd8SNickeau } 78037748cd8SNickeau 78137748cd8SNickeau if (!$this->endWasReached) { 78237748cd8SNickeau $actualCall = $this->getActualCall(); 78337748cd8SNickeau $actualState = $actualCall->getState(); 78437748cd8SNickeau if (!in_array($actualState, CallStack::TAG_STATE)) { 78537748cd8SNickeau LogUtility::msg("A previous sibling can be asked only from a tag call. The state is " . $actualState, LogUtility::LVL_MSG_ERROR, "support"); 78637748cd8SNickeau return false; 78737748cd8SNickeau } 78837748cd8SNickeau } 78937748cd8SNickeau $level = 0; 79037748cd8SNickeau while ($this->previous()) { 79137748cd8SNickeau 79237748cd8SNickeau $actualCall = $this->getActualCall(); 79337748cd8SNickeau $state = $actualCall->getState(); 79437748cd8SNickeau switch ($state) { 79537748cd8SNickeau case DOKU_LEXER_ENTER: 79637748cd8SNickeau case DOKU_LEXER_SPECIAL: 79737748cd8SNickeau $level++; 79837748cd8SNickeau break; 79937748cd8SNickeau case DOKU_LEXER_EXIT: 80037748cd8SNickeau $level--; 80137748cd8SNickeau break; 80237748cd8SNickeau } 80337748cd8SNickeau 80437748cd8SNickeau if ($level == 0 && in_array($state, self::TAG_STATE)) { 80537748cd8SNickeau break; 80637748cd8SNickeau } 80737748cd8SNickeau } 80837748cd8SNickeau if ($level == 0 && !$this->startWasReached) { 80937748cd8SNickeau return $this->getActualCall(); 81037748cd8SNickeau } else { 81137748cd8SNickeau return false; 81237748cd8SNickeau } 81337748cd8SNickeau } 81437748cd8SNickeau 81537748cd8SNickeau /** 81637748cd8SNickeau * Delete all calls after the passed call 81737748cd8SNickeau * 81837748cd8SNickeau * It's used in syntax generator that: 81937748cd8SNickeau * * capture the children callstack at the end, 82037748cd8SNickeau * * delete it 82137748cd8SNickeau * * and use it to generate more calls. 82237748cd8SNickeau * 82337748cd8SNickeau * @param Call $call 82437748cd8SNickeau */ 82537748cd8SNickeau public function deleteAllCallsAfter(Call $call) 82637748cd8SNickeau { 82737748cd8SNickeau $key = $call->getKey(); 82837748cd8SNickeau $offset = array_search($key, array_keys($this->callStack), true); 82937748cd8SNickeau if ($offset !== false) { 83037748cd8SNickeau /** 83137748cd8SNickeau * We delete from the next 83237748cd8SNickeau * {@link array_splice()} delete also the given offset 83337748cd8SNickeau */ 83437748cd8SNickeau array_splice($this->callStack, $offset + 1); 83537748cd8SNickeau } else { 83637748cd8SNickeau LogUtility::msg("The call ($call) could not be found in the callStack. We couldn't therefore delete the calls after"); 83737748cd8SNickeau } 83837748cd8SNickeau 83937748cd8SNickeau } 84037748cd8SNickeau 84137748cd8SNickeau /** 84237748cd8SNickeau * @param Call[] $calls 84337748cd8SNickeau */ 84437748cd8SNickeau public function appendInstructionsFromCallObjects($calls) 84537748cd8SNickeau { 84637748cd8SNickeau foreach($calls as $call){ 84737748cd8SNickeau $this->appendCallAtTheEnd($call); 84837748cd8SNickeau } 84937748cd8SNickeau 85037748cd8SNickeau } 85137748cd8SNickeau 85237748cd8SNickeau 85337748cd8SNickeau} 854