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 181*4cadd4f8SNickeau static function createFromMarkup($marki): CallStack 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 197*4cadd4f8SNickeau public static function createEmpty(): CallStack 198*4cadd4f8SNickeau { 199*4cadd4f8SNickeau $emptyHandler = new class extends \Doku_Handler { 200*4cadd4f8SNickeau public $calls = []; 201*4cadd4f8SNickeau 202*4cadd4f8SNickeau public function getCallWriter(): object 203*4cadd4f8SNickeau { 204*4cadd4f8SNickeau return new class { 205*4cadd4f8SNickeau public $calls = array(); 206*4cadd4f8SNickeau }; 207*4cadd4f8SNickeau } 208*4cadd4f8SNickeau }; 209*4cadd4f8SNickeau return new CallStack($emptyHandler); 210*4cadd4f8SNickeau } 211*4cadd4f8SNickeau 212*4cadd4f8SNickeau public static function createFromInstructions(?array $callStackArray): CallStack 213*4cadd4f8SNickeau { 214*4cadd4f8SNickeau return CallStack::createEmpty() 215*4cadd4f8SNickeau ->appendInstructionsFromNativeArray($callStackArray); 216*4cadd4f8SNickeau 217*4cadd4f8SNickeau } 218*4cadd4f8SNickeau 219*4cadd4f8SNickeau 22037748cd8SNickeau /** 22137748cd8SNickeau * Reset the pointer 22237748cd8SNickeau */ 22337748cd8SNickeau public 22437748cd8SNickeau function closeAndResetPointer() 22537748cd8SNickeau { 22637748cd8SNickeau reset($this->callStack); 22737748cd8SNickeau } 22837748cd8SNickeau 22937748cd8SNickeau /** 23037748cd8SNickeau * Delete from the call stack 23137748cd8SNickeau * @param $calls 23237748cd8SNickeau * @param $start 23337748cd8SNickeau * @param $end 23437748cd8SNickeau */ 23537748cd8SNickeau public 23637748cd8SNickeau static function deleteCalls(&$calls, $start, $end) 23737748cd8SNickeau { 23837748cd8SNickeau for ($i = $start; $i <= $end; $i++) { 23937748cd8SNickeau unset($calls[$i]); 24037748cd8SNickeau } 24137748cd8SNickeau } 24237748cd8SNickeau 24337748cd8SNickeau /** 24437748cd8SNickeau * @param array $calls 24537748cd8SNickeau * @param integer $position 24637748cd8SNickeau * @param array $callStackToInsert 24737748cd8SNickeau */ 24837748cd8SNickeau public 24937748cd8SNickeau static function insertCallStackUpWards(&$calls, $position, $callStackToInsert) 25037748cd8SNickeau { 25137748cd8SNickeau 25237748cd8SNickeau array_splice($calls, $position, 0, $callStackToInsert); 25337748cd8SNickeau 25437748cd8SNickeau } 25537748cd8SNickeau 25637748cd8SNickeau /** 25737748cd8SNickeau * A callstack pointer based implementation 25837748cd8SNickeau * that starts at the end 259*4cadd4f8SNickeau * @param mixed|Doku_Handler $handler - mixed because we test if the handler passed is not the good one (It can happen with third plugin) 26037748cd8SNickeau * @return CallStack 26137748cd8SNickeau */ 26237748cd8SNickeau public 263*4cadd4f8SNickeau static function createFromHandler(&$handler): CallStack 26437748cd8SNickeau { 26537748cd8SNickeau return new CallStack($handler); 26637748cd8SNickeau } 26737748cd8SNickeau 26837748cd8SNickeau 26937748cd8SNickeau /** 27037748cd8SNickeau * Process the EOL call to the end of stack 27137748cd8SNickeau * replacing them with paragraph call 27237748cd8SNickeau * 27337748cd8SNickeau * A sort of {@link Block::process()} but only from a tag 27437748cd8SNickeau * to the end of the current stack 27537748cd8SNickeau * 27637748cd8SNickeau * This function is used basically in the {@link DOKU_LEXER_EXIT} 27737748cd8SNickeau * state of {@link SyntaxPlugin::handle()} to create paragraph 27837748cd8SNickeau * with the class given as parameter 27937748cd8SNickeau * 280*4cadd4f8SNickeau * @param array $attributes - the attributes in an callstack array form passed to the paragraph 28137748cd8SNickeau */ 28237748cd8SNickeau public 283*4cadd4f8SNickeau function processEolToEndStack(array $attributes = []) 28437748cd8SNickeau { 28537748cd8SNickeau 28637748cd8SNickeau \syntax_plugin_combo_para::fromEolToParagraphUntilEndOfStack($this, $attributes); 28737748cd8SNickeau 28837748cd8SNickeau } 28937748cd8SNickeau 29037748cd8SNickeau /** 29137748cd8SNickeau * Delete the call where the pointer is 29237748cd8SNickeau * And go to the previous position 29337748cd8SNickeau * 29437748cd8SNickeau * This function can be used in a next loop 29537748cd8SNickeau * 29637748cd8SNickeau * @return Call the deleted call 29737748cd8SNickeau */ 29837748cd8SNickeau public 2991fa8c418SNickeau function deleteActualCallAndPrevious(): ?Call 30037748cd8SNickeau { 30137748cd8SNickeau 30237748cd8SNickeau $actualCall = $this->getActualCall(); 30337748cd8SNickeau 30437748cd8SNickeau $offset = $this->getActualOffset(); 30537748cd8SNickeau array_splice($this->callStack, $offset, 1, []); 30637748cd8SNickeau 30737748cd8SNickeau /** 30837748cd8SNickeau * Move to the next element (array splice reset the pointer) 30937748cd8SNickeau * if there is a eol as, we delete it 31037748cd8SNickeau * otherwise we may end up with two eol 31137748cd8SNickeau * and this is an empty paragraph 31237748cd8SNickeau */ 31337748cd8SNickeau $this->moveToOffset($offset); 31437748cd8SNickeau if (!$this->isPointerAtEnd()) { 31537748cd8SNickeau if ($this->getActualCall()->getTagName() == 'eol') { 31637748cd8SNickeau array_splice($this->callStack, $offset, 1, []); 31737748cd8SNickeau } 31837748cd8SNickeau } 31937748cd8SNickeau 32037748cd8SNickeau /** 32137748cd8SNickeau * Move to the previous element 32237748cd8SNickeau */ 32337748cd8SNickeau $this->moveToOffset($offset - 1); 32437748cd8SNickeau 32537748cd8SNickeau return $actualCall; 32637748cd8SNickeau 32737748cd8SNickeau } 32837748cd8SNickeau 32937748cd8SNickeau /** 33037748cd8SNickeau * @return Call - get a reference to the actual call 33137748cd8SNickeau * This function returns a {@link Call call} object 33237748cd8SNickeau * by reference, meaning that every update will also modify the element 33337748cd8SNickeau * in the stack 33437748cd8SNickeau */ 33537748cd8SNickeau public 336*4cadd4f8SNickeau function getActualCall(): ?Call 33737748cd8SNickeau { 33837748cd8SNickeau if ($this->endWasReached) { 33937748cd8SNickeau LogUtility::msg("The actual call cannot be ask because the end of the stack was reached", LogUtility::LVL_MSG_ERROR, self::CANONICAL); 34037748cd8SNickeau return null; 34137748cd8SNickeau } 34237748cd8SNickeau if ($this->startWasReached) { 34337748cd8SNickeau LogUtility::msg("The actual call cannot be ask because the start of the stack was reached", LogUtility::LVL_MSG_ERROR, self::CANONICAL); 34437748cd8SNickeau return null; 34537748cd8SNickeau } 34637748cd8SNickeau $actualCallKey = key($this->callStack); 34737748cd8SNickeau $actualCallArray = &$this->callStack[$actualCallKey]; 34837748cd8SNickeau return new Call($actualCallArray, $actualCallKey); 34937748cd8SNickeau 35037748cd8SNickeau } 35137748cd8SNickeau 35237748cd8SNickeau /** 35337748cd8SNickeau * put the pointer one position further 35437748cd8SNickeau * false if at the end 35537748cd8SNickeau * @return false|Call 35637748cd8SNickeau */ 35737748cd8SNickeau public 35837748cd8SNickeau function next() 35937748cd8SNickeau { 36037748cd8SNickeau if ($this->startWasReached) { 36137748cd8SNickeau $this->startWasReached = false; 36237748cd8SNickeau $result = reset($this->callStack); 36337748cd8SNickeau if ($result === false) { 36437748cd8SNickeau return false; 36537748cd8SNickeau } else { 36637748cd8SNickeau return $this->getActualCall(); 36737748cd8SNickeau } 36837748cd8SNickeau } else { 36937748cd8SNickeau $next = next($this->callStack); 37037748cd8SNickeau if ($next === false) { 37137748cd8SNickeau $this->endWasReached = true; 37237748cd8SNickeau return $next; 37337748cd8SNickeau } else { 37437748cd8SNickeau return $this->getActualCall(); 37537748cd8SNickeau } 37637748cd8SNickeau } 37737748cd8SNickeau 37837748cd8SNickeau } 37937748cd8SNickeau 38037748cd8SNickeau /** 38137748cd8SNickeau * 38237748cd8SNickeau * From an exit call, move the corresponding Opening call 38337748cd8SNickeau * 38437748cd8SNickeau * This is used mostly in {@link SyntaxPlugin::handle()} from a {@link DOKU_LEXER_EXIT} 38537748cd8SNickeau * to retrieve the {@link DOKU_LEXER_ENTER} call 38637748cd8SNickeau * 38737748cd8SNickeau * @return bool|Call 38837748cd8SNickeau */ 38937748cd8SNickeau public 39037748cd8SNickeau function moveToPreviousCorrespondingOpeningCall() 39137748cd8SNickeau { 39237748cd8SNickeau 39337748cd8SNickeau /** 394*4cadd4f8SNickeau * Edge case 39537748cd8SNickeau */ 39637748cd8SNickeau if (empty($this->callStack)) { 39737748cd8SNickeau return false; 39837748cd8SNickeau } 39937748cd8SNickeau 40037748cd8SNickeau if (!$this->endWasReached) { 40137748cd8SNickeau $actualCall = $this->getActualCall(); 40237748cd8SNickeau $actualState = $actualCall->getState(); 40337748cd8SNickeau if ($actualState != DOKU_LEXER_EXIT) { 40437748cd8SNickeau /** 40537748cd8SNickeau * Check if we are at the end of the stack 40637748cd8SNickeau */ 40737748cd8SNickeau 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"); 40837748cd8SNickeau return false; 40937748cd8SNickeau } 41037748cd8SNickeau } 41137748cd8SNickeau $level = 0; 41237748cd8SNickeau while ($actualCall = $this->previous()) { 41337748cd8SNickeau 41437748cd8SNickeau $state = $actualCall->getState(); 41537748cd8SNickeau switch ($state) { 41637748cd8SNickeau case DOKU_LEXER_ENTER: 41737748cd8SNickeau $level++; 41837748cd8SNickeau break; 41937748cd8SNickeau case DOKU_LEXER_EXIT: 42037748cd8SNickeau $level--; 42137748cd8SNickeau break; 42237748cd8SNickeau } 42337748cd8SNickeau if ($level > 0) { 42437748cd8SNickeau break; 42537748cd8SNickeau } 42637748cd8SNickeau 42737748cd8SNickeau } 42837748cd8SNickeau if ($level > 0) { 42937748cd8SNickeau return $actualCall; 43037748cd8SNickeau } else { 43137748cd8SNickeau return false; 43237748cd8SNickeau } 43337748cd8SNickeau } 43437748cd8SNickeau 43537748cd8SNickeau 43637748cd8SNickeau /** 43737748cd8SNickeau * @return Call|false the previous call or false if there is no more previous call 43837748cd8SNickeau */ 43937748cd8SNickeau public 44037748cd8SNickeau function previous() 44137748cd8SNickeau { 44237748cd8SNickeau if ($this->endWasReached) { 44337748cd8SNickeau $this->endWasReached = false; 44437748cd8SNickeau $return = end($this->callStack); 44537748cd8SNickeau if ($return == false) { 44637748cd8SNickeau // empty array (first call on the stack) 44737748cd8SNickeau return false; 44837748cd8SNickeau } else { 44937748cd8SNickeau return $this->getActualCall(); 45037748cd8SNickeau } 45137748cd8SNickeau } else { 45237748cd8SNickeau $prev = prev($this->callStack); 45337748cd8SNickeau if ($prev === false) { 45437748cd8SNickeau $this->startWasReached = true; 45537748cd8SNickeau return $prev; 45637748cd8SNickeau } else { 45737748cd8SNickeau return $this->getActualCall(); 45837748cd8SNickeau } 45937748cd8SNickeau } 46037748cd8SNickeau 46137748cd8SNickeau } 46237748cd8SNickeau 46337748cd8SNickeau /** 46437748cd8SNickeau * Return the first enter or special child call (ie a tag) 46537748cd8SNickeau * @return Call|false 46637748cd8SNickeau */ 46737748cd8SNickeau public 46837748cd8SNickeau function moveToFirstChildTag() 46937748cd8SNickeau { 47037748cd8SNickeau $found = false; 47137748cd8SNickeau while ($this->next()) { 47237748cd8SNickeau 47337748cd8SNickeau $actualCall = $this->getActualCall(); 47437748cd8SNickeau $state = $actualCall->getState(); 47537748cd8SNickeau switch ($state) { 47637748cd8SNickeau case DOKU_LEXER_ENTER: 47737748cd8SNickeau case DOKU_LEXER_SPECIAL: 47837748cd8SNickeau $found = true; 47937748cd8SNickeau break 2; 48037748cd8SNickeau case DOKU_LEXER_EXIT: 48137748cd8SNickeau break 2; 48237748cd8SNickeau } 48337748cd8SNickeau 48437748cd8SNickeau } 48537748cd8SNickeau if ($found) { 48637748cd8SNickeau return $this->getActualCall(); 48737748cd8SNickeau } else { 48837748cd8SNickeau return false; 48937748cd8SNickeau } 49037748cd8SNickeau 49137748cd8SNickeau 49237748cd8SNickeau } 49337748cd8SNickeau 49437748cd8SNickeau /** 49537748cd8SNickeau * The end is the one after the last element 49637748cd8SNickeau */ 49737748cd8SNickeau public 49837748cd8SNickeau function moveToEnd() 49937748cd8SNickeau { 50037748cd8SNickeau if ($this->startWasReached) { 50137748cd8SNickeau $this->startWasReached = false; 50237748cd8SNickeau } 50337748cd8SNickeau end($this->callStack); 504*4cadd4f8SNickeau return $this->next(); 50537748cd8SNickeau } 50637748cd8SNickeau 50737748cd8SNickeau /** 50837748cd8SNickeau * On the same level 50937748cd8SNickeau */ 51037748cd8SNickeau public 51137748cd8SNickeau function moveToNextSiblingTag() 51237748cd8SNickeau { 51337748cd8SNickeau 51437748cd8SNickeau /** 51537748cd8SNickeau * Edgde case 51637748cd8SNickeau */ 51737748cd8SNickeau if (empty($this->callStack)) { 51837748cd8SNickeau return false; 51937748cd8SNickeau } 52037748cd8SNickeau 52137748cd8SNickeau $actualCall = $this->getActualCall(); 522*4cadd4f8SNickeau $enterState = $actualCall->getState(); 523*4cadd4f8SNickeau if (!in_array($enterState, CallStack::TAG_STATE)) { 52437748cd8SNickeau LogUtility::msg("A next sibling can be asked only from a tag call. The state is " . $actualState, LogUtility::LVL_MSG_ERROR, "support"); 52537748cd8SNickeau return false; 52637748cd8SNickeau } 52737748cd8SNickeau $level = 0; 52837748cd8SNickeau while ($this->next()) { 52937748cd8SNickeau 53037748cd8SNickeau $actualCall = $this->getActualCall(); 53137748cd8SNickeau $state = $actualCall->getState(); 53237748cd8SNickeau switch ($state) { 53337748cd8SNickeau case DOKU_LEXER_ENTER: 53437748cd8SNickeau $level++; 53537748cd8SNickeau break; 536*4cadd4f8SNickeau case DOKU_LEXER_SPECIAL: 537*4cadd4f8SNickeau if ($enterState === DOKU_LEXER_SPECIAL) { 538*4cadd4f8SNickeau break; 539*4cadd4f8SNickeau } else { 540*4cadd4f8SNickeau // ENTER TAG 541*4cadd4f8SNickeau continue 2; 542*4cadd4f8SNickeau } 54337748cd8SNickeau case DOKU_LEXER_EXIT: 54437748cd8SNickeau $level--; 54537748cd8SNickeau break; 54637748cd8SNickeau } 54737748cd8SNickeau 54837748cd8SNickeau if ($level == 0 && in_array($state, self::TAG_STATE)) { 54937748cd8SNickeau break; 55037748cd8SNickeau } 55137748cd8SNickeau } 55237748cd8SNickeau if ($level == 0 && !$this->endWasReached) { 55337748cd8SNickeau return $this->getActualCall(); 55437748cd8SNickeau } else { 55537748cd8SNickeau return false; 55637748cd8SNickeau } 55737748cd8SNickeau } 55837748cd8SNickeau 55937748cd8SNickeau /** 56037748cd8SNickeau * @param Call $call 56137748cd8SNickeau * @return Call the inserted call 56237748cd8SNickeau */ 56337748cd8SNickeau public 5641fa8c418SNickeau function insertBefore(Call $call): Call 56537748cd8SNickeau { 56637748cd8SNickeau if ($this->endWasReached) { 56737748cd8SNickeau 56837748cd8SNickeau $this->callStack[] = $call->toCallArray(); 56937748cd8SNickeau 57037748cd8SNickeau } else { 57137748cd8SNickeau 57237748cd8SNickeau $offset = $this->getActualOffset(); 57337748cd8SNickeau array_splice($this->callStack, $offset, 0, [$call->toCallArray()]); 57437748cd8SNickeau // array splice reset the pointer 57537748cd8SNickeau // we move it to the actual element (ie the key is offset +1) 57637748cd8SNickeau $this->moveToOffset($offset + 1); 57737748cd8SNickeau 57837748cd8SNickeau } 57937748cd8SNickeau return $call; 58037748cd8SNickeau } 58137748cd8SNickeau 58237748cd8SNickeau /** 58337748cd8SNickeau * Move pointer by offset 58437748cd8SNickeau * @param $offset 58537748cd8SNickeau */ 58637748cd8SNickeau private 58737748cd8SNickeau function moveToOffset($offset) 58837748cd8SNickeau { 58937748cd8SNickeau $this->resetPointer(); 59037748cd8SNickeau for ($i = 0; $i < $offset; $i++) { 59137748cd8SNickeau $result = $this->next(); 59237748cd8SNickeau if ($result === false) { 59337748cd8SNickeau break; 59437748cd8SNickeau } 59537748cd8SNickeau } 59637748cd8SNickeau } 59737748cd8SNickeau 59837748cd8SNickeau /** 59937748cd8SNickeau * Move pointer by key 60037748cd8SNickeau * @param $targetKey 60137748cd8SNickeau */ 60237748cd8SNickeau private 60337748cd8SNickeau function moveToKey($targetKey) 60437748cd8SNickeau { 60537748cd8SNickeau $this->resetPointer(); 60637748cd8SNickeau for ($i = 0; $i < $targetKey; $i++) { 60737748cd8SNickeau next($this->callStack); 60837748cd8SNickeau } 60937748cd8SNickeau $actualKey = key($this->callStack); 61037748cd8SNickeau if ($actualKey != $targetKey) { 61137748cd8SNickeau LogUtility::msg("The target key ($targetKey) is not equal to the actual key ($actualKey). The moveToKey was not successful"); 61237748cd8SNickeau } 61337748cd8SNickeau } 61437748cd8SNickeau 61537748cd8SNickeau /** 61637748cd8SNickeau * Insert After. The pointer stays at the current state. 61737748cd8SNickeau * If you don't need to process the call that you just 61837748cd8SNickeau * inserted, you may want to call {@link CallStack::next()} 61937748cd8SNickeau * @param Call $call 62037748cd8SNickeau */ 62137748cd8SNickeau public 62237748cd8SNickeau function insertAfter($call) 62337748cd8SNickeau { 62437748cd8SNickeau $actualKey = key($this->callStack); 62537748cd8SNickeau if ($actualKey == null) { 62637748cd8SNickeau if ($this->endWasReached == true) { 62737748cd8SNickeau $this->callStack[] = $call->toCallArray(); 62837748cd8SNickeau } else { 62937748cd8SNickeau LogUtility::msg("Callstack: Actual key is null, we can't insert after null"); 63037748cd8SNickeau } 63137748cd8SNickeau } else { 63237748cd8SNickeau $offset = array_search($actualKey, array_keys($this->callStack), true); 63337748cd8SNickeau array_splice($this->callStack, $offset + 1, 0, [$call->toCallArray()]); 63437748cd8SNickeau // array splice reset the pointer 63537748cd8SNickeau // we move it to the actual element 63637748cd8SNickeau $this->moveToKey($actualKey); 63737748cd8SNickeau } 63837748cd8SNickeau } 63937748cd8SNickeau 64037748cd8SNickeau public 64137748cd8SNickeau function getActualKey() 64237748cd8SNickeau { 64337748cd8SNickeau return key($this->callStack); 64437748cd8SNickeau } 64537748cd8SNickeau 64637748cd8SNickeau /** 64737748cd8SNickeau * Insert an EOL call if the next call is not an EOL 64837748cd8SNickeau * This is to enforce an new paragraph 64937748cd8SNickeau */ 65037748cd8SNickeau public 65137748cd8SNickeau function insertEolIfNextCallIsNotEolOrBlock() 65237748cd8SNickeau { 65337748cd8SNickeau if (!$this->isPointerAtEnd()) { 65437748cd8SNickeau $nextCall = $this->next(); 65537748cd8SNickeau if ($nextCall != false) { 65637748cd8SNickeau if ($nextCall->getTagName() != "eol" && $nextCall->getDisplay() != "block") { 65737748cd8SNickeau $this->insertBefore( 65837748cd8SNickeau Call::createNativeCall("eol") 65937748cd8SNickeau ); 66037748cd8SNickeau // move on the eol 66137748cd8SNickeau $this->previous(); 66237748cd8SNickeau } 66337748cd8SNickeau // move back 66437748cd8SNickeau $this->previous(); 66537748cd8SNickeau } 66637748cd8SNickeau } 66737748cd8SNickeau } 66837748cd8SNickeau 66937748cd8SNickeau private 67037748cd8SNickeau function isPointerAtEnd() 67137748cd8SNickeau { 67237748cd8SNickeau return $this->endWasReached; 67337748cd8SNickeau } 67437748cd8SNickeau 67537748cd8SNickeau public 67637748cd8SNickeau function &getHandler() 67737748cd8SNickeau { 67837748cd8SNickeau return $this->handler; 67937748cd8SNickeau } 68037748cd8SNickeau 68137748cd8SNickeau /** 68237748cd8SNickeau * Return The offset (not the key): 68337748cd8SNickeau * * starting at 0 for the first element 68437748cd8SNickeau * * 1 for the second ... 68537748cd8SNickeau * 68637748cd8SNickeau * @return false|int|string 68737748cd8SNickeau */ 68837748cd8SNickeau private 68937748cd8SNickeau function getActualOffset() 69037748cd8SNickeau { 69137748cd8SNickeau $actualKey = key($this->callStack); 69237748cd8SNickeau return array_search($actualKey, array_keys($this->callStack), true); 69337748cd8SNickeau } 69437748cd8SNickeau 69537748cd8SNickeau private 69637748cd8SNickeau function resetPointer() 69737748cd8SNickeau { 69837748cd8SNickeau reset($this->callStack); 69937748cd8SNickeau $this->endWasReached = false; 70037748cd8SNickeau } 70137748cd8SNickeau 70237748cd8SNickeau public 70337748cd8SNickeau function moveToStart() 70437748cd8SNickeau { 70537748cd8SNickeau $this->resetPointer(); 706*4cadd4f8SNickeau return $this->previous(); 70737748cd8SNickeau } 70837748cd8SNickeau 70937748cd8SNickeau /** 71037748cd8SNickeau * @return Call|false the parent call or false if there is no parent 71137748cd8SNickeau * If you are on an {@link DOKU_LEXER_EXIT} state, you should 71237748cd8SNickeau * call first the {@link CallStack::moveToPreviousCorrespondingOpeningCall()} 71337748cd8SNickeau */ 71437748cd8SNickeau public function moveToParent() 71537748cd8SNickeau { 71637748cd8SNickeau 71737748cd8SNickeau /** 71837748cd8SNickeau * Case when we start from the exit state element 71937748cd8SNickeau * We go first to the opening tag 72037748cd8SNickeau * because the algorithm is level based. 72137748cd8SNickeau * 72237748cd8SNickeau * When the end is reached, there is no call 72337748cd8SNickeau * (this not the {@link end php end} but one further 72437748cd8SNickeau */ 72537748cd8SNickeau if (!$this->endWasReached && !$this->startWasReached && $this->getActualCall()->getState() == DOKU_LEXER_EXIT) { 72637748cd8SNickeau 72737748cd8SNickeau $this->moveToPreviousCorrespondingOpeningCall(); 72837748cd8SNickeau 72937748cd8SNickeau } 73037748cd8SNickeau 73137748cd8SNickeau 73237748cd8SNickeau /** 73337748cd8SNickeau * We are in a parent when the tree level is negative 73437748cd8SNickeau */ 73537748cd8SNickeau $treeLevel = 0; 73637748cd8SNickeau while ($actualCall = $this->previous()) { 73737748cd8SNickeau 73837748cd8SNickeau /** 73937748cd8SNickeau * Add 74037748cd8SNickeau * would become a parent on its enter state 74137748cd8SNickeau */ 74237748cd8SNickeau $actualCallState = $actualCall->getState(); 74337748cd8SNickeau switch ($actualCallState) { 74437748cd8SNickeau case DOKU_LEXER_ENTER: 74537748cd8SNickeau $treeLevel = $treeLevel - 1; 74637748cd8SNickeau break; 74737748cd8SNickeau case DOKU_LEXER_EXIT: 74837748cd8SNickeau /** 74937748cd8SNickeau * When the tag has a sibling with an exit tag 75037748cd8SNickeau */ 75137748cd8SNickeau $treeLevel = $treeLevel + 1; 75237748cd8SNickeau break; 75337748cd8SNickeau } 75437748cd8SNickeau 75537748cd8SNickeau /** 75637748cd8SNickeau * The breaking statement 75737748cd8SNickeau */ 75837748cd8SNickeau if ($treeLevel < 0) { 75937748cd8SNickeau break; 76037748cd8SNickeau } 76137748cd8SNickeau 76237748cd8SNickeau } 76337748cd8SNickeau return $actualCall; 76437748cd8SNickeau 76537748cd8SNickeau 76637748cd8SNickeau } 76737748cd8SNickeau 76837748cd8SNickeau /** 76937748cd8SNickeau * Delete the anchor link to the image (ie the lightbox) 77037748cd8SNickeau * 77137748cd8SNickeau * This is used in navigation and for instance 77237748cd8SNickeau * in heading 77337748cd8SNickeau */ 77437748cd8SNickeau public function processNoLinkOnImageToEndStack() 77537748cd8SNickeau { 77637748cd8SNickeau while ($this->next()) { 77737748cd8SNickeau $actualCall = $this->getActualCall(); 77837748cd8SNickeau if ($actualCall->getTagName() == syntax_plugin_combo_media::TAG) { 77937748cd8SNickeau $actualCall->addAttribute(MediaLink::LINKING_KEY, MediaLink::LINKING_NOLINK_VALUE); 78037748cd8SNickeau } 78137748cd8SNickeau } 78237748cd8SNickeau } 78337748cd8SNickeau 78437748cd8SNickeau /** 78537748cd8SNickeau * Append instructions to the callstack (ie at the end) 78637748cd8SNickeau * @param array $instructions 787*4cadd4f8SNickeau * @return CallStack 78837748cd8SNickeau */ 789*4cadd4f8SNickeau public function appendInstructionsFromNativeArray(array $instructions): CallStack 79037748cd8SNickeau { 79137748cd8SNickeau array_splice($this->callStack, count($this->callStack), 0, $instructions); 792*4cadd4f8SNickeau return $this; 79337748cd8SNickeau } 79437748cd8SNickeau 79537748cd8SNickeau /** 79637748cd8SNickeau * @param Call $call 79737748cd8SNickeau */ 79837748cd8SNickeau public function appendCallAtTheEnd($call) 79937748cd8SNickeau { 80037748cd8SNickeau $this->callStack[] = $call->toCallArray(); 80137748cd8SNickeau } 80237748cd8SNickeau 80337748cd8SNickeau public function moveToPreviousSiblingTag() 80437748cd8SNickeau { 80537748cd8SNickeau /** 80637748cd8SNickeau * Edge case 80737748cd8SNickeau */ 80837748cd8SNickeau if (empty($this->callStack)) { 80937748cd8SNickeau return false; 81037748cd8SNickeau } 81137748cd8SNickeau 812*4cadd4f8SNickeau $enterState = null; 81337748cd8SNickeau if (!$this->endWasReached) { 81437748cd8SNickeau $actualCall = $this->getActualCall(); 815*4cadd4f8SNickeau $enterState = $actualCall->getState(); 816*4cadd4f8SNickeau if (!in_array($enterState, CallStack::TAG_STATE)) { 81737748cd8SNickeau LogUtility::msg("A previous sibling can be asked only from a tag call. The state is " . $actualState, LogUtility::LVL_MSG_ERROR, "support"); 81837748cd8SNickeau return false; 81937748cd8SNickeau } 82037748cd8SNickeau } 82137748cd8SNickeau $level = 0; 822*4cadd4f8SNickeau while ($actualCall = $this->previous()) { 82337748cd8SNickeau 82437748cd8SNickeau $state = $actualCall->getState(); 82537748cd8SNickeau switch ($state) { 82637748cd8SNickeau case DOKU_LEXER_ENTER: 82737748cd8SNickeau $level++; 82837748cd8SNickeau break; 829*4cadd4f8SNickeau case DOKU_LEXER_SPECIAL: 830*4cadd4f8SNickeau if ($enterState === DOKU_LEXER_SPECIAL) { 831*4cadd4f8SNickeau break; 832*4cadd4f8SNickeau } else { 833*4cadd4f8SNickeau continue 2; 834*4cadd4f8SNickeau } 83537748cd8SNickeau case DOKU_LEXER_EXIT: 83637748cd8SNickeau $level--; 83737748cd8SNickeau break; 838*4cadd4f8SNickeau default: 839*4cadd4f8SNickeau // cdata 840*4cadd4f8SNickeau continue 2; 84137748cd8SNickeau } 84237748cd8SNickeau 84337748cd8SNickeau if ($level == 0 && in_array($state, self::TAG_STATE)) { 84437748cd8SNickeau break; 84537748cd8SNickeau } 84637748cd8SNickeau } 84737748cd8SNickeau if ($level == 0 && !$this->startWasReached) { 84837748cd8SNickeau return $this->getActualCall(); 84937748cd8SNickeau } else { 85037748cd8SNickeau return false; 85137748cd8SNickeau } 85237748cd8SNickeau } 85337748cd8SNickeau 85437748cd8SNickeau /** 85537748cd8SNickeau * Delete all calls after the passed call 85637748cd8SNickeau * 85737748cd8SNickeau * It's used in syntax generator that: 85837748cd8SNickeau * * capture the children callstack at the end, 85937748cd8SNickeau * * delete it 86037748cd8SNickeau * * and use it to generate more calls. 86137748cd8SNickeau * 86237748cd8SNickeau * @param Call $call 86337748cd8SNickeau */ 86437748cd8SNickeau public function deleteAllCallsAfter(Call $call) 86537748cd8SNickeau { 86637748cd8SNickeau $key = $call->getKey(); 86737748cd8SNickeau $offset = array_search($key, array_keys($this->callStack), true); 86837748cd8SNickeau if ($offset !== false) { 86937748cd8SNickeau /** 87037748cd8SNickeau * We delete from the next 87137748cd8SNickeau * {@link array_splice()} delete also the given offset 87237748cd8SNickeau */ 87337748cd8SNickeau array_splice($this->callStack, $offset + 1); 87437748cd8SNickeau } else { 87537748cd8SNickeau LogUtility::msg("The call ($call) could not be found in the callStack. We couldn't therefore delete the calls after"); 87637748cd8SNickeau } 87737748cd8SNickeau 87837748cd8SNickeau } 87937748cd8SNickeau 88037748cd8SNickeau /** 88137748cd8SNickeau * @param Call[] $calls 88237748cd8SNickeau */ 88337748cd8SNickeau public function appendInstructionsFromCallObjects($calls) 88437748cd8SNickeau { 88537748cd8SNickeau foreach ($calls as $call) { 88637748cd8SNickeau $this->appendCallAtTheEnd($call); 88737748cd8SNickeau } 888c3437056SNickeau 889c3437056SNickeau } 890c3437056SNickeau 891c3437056SNickeau /** 892c3437056SNickeau * 893c3437056SNickeau * @return int|mixed - the last position on the callstack 894c3437056SNickeau * If you are at the end of the callstack after a full parsing, 895c3437056SNickeau * this should be the length of the string of the page 896c3437056SNickeau */ 897c3437056SNickeau public function getLastCharacterPosition() 898c3437056SNickeau { 899c3437056SNickeau $offset = $this->getActualOffset(); 900c3437056SNickeau 901c3437056SNickeau $lastEndPosition = 0; 902c3437056SNickeau $this->moveToEnd(); 903c3437056SNickeau while ($actualCall = $this->previous()) { 904c3437056SNickeau // p_open and p_close have always a position value of 0 905c3437056SNickeau $lastEndPosition = $actualCall->getLastMatchedCharacterPosition(); 906c3437056SNickeau if ($lastEndPosition !== 0) { 907c3437056SNickeau break; 908c3437056SNickeau } 909c3437056SNickeau } 910c3437056SNickeau if ($offset == null) { 911c3437056SNickeau $this->moveToEnd(); 912c3437056SNickeau } else { 913c3437056SNickeau $this->moveToOffset($offset); 914c3437056SNickeau } 915c3437056SNickeau return $lastEndPosition; 91637748cd8SNickeau 91737748cd8SNickeau } 91837748cd8SNickeau 919*4cadd4f8SNickeau public function getStack(): array 920*4cadd4f8SNickeau { 921*4cadd4f8SNickeau return $this->callStack; 922*4cadd4f8SNickeau } 923*4cadd4f8SNickeau 924*4cadd4f8SNickeau public function moveToFirstEnterTag() 925*4cadd4f8SNickeau { 926*4cadd4f8SNickeau 927*4cadd4f8SNickeau while ($actualCall = $this->next()) { 928*4cadd4f8SNickeau 929*4cadd4f8SNickeau if ($actualCall->getState() === DOKU_LEXER_ENTER) { 930*4cadd4f8SNickeau return $this->getActualCall(); 931*4cadd4f8SNickeau } 932*4cadd4f8SNickeau } 933*4cadd4f8SNickeau return false; 934*4cadd4f8SNickeau 935*4cadd4f8SNickeau } 936*4cadd4f8SNickeau 937*4cadd4f8SNickeau /** 938*4cadd4f8SNickeau * Move the pointer to the corresponding exit call 939*4cadd4f8SNickeau * and return it or false if not found 940*4cadd4f8SNickeau * @return Call|false 941*4cadd4f8SNickeau */ 942*4cadd4f8SNickeau public function moveToNextCorrespondingExitTag() 943*4cadd4f8SNickeau { 944*4cadd4f8SNickeau /** 945*4cadd4f8SNickeau * Edge case 946*4cadd4f8SNickeau */ 947*4cadd4f8SNickeau if (empty($this->callStack)) { 948*4cadd4f8SNickeau return false; 949*4cadd4f8SNickeau } 950*4cadd4f8SNickeau 951*4cadd4f8SNickeau /** 952*4cadd4f8SNickeau * Check if we are on an enter tag 953*4cadd4f8SNickeau */ 954*4cadd4f8SNickeau $actualCall = $this->getActualCall(); 955*4cadd4f8SNickeau if ($actualCall === null) { 956*4cadd4f8SNickeau LogUtility::msg("You are not on the stack (start or end), you can't ask for the corresponding exit call", LogUtility::LVL_MSG_ERROR, self::CANONICAL); 957*4cadd4f8SNickeau return false; 958*4cadd4f8SNickeau } 959*4cadd4f8SNickeau $actualState = $actualCall->getState(); 960*4cadd4f8SNickeau if ($actualState != DOKU_LEXER_ENTER) { 961*4cadd4f8SNickeau LogUtility::msg("You are not on an enter tag ($actualState). You can't ask for the corresponding exit call .", LogUtility::LVL_MSG_ERROR, self::CANONICAL); 962*4cadd4f8SNickeau return false; 963*4cadd4f8SNickeau } 964*4cadd4f8SNickeau 965*4cadd4f8SNickeau $level = 0; 966*4cadd4f8SNickeau while ($actualCall = $this->next()) { 967*4cadd4f8SNickeau 968*4cadd4f8SNickeau $state = $actualCall->getState(); 969*4cadd4f8SNickeau switch ($state) { 970*4cadd4f8SNickeau case DOKU_LEXER_ENTER: 971*4cadd4f8SNickeau $level++; 972*4cadd4f8SNickeau break; 973*4cadd4f8SNickeau case DOKU_LEXER_EXIT: 974*4cadd4f8SNickeau $level--; 975*4cadd4f8SNickeau break; 976*4cadd4f8SNickeau } 977*4cadd4f8SNickeau if ($level < 0) { 978*4cadd4f8SNickeau break; 979*4cadd4f8SNickeau } 980*4cadd4f8SNickeau 981*4cadd4f8SNickeau } 982*4cadd4f8SNickeau if ($level < 0) { 983*4cadd4f8SNickeau return $actualCall; 984*4cadd4f8SNickeau } else { 985*4cadd4f8SNickeau return false; 986*4cadd4f8SNickeau } 987*4cadd4f8SNickeau 988*4cadd4f8SNickeau } 989*4cadd4f8SNickeau 990*4cadd4f8SNickeau public function moveToCall(Call $call): ?Call 991*4cadd4f8SNickeau { 992*4cadd4f8SNickeau $targetKey = $call->getKey(); 993*4cadd4f8SNickeau $actualKey = $this->getActualKey(); 994*4cadd4f8SNickeau $diff = $targetKey - $actualKey ; 995*4cadd4f8SNickeau for ($i = 0; $i < abs($diff); $i++) { 996*4cadd4f8SNickeau if ($diff > 0) { 997*4cadd4f8SNickeau $this->next(); 998*4cadd4f8SNickeau } else { 999*4cadd4f8SNickeau $this->previous(); 1000*4cadd4f8SNickeau } 1001*4cadd4f8SNickeau } 1002*4cadd4f8SNickeau return $this->getActualCall(); 1003*4cadd4f8SNickeau } 1004*4cadd4f8SNickeau 100537748cd8SNickeau 100637748cd8SNickeau} 1007