1<?php
2/* Copyright (c) 1998-2012 ILIAS open source, Extended GPL, see docs/LICENSE */
3require_once('class.ilCachedCtrl.php');
4
5/**
6 * This class provides processing control methods.
7 * A global instance is available via variable $ilCtrl
8 *
9 * xml_style parameters: This mode was activated per default in the past, is now set to false but still being
10 * used and needed, if link information is passed to the xslt processing e.g. in content pages.
11 *
12 * @author Alex Killing <alex.killing@gmx.de>
13 * @version $Id$
14 */
15class ilCtrl
16{
17    const IL_RTOKEN_NAME = 'rtoken';
18
19    /**
20     * Maps lowercase class names to lists of parameter names that saved for them.
21     *
22     * See saveParameter/setParameter for difference to save_parameter.
23     *
24     * This is used in: saveParameter, saveParameterByClass, getParameterArrayByClass
25     *
26     * @var	array<string, array<string, mixed>>
27     */
28    protected $save_parameter;
29
30    /**
31     * Maps lowercase class names to lists of parameters set for them.
32     *
33     * See saveParameter/setParameter for difference to save_parameter.
34     *
35     * This is used in: setParameter, setParameterByClass, getParameterArrayByClass
36     *
37     * @var	array<string, mixed[]>
38     */
39    protected $parameter;
40
41    /**
42     * Return commands per class.
43     *
44     * Return command sare defined by an upper context classes. If a subcontext calls
45     * returnToParent() it will redirect to the return command of the next upper context that defined
46     * a return command.
47     *
48     * This is used in: setReturn, setReturnByClass, getParentReturnByClass, searchReturnClass
49     *
50     * @var	array<string, string>
51     */
52    protected $return;
53
54    /**
55     * Stores the order in which different GUI classes were called.
56     *
57     * TODO: Might better be called call_stack.
58     *
59     * This is used in: forwardCommand, getHTML, getCallHistory
60     */
61    protected $call_hist = array();	// calling history
62
63    /**
64     * Stores which class calls which other class.
65     *
66     * This is used in: getNodeIdForTargetClass, fetchCallsOfClassFromCache, callOfClassNotKnown
67     */
68    protected $calls = array();
69
70    /**
71     * Request token, prevents XSS.
72     *
73     * This is used in: getRequestToken
74     *
75     * @var string
76     */
77    protected $rtoken = false;
78
79    /**
80     * Base script for link targets, reloads and so on
81     * @var string
82     */
83    protected $target_script = "ilias.php";
84
85    /**
86     * @var string
87     */
88    protected $module_dir;
89
90    /**
91     * control class constructor
92     */
93    public function __construct()
94    {
95        $this->initializeMemberVariables();
96
97        // this information should go to xml files one day
98        $this->stored_trees = array("ilrepositorygui", "ildashboardgui",
99            "illmpresentationgui", "illmeditorgui",
100            "iladministrationgui");
101    }
102
103    /**
104     * Initialize member variables.
105     *
106     * This is used in __construct and initBaseClass.
107     */
108    protected function initializeMemberVariables()
109    {
110        $this->save_parameter = array();
111        $this->parameter = array();			// save parameter array
112        $this->return = array();			// return commmands
113        $this->tab = array();
114        $this->current_node = 0;
115        $this->call_node = array();
116        $this->root_class = "";
117    }
118
119    /**
120     * Calls base class of current request. The base class is
121     * passed via $_GET["baseClass"] and is the first class in
122     * the call sequence of the request. Do not call this method
123     * within other scripts than ilias.php.
124     * @throws ilCtrlException
125     */
126    public function callBaseClass()
127    {
128        global $DIC;
129
130        $ilDB = $DIC->database();
131
132        $baseClass = strtolower($_GET["baseClass"]);
133
134        $module_class = ilCachedCtrl::getInstance();
135        $mc_rec = $module_class->lookupModuleClass($baseClass);
136
137        $module = $mc_rec["module"];
138        $class = $mc_rec["class"];
139        $class_dir = $mc_rec["dir"];
140
141        if ($module != "") {
142            $m_set = $ilDB->query("SELECT * FROM il_component WHERE name = " .
143                $ilDB->quote($module, "text"));
144            $m_rec = $ilDB->fetchAssoc($m_set);
145        } else {		// check whether class belongs to a service
146            $mc_rec = $module_class->lookupServiceClass($baseClass);
147
148            $service = $mc_rec["service"];
149            $class = $mc_rec["class"];
150            $class_dir = $mc_rec["dir"];
151
152            if ($service == "") {
153                include_once("./Services/UICore/exceptions/class.ilCtrlException.php");
154                throw new ilCtrlException("Could not find entry in modules.xml or services.xml for " .
155                    $baseClass . " <br/>" . str_replace("&", "<br />&", htmlentities($_SERVER["REQUEST_URI"])));
156            }
157
158            $m_rec = ilComponent::getComponentInfo('Services', $service);
159        }
160
161        // forward processing to base class
162        $this->getCallStructure(strtolower($baseClass));
163        $base_class_gui = new $class();
164        $this->forwardCommand($base_class_gui);
165    }
166
167    /**
168     * get directory of current module
169     * @deprecated
170     * @return mixed
171     * @throws Exception
172     */
173    public function getModuleDir()
174    {
175        throw new Exception("ilCtrl::getModuleDir is deprecated.");
176        //return $this->module_dir;
177    }
178
179    /**
180     * Forward flow of control to next gui class
181     * this invokes the executeCommand() method of the
182     * gui object that is passed via reference
183     *
184     * @param object $a_gui_object gui object that should receive
185     * @return mixed return data of invoked executeCommand() method
186     * @throws ilCtrlException
187     */
188    public function forwardCommand($a_gui_object)
189    {
190        $class = strtolower(get_class($a_gui_object));
191        $nr = $this->getNodeIdForTargetClass($this->current_node, $class);
192        $nr = $nr["node_id"];
193        if ($nr != "") {
194            $current_node = $this->current_node;
195
196            $this->current_node = $nr;
197
198            // always populate the call history
199            // it will only be displayed in DEVMODE but is needed for UI plugins, too
200            $this->call_hist[] = array("class" => get_class($a_gui_object),
201                    "mode" => "execComm", "cmd" => $this->getCmd());
202
203            $html = $a_gui_object->executeCommand();
204
205            // reset current node
206            $this->current_node = $current_node;
207
208            return $html;
209        }
210
211        include_once("./Services/UICore/exceptions/class.ilCtrlException.php");
212        throw new ilCtrlException("ERROR: Can't forward to class $class.");
213    }
214
215    /**
216     * Gets an HTML output from another GUI class and
217     * returns the flow of control to the calling class.
218     *
219     * @param object $a_gui_object GUI class that implements getHTML() method to return its HTML
220     * @param array|null $a_parameters parameter array
221     * @return string
222     * @throws ilCtrlException
223     */
224    public function getHTML($a_gui_object, array $a_parameters = null)
225    {
226        $class = strtolower(get_class($a_gui_object));
227
228        $nr = $this->getNodeIdForTargetClass($this->current_node, $class);
229        $nr = $nr["node_id"];
230        if ($nr != "") {
231            $current_node = $this->current_node;
232
233            // set current node to new gui class
234            $this->current_node = $nr;
235
236            // always populate the call history
237            // it will only be displayed in DEVMODE but is needed for UI plugins, too
238            $this->call_hist[] = array("class" => get_class($a_gui_object),
239                    "mode" => "getHtml", "cmd" => $this->getCmd());
240
241            // get block
242            if ($a_parameters != null) {
243                $html = $a_gui_object->getHTML($a_parameters);
244            } else {
245                $html = $a_gui_object->getHTML();
246            }
247
248            // reset current node
249            $this->current_node = $current_node;
250
251            // return block
252            return $html;
253        }
254
255        include_once("./Services/UICore/exceptions/class.ilCtrlException.php");
256        throw new ilCtrlException("ERROR: Can't getHTML from class $class.");
257    }
258
259    /**
260     * Set context of current user interface. A context is a ILIAS repository
261     * object (obj ID + obj type) with an additional optional subobject (ID + Type)
262     *
263     * @param	integer		object ID
264     * @param	string		object type
265     * @param	integer		subobject ID
266     * @param	string		subobject type
267     */
268    public function setContext($a_obj_id, $a_obj_type, $a_sub_obj_id = 0, $a_sub_obj_type = "")
269    {
270        $this->context_obj_id = $a_obj_id;
271        $this->context_obj_type = $a_obj_type;
272        $this->context_sub_obj_id = $a_sub_obj_id;
273        $this->context_sub_obj_type = $a_sub_obj_type;
274    }
275
276    /**
277     * Get context object id
278     *
279     * @return	int		object id
280     */
281    public function getContextObjId()
282    {
283        return $this->context_obj_id;
284    }
285
286    /**
287     * Get context object type
288     *
289     * @return	string		object type
290     */
291    public function getContextObjType()
292    {
293        return $this->context_obj_type;
294    }
295
296    /**
297     * Get context subobject id
298     *
299     * @return	int		subobject id
300     */
301    public function getContextSubObjId()
302    {
303        return $this->context_sub_obj_id;
304    }
305
306    /**
307     * Get context subobject type
308     *
309     * @return	string		subobject type
310     */
311    public function getContextSubObjType()
312    {
313        return $this->context_sub_obj_type;
314    }
315
316    /**
317     * Searches a node for a given class ($a_class) "near" another
318     * node ($a_par_node).
319     *
320     * It first looks if the given class is a child class of the current node.
321     * If such a child node has been found, its id is returned.
322     *
323     * If not, this method determines, whether the given class is a sibling
324     * of the current node within the call structure. If this is the case,
325     * then the corresponding id is returned.
326     *
327     * At last the method searches for the given class along the path from
328     * the current node to the root class of the call structure.
329     *
330     * @param string $a_par_node id of starting node for the search
331     * @param string $a_class class that should be searched
332     * @param bool $a_check
333     * @return array|bool id of target node that has been found
334     * @throws ilCtrlException
335     */
336    private function getNodeIdForTargetClass($a_par_node, $a_class, $a_check = false)
337    {
338        $class = strtolower($a_class);
339        $this->readClassInfo($class);
340
341        if ($a_par_node === 0 || $a_par_node == "") {
342            return array("node_id" => $this->getCidForClass($class),
343                "base_class" => $class);
344        }
345
346        $this->readNodeInfo($a_par_node);
347
348        $node_cid = $this->getCurrentCidOfNode($a_par_node);
349
350        // target class is class of current node id
351        if ($class == $this->getClassForCid($node_cid)) {
352            return array("node_id" => $a_par_node,
353                "base_class" => "");
354        }
355
356        // target class is child of current node id
357        if (isset($this->calls[$this->getClassForCid($node_cid)]) &&
358            is_array($this->calls[$this->getClassForCid($node_cid)]) &&
359            in_array($a_class, $this->calls[$this->getClassForCid($node_cid)])) {
360            return array("node_id" => $a_par_node . ":" . $this->getCidForClass($class),
361                "base_class" => "");
362        }
363
364        // target class is sibling
365        $par_cid = $this->getParentCidOfNode($a_par_node);
366        if ($par_cid != "") {
367            if (is_array($this->calls[$this->getClassForCid($par_cid)]) &&
368                in_array($a_class, $this->calls[$this->getClassForCid($par_cid)])) {
369                return array("node_id" =>
370                    $this->removeLastCid($a_par_node) . ":" . $this->getCidForClass($class),
371                    "base_class" => "");
372            }
373        }
374
375        // target class is parent
376        $temp_node = $this->removeLastCid($a_par_node);
377        while ($temp_node != "") {
378            $temp_cid = $this->getCurrentCidOfNode($temp_node);
379            if ($this->getClassForCid($temp_cid) == $a_class) {
380                return array("node_id" => $temp_node,
381                    "base_class" => "");
382            }
383            $temp_node = $this->removeLastCid($temp_node);
384        }
385
386        // target class is another base class
387        $n_class = "";
388        if ($a_class != "") {
389            $module_class = ilCachedCtrl::getInstance();
390            $mc_rec = $module_class->lookupModuleClass($class);
391            $n_class = $mc_rec['lower_class'];
392
393            if ($n_class == "") {
394                $mc_rec = $module_class->lookupServiceClass($class);
395                $n_class = $mc_rec['lower_class'];
396            }
397
398            if ($n_class != "") {
399                $this->getCallStructure($n_class);
400                return array("node_id" => $this->getCidForClass($n_class),
401                    "base_class" => $class);
402            }
403        }
404
405        if ($a_check) {
406            return false;
407        }
408
409        // Please do NOT change these lines.
410        // Developers must be aware, if they use classes unknown to the controller
411        // otherwise certain problem will be extremely hard to track down...
412
413        error_log("ERROR: Can't find target class $a_class for node $a_par_node " .
414            "(" . $this->cid_class[$this->getParentCidOfNode($a_par_node)] . ")");
415
416        include_once("./Services/UICore/exceptions/class.ilCtrlException.php");
417        throw new ilCtrlException("ERROR: Can't find target class $a_class for node $a_par_node " .
418            "(" . $this->cid_class[$this->getParentCidOfNode($a_par_node)] . ").");
419    }
420
421    /**
422     * Check whether target is valid
423     *
424     * @param
425     * @return
426     */
427    public function checkTargetClass($a_class)
428    {
429        if (!is_array($a_class)) {
430            $a_class = array($a_class);
431        }
432
433        $nr = $this->current_node;
434        foreach ($a_class as $class) {
435            $class = strtolower($class);
436
437            if (!$this->getCidForClass($class, true)) {
438                return false;
439            }
440
441            $nr = $this->getNodeIdForTargetClass($nr, $class, true);
442            $nr = $nr["node_id"];
443            if ($nr === false) {
444                return false;
445            }
446        }
447        return true;
448    }
449
450    /**
451     * Get command target node
452     *
453     * @return	string		id of current command target node
454     */
455    public function getCmdNode()
456    {
457        return $_GET["cmdNode"];
458    }
459
460    /**
461     * Add a tab to tabs array (@deprecated use $ilTabs)
462     *
463     * @param	string		$a_lang_var		language variable
464     * @param	string		$a_link			link
465     * @param	string		$a_cmd			command (must be same as in link)
466     * @param	string		$a_class		command class (must be same as in link)
467     */
468    public function addTab($a_lang_var, $a_link, $a_cmd, $a_class)
469    {
470        $a_class = strtolower($a_class);
471
472        $this->tab[] = array("lang_var" => $a_lang_var,
473            "link" => $a_link, "cmd" => $a_cmd, "class" => $a_class);
474    }
475
476    /**
477     * Get tabs array		(@deprecated, use $ilTabs)
478     *
479     * @return	array		array of tab entries (array("lang_var", "link", "cmd", "class))
480     */
481    public function getTabs()
482    {
483        return $this->tab;
484    }
485
486    /**
487     * Get controller call history.
488     *
489     * This is used for the developer mode and presented in the footer
490     *
491     * @return	array		array of call history entries
492     */
493    public function getCallHistory()
494    {
495        return $this->call_hist;
496    }
497
498    /**
499     * Get call structure of class context. This method must be called
500     * for the top level gui class in the leading php script. It must be
501     * called before the the current command is forwarded to the top level
502     * gui class. Example:
503     *
504     *	include_once "classes/class.ilRepositoryGUI.php";
505     *	$ilCtrl->getCallStructure("ilrepositorygui");
506     *	$repository_gui = new ilRepositoryGUI();
507     *	$ilCtrl->forwardCommand($repository_gui);
508     *
509     * @param	string		$a_class	gui class name
510     *
511     * @access	public
512     */
513    public function getCallStructure($a_class)
514    {
515        $this->readClassInfo($a_class);
516    }
517
518    /**
519     * Reads call structure from db
520     */
521    public function readCallStructure($a_class, $a_nr = 0, $a_parent = 0)
522    {
523        global $DIC;
524
525        $ilDB = $DIC->database();
526
527        $a_class = strtolower($a_class);
528
529        $a_nr++;
530
531        // determine call node structure
532        $this->call_node[$a_nr] = array("class" => $a_class, "parent" => $a_parent);
533
534        $call_set = $ilDB->query("SELECT * FROM ctrl_calls WHERE parent = " .
535            $ilDB->quote(strtolower($a_class), "text") .
536            " ORDER BY child", array("text"));
537        $a_parent = $a_nr;
538        while ($call_rec = $ilDB->fetchAssoc($call_set)) {
539            $a_nr = $this->readCallStructure($call_rec["child"], $a_nr, $a_parent);
540            $forw[] = $call_rec["child"];
541        }
542
543        // determine root class
544        $this->root_class = $a_class;
545        return $a_nr;
546    }
547
548    /**
549     * Set parameters that should be passed in every form and link of a
550     * gui class. All links that relate to the specified gui object class and
551     * are build e.g. by using getLinkTarger() or getFormAction() will include
552     * this parameter. This is the mechanism to add url parameters to the standard
553     * url target everytime.
554     *
555     * A typical example is the "ref_id" that should be included in almost every
556     * link or form action url. So the constructor of ilRepositoryGUI includes
557     * the command:
558     *
559     *	$this->ctrl->saveParameter($this, array("ref_id"));
560     *
561     * @param	object	$a_obj			gui object that will process the parameter
562     * @param	mixed	$a_parameter	parameter name (string) or array of parameter
563     *									names
564     *
565     * @access	public
566     */
567    public function saveParameter($a_obj, $a_parameter)
568    {
569        if (is_object($a_obj)) {
570            $this->saveParameterByClass(get_class($a_obj), $a_parameter);
571        }
572    }
573
574    /**
575     * Save parameter for a class
576     *
577     * @param	string	class name
578     * @param	string	parameter name
579     */
580    public function saveParameterByClass($a_class, $a_parameter)
581    {
582        if (is_array($a_parameter)) {
583            foreach ($a_parameter as $parameter) {
584                $this->save_parameter[strtolower($a_class)][] = $parameter;
585            }
586        } else {
587            $this->save_parameter[strtolower($a_class)][] = $a_parameter;
588        }
589    }
590
591
592    /**
593     * Set parameters that should be passed a form and link of a
594     * gui class. All links that relate to the specified gui object class and
595     * are build e.g. by using getLinkTarger() or getFormAction() will include
596     * this parameter. This is the mechanism to add url parameters to the standard
597     * url target. The difference to the saveParameter() method is, that setParameter()
598     * does not simply forward the url parameter of the last request. You can set
599     * a spefific value.
600     *
601     * If this parameter is also a "saved parameter" (set by saveParameter() method)
602     * the saved value will be overwritten.
603     *
604     * The method is usually used in conjunction with a getFormAction() or getLinkTarget()
605     * call. E.g.:
606     *
607     *		$this->ctrl->setParameter($this, "obj_id", $data_row["obj_id"]);
608     *		$obj_link = $this->ctrl->getLinkTarget($this, "view");
609     *
610     * @param	object		$a_obj			gui object
611     * @param	string		$a_parameter	parameter name
612     * @param	string		$a_parameter	parameter value
613     */
614    public function setParameter($a_obj, $a_parameter, $a_value)
615    {
616        $this->parameter[strtolower(get_class($a_obj))][$a_parameter] = $a_value;
617    }
618
619
620    /**
621     * Same as setParameterByClass, except that a class name is passed.
622     *
623     * @param	string		$a_class		gui class name
624     * @param	string		$a_parameter	parameter name
625     * @param	string		$a_parameter	parameter value
626     */
627    public function setParameterByClass($a_class, $a_parameter, $a_value)
628    {
629        $this->parameter[strtolower($a_class)][$a_parameter] = $a_value;
630    }
631
632    /**
633     * Same as setParameterByClass, except that a class name is passed.
634     *
635     * @param	string		$a_class		gui class name
636     * @param	string		$a_parameter	parameter name
637     * @param	string		$a_parameter	parameter value
638     */
639    public function clearParameterByClass($a_class, $a_parameter)
640    {
641        unset($this->parameter[strtolower($a_class)][$a_parameter]);
642    }
643
644    /**
645     * Clears all parameters that have been set via setParameter for
646     * a GUI class.
647     *
648     * @param	object		$a_obj			gui object
649     */
650    public function clearParameters($a_obj)
651    {
652        $this->clearParametersByClass(strtolower(get_class($a_obj)));
653    }
654
655    /**
656     * Clears all parameters that have been set via setParameter for
657     * a GUI class.
658     *
659     * @param	string		$a_class		gui class name
660     */
661    public function clearParametersByClass($a_class)
662    {
663        $this->parameter[strtolower($a_class)] = array();
664    }
665
666    protected function checkLPSettingsForward($a_gui_obj, $a_cmd_node)
667    {
668        global $DIC;
669
670        $objDefinition = $DIC["objDefinition"];
671
672        // forward to learning progress settings if possible and accessible
673        if ($_GET["gotolp"] &&
674            $a_gui_obj) {
675            $ref_id = $_GET["ref_id"];
676            if (!$ref_id) {
677                $ref_id = $_REQUEST["ref_id"];
678            }
679
680            $gui_class = get_class($a_gui_obj);
681
682            if ($gui_class == "ilSAHSEditGUI") {
683                // #1625 - because of scorm "sub-types" this is all very special
684                include_once "./Modules/ScormAicc/classes/class.ilObjSAHSLearningModule.php";
685                $obj_id = ilObject::_lookupObjectId($ref_id);
686                switch (ilObjSAHSLearningModule::_lookupSubType($obj_id)) {
687                    case "scorm2004":
688                        $class = "ilObjSCORM2004LearningModuleGUI";
689                        break;
690
691                    case "scorm":
692                        $class = "ilObjSCORMLearningModuleGUI";
693                        break;
694
695                    case "aicc":
696                        $class = "ilObjAICCLearningModuleGUI";
697                        break;
698
699                    case "hacp":
700                        $class = "ilObjHACPLearningModuleGUI";
701                        break;
702                }
703                if ($GLOBALS["ilAccess"]->checkAccess("edit_learning_progress", "", $ref_id)) {
704                    $this->redirectByClass(array($gui_class, $class, "illearningprogressgui", "illplistofsettingsgui"), "");
705                }
706            }
707            // special case: cannot use any presentation GUIs
708            elseif ($gui_class == "ilLMPresentationGUI") {
709                $this->setParameterByClass("ilObjLearningModuleGUI", "gotolp", 1);
710                $this->redirectByClass(array("ilLMEditorGUI", "ilObjLearningModuleGUI"), "");
711            }
712
713            include_once "Services/Object/classes/class.ilObjectLP.php";
714            $type = ilObject::_lookupType($ref_id, true);
715            $class = "ilObj" . $objDefinition->getClassName($type) . "GUI";
716
717            if ($gui_class == $class &&
718                ilObjectLP::isSupportedObjectType($type) &&
719                $GLOBALS["ilAccess"]->checkAccess("edit_learning_progress", "", $ref_id)) {
720                // add path to repository object gui if missing from cmdNode
721                if (!$a_cmd_node) {
722                    $repo_node = $this->getNodeIdForTargetClass(null, "ilrepositorygui");
723                    $obj_node = $this->getNodeIdForTargetClass($repo_node["node_id"], $gui_class);
724                    $a_cmd_node = $obj_node["node_id"];
725                }
726                // find path to lp settings
727                $lp_node = $this->getNodeIdForTargetClass($a_cmd_node, "illearningprogressgui");
728                $lp_settings_node = $this->getNodeIdForTargetClass($lp_node["node_id"], "illplistofsettingsgui");
729                $_GET["cmdNode"] = $lp_settings_node["node_id"];
730                $_GET["cmdClass"] = "ilLPListOfSettingsGUI";
731                $_GET["cmd"] = "";
732                return "illearningprogressgui";
733            }
734        }
735    }
736
737    /**
738     * Get next class in the control path from the current class
739     * to the target command class. This is the class that should
740     * be instantiated and be invoked via $ilCtrl->forwardCommand($class)
741     * next.
742     *
743     * @return	string		class name of next class
744     */
745    public function getNextClass($a_gui_class = null)
746    {
747        $cmdNode = $this->getCmdNode();
748        if ($cmdNode == "") {
749            return ($class = $this->checkLPSettingsForward($a_gui_class, $cmdNode))
750                ? $class
751                : false;
752        } else {
753            if ($this->current_node == $cmdNode) {
754                return ($class = $this->checkLPSettingsForward($a_gui_class, $cmdNode))
755                    ? $class
756                    : "";
757            } else {
758                $path = $this->getPathNew($this->current_node, $cmdNode);
759                $this->readCidInfo($this->getCurrentCidOfNode($path[1]));
760                return $this->cid_class[$this->getCurrentCidOfNode($path[1])];
761            }
762        }
763    }
764
765    /**
766     * Get class path that can be used in include statements
767     * for a given class name.
768     *
769     * @param	string		$a_class_name		class name
770     */
771    public function lookupClassPath($a_class_name)
772    {
773        $a_class_name = strtolower($a_class_name);
774
775        $cached_ctrl = ilCachedCtrl::getInstance();
776        $class_rec = $cached_ctrl->lookupClassFile($a_class_name);
777
778        if ($class_rec["plugin_path"] != "") {
779            return $class_rec["plugin_path"] . "/" . $class_rec["filename"];
780        } else {
781            return $class_rec["filename"];
782        }
783    }
784
785    /**
786     * this method assumes that the class path has the format "dir/class.<class_name>.php"
787     *
788     * @param	string		$a_class_path		class path
789     * @access	public
790     *
791     * @return	string		class name
792     */
793    public function getClassForClasspath($a_class_path)
794    {
795        $path = pathinfo($a_class_path);
796        $file = $path["basename"];
797        $class = substr($file, 6, strlen($file) - 10);
798
799        return $class;
800    }
801
802    /**
803     * Get path in call structure.
804     *
805     * @param	string		$a_source_node		source node id
806     * @param	string		$a_source_node		target node id
807     */
808    private function getPathNew($a_source_node, $a_target_node)
809    {
810        if ($a_source_node == "1") {
811            $a_source_node = "";
812        }
813        if (substr($a_target_node, 0, strlen($a_source_node)) != $a_source_node) {
814            $failure = "ERROR: Path not found. Source:" . $a_source_node .
815                ", Target:" . $a_target_node;
816            if (DEVMODE == 1) {
817                include_once("./Services/UICore/exceptions/class.ilCtrlException.php");
818                throw new ilCtrlException($failure);
819            }
820            $GLOBALS['ilLog']->write(__METHOD__ . ' ' . $failure);
821            $this->redirectToURL('./ilias.php?baseClass=ilRepositoryGUI');
822        }
823        $temp_node = $a_source_node;
824
825        $path = array();
826        if ($a_source_node != "") {
827            $path = array($a_source_node);
828        }
829
830        $diffstart = ($a_source_node == "")
831            ? 0
832            : strlen($a_source_node) + 1;
833        $diff = substr($a_target_node, $diffstart);
834        $diff_arr = explode(":", $diff);
835        foreach ($diff_arr as $cid) {
836            if ($temp_node != "") {
837                $temp_node .= ":";
838            }
839            $temp_node .= $cid;
840            $path[] = $temp_node;
841        }
842        return $path;
843    }
844
845    /**
846     * set target script name
847     *
848     * @param	string		$a_target_script		target script name
849     */
850    public function setTargetScript(string $a_target_script)
851    {
852        $this->target_script = $a_target_script;
853    }
854
855
856    /**
857     * Get target script name
858     *
859     * @return	string		target script name
860     */
861    public function getTargetScript() : string
862    {
863        return $this->target_script;
864    }
865
866
867    /**
868     * Initialises new base class
869     *
870     * Note: this resets the whole current ilCtrl context completely.
871     * You can call callBaseClass() after that.
872     *
873     * @param	string		base class name
874     */
875    public function initBaseClass($a_base_class)
876    {
877        $_GET["baseClass"] = $a_base_class;
878        $_GET["cmd"] = "";
879        $_GET["cmdClass"] = "";
880        $_GET["cmdNode"] = "";
881        $this->initializeMemberVariables();
882    }
883
884    /**
885     * Determines current get/post command
886     *
887     * @param	string		default command
888     * @param	array		safe commands: for these commands no token
889     *						is checked for post requests
890     */
891    public function getCmd($a_default_cmd = "", $a_safe_commands = "")
892    {
893        $cmd = "";
894        if (isset($_GET["cmd"])) {
895            $cmd = $_GET["cmd"];
896        }
897        if ($cmd == "post") {
898            if (isset($_POST["cmd"]) && is_array($_POST["cmd"])) {
899                reset($_POST["cmd"]);
900            }
901            $cmd = @key($_POST["cmd"]);
902
903            // verify command
904            if ($this->verified_cmd != "") {
905                return $this->verified_cmd;
906            } else {
907                if (!$this->verifyToken() &&
908                    (!is_array($a_safe_commands) || !in_array($cmd, $a_safe_commands))) {
909                    return $a_default_cmd;
910                }
911            }
912
913            $this->verified_cmd = $cmd;
914            if ($cmd == "" && isset($_POST["table_top_cmd"])) {		// selected command in multi-list (table2)
915                $cmd = @key($_POST["table_top_cmd"]);
916                $this->verified_cmd = $cmd;
917                $_POST[$_POST["cmd_sv"][$cmd]] = $_POST[$_POST["cmd_sv"][$cmd] . "_2"];
918            }
919            if ($cmd == "" && isset($_POST["select_cmd2"])) {		// selected command in multi-list (table2)
920                if (isset($_POST["select_cmd_all2"])) {
921                    $_POST["select_cmd_all"] = $_POST["select_cmd_all2"];
922                } else {
923                    $_POST["select_cmd_all"] = $_POST["select_cmd_all2"] = null;
924                }
925                $cmd = $_POST["selected_cmd2"];
926                $this->verified_cmd = $cmd;
927            }
928            if ($cmd == "" && isset($_POST["select_cmd"])) {		// selected command in multi-list (table2)
929                if (isset($_POST["select_cmd_all"])) {
930                    $_POST["select_cmd_all2"] = $_POST["select_cmd_all"];
931                } else {
932                    $_POST["select_cmd_all"] = $_POST["select_cmd_all2"] = null;
933                }
934                $cmd = $_POST["selected_cmd"];
935                $this->verified_cmd = $cmd;
936            }
937            if ($cmd == "") {
938                $cmd = $_GET["fallbackCmd"];
939                $this->verified_cmd = $cmd;
940            }
941        }
942        if ($cmd == "") {
943            $cmd = $a_default_cmd;
944        }
945        return $cmd;
946    }
947
948    /**
949     * Set the current command
950     *
951     * IMPORTANT NOTE:
952     *
953     * please use this function only in exceptional cases
954     * it is not intended for setting commands in forms or links!
955     * use the corresponding parameters of getFormAction() and
956     * getLinkTarget() instead.
957     */
958    public function setCmd($a_cmd)
959    {
960        $_GET["cmd"] = $a_cmd;
961    }
962
963    /**
964     * Set the current command class
965     *
966     * IMPORTANT NOTE:
967     *
968     * please use this function only in exceptional cases
969     * it is not intended for setting the command class in forms or links!
970     * use the corresponding parameters of getFormAction() and
971     * getLinkTarget() instead.
972     */
973    public function setCmdClass($a_cmd_class)
974    {
975        $a_cmd_class = strtolower($a_cmd_class);
976        $nr = $this->getNodeIdForTargetClass($this->current_node, $a_cmd_class);
977        $nr = $nr["node_id"];
978        $_GET["cmdClass"] = $a_cmd_class;
979        $_GET["cmdNode"] = $nr;
980    }
981
982    /**
983     * Determines class that should execute the current command
984     *
985     * @return	string		class name
986     */
987    public function getCmdClass()
988    {
989        return strtolower($_GET["cmdClass"]);
990    }
991
992    /**
993     * Get form action url for gui class object
994     *
995     * @param	object		gui object
996     * @param	string		fallback command
997     * @param	string		anchor
998     * @param	bool		asynchronous call
999     * @param	bool		xml style t/f
1000     * @return	string		script url
1001     */
1002    public function getFormAction(
1003        $a_gui_obj,
1004        $a_fallback_cmd = "",
1005        $a_anchor = "",
1006        $a_asynch = false,
1007        $xml_style = false
1008    ) {
1009        $script = $this->getFormActionByClass(
1010            strtolower(get_class($a_gui_obj)),
1011            $a_fallback_cmd,
1012            $a_anchor,
1013            $a_asynch,
1014            $xml_style
1015        );
1016        return $script;
1017    }
1018
1019    /**
1020     * Get form action url for gui class name
1021     *
1022     * @param	string		gui class name
1023     * @param	string		fallback command
1024     * @param	string		anchor
1025     * @param	bool		asynchronous call
1026     * @param	bool		xml style t/f
1027     * @return	string		script url
1028     */
1029    public function getFormActionByClass(
1030        $a_class,
1031        $a_fallback_cmd = "",
1032        $a_anchor = "",
1033        $a_asynch = false,
1034        $xml_style = false
1035    ) {
1036        if (!is_array($a_class)) {
1037            $a_class = strtolower($a_class);
1038        }
1039
1040        $tok = $this->getRequestToken();
1041
1042        if ($a_asynch) {
1043            $xml_style = false;
1044        }
1045
1046        $script = $this->getLinkTargetByClass($a_class, "post", "", $a_asynch, $xml_style);
1047        if ($a_fallback_cmd != "") {
1048            $script = ilUtil::appendUrlParameterString($script, "fallbackCmd=" . $a_fallback_cmd, $xml_style);
1049        }
1050        $script = ilUtil::appendUrlParameterString(
1051            $script,
1052            self::IL_RTOKEN_NAME . '=' . $this->getRequestToken(),
1053            $xml_style
1054        );
1055        if ($a_anchor != "") {
1056            $script = $script . "#" . $a_anchor;
1057        }
1058
1059        return $script;
1060    }
1061
1062    /**
1063     * Append request token as url parameter
1064     *
1065     * @param	string	url
1066     * @param	boolean	xml style
1067     */
1068    public function appendRequestTokenParameterString($a_url, $xml_style = false)
1069    {
1070        return ilUtil::appendUrlParameterString(
1071            $a_url,
1072            self::IL_RTOKEN_NAME . '=' . $this->getRequestToken(),
1073            $xml_style
1074        );
1075    }
1076
1077    /**
1078     * Get request token.
1079     *
1080     * @return	string		request token for user and session
1081     */
1082    public function getRequestToken()
1083    {
1084        global $DIC;
1085
1086        $ilUser = $DIC["ilUser"];
1087        $ilDB = $DIC->database();
1088
1089
1090        if ($this->rtoken != "") {
1091            return $this->rtoken;
1092        } else {
1093            if (is_object($ilDB) && is_object($ilUser) && $ilUser->getId() > 0 &&
1094                $ilUser->getId() != ANONYMOUS_USER_ID) {
1095                $res = $ilDB->query("SELECT token FROM il_request_token WHERE user_id = " .
1096                    $ilDB->quote($ilUser->getId(), "integer") .
1097                    " AND session_id = " . $ilDB->quote(session_id(), "text"));
1098                $rec = $ilDB->fetchAssoc($res);
1099                if ($rec["token"] != "") {
1100                    $this->rtoken = $rec["token"];
1101                    return $rec["token"];
1102                }
1103                //echo "new rtoken, new entry for :".$ilUser->getId().":".session_id().":"; exit;
1104                $random = new \ilRandom();
1105                $this->rtoken = md5(uniqid($random->int(), true));
1106
1107                // delete entries older than one and a half days
1108                if ($random->int(1, 200) == 2) {
1109                    $dt = new ilDateTime(time(), IL_CAL_UNIX);
1110                    $dt->increment(IL_CAL_DAY, -1);
1111                    $dt->increment(IL_CAL_HOUR, -12);
1112                    $dq = "DELETE FROM il_request_token WHERE " .
1113                        " stamp < " . $ilDB->quote($dt->get(IL_CAL_DATETIME), "timestamp");
1114                    $ilDB->manipulate($dq);
1115                }
1116
1117                // IMPORTANT: Please do NOT try to move this implementation to a
1118                // session basis. This will fail due to framesets that are used
1119                // occasionally in ILIAS, e.g. in the chat, where multiple
1120                // forms are loaded in different frames.
1121                $ilDB->manipulate("INSERT INTO il_request_token (user_id, token, stamp, session_id) VALUES " .
1122                    "(" .
1123                    $ilDB->quote($ilUser->getId(), "integer") . "," .
1124                    $ilDB->quote($this->rtoken, "text") . "," .
1125                    $ilDB->now() . "," .
1126                    $ilDB->quote(session_id(), "text") . ")");
1127                return $this->rtoken;
1128            }
1129        }
1130        return "";
1131    }
1132
1133    /**
1134     * Verify Token
1135     *
1136     * @return	boolean		valid t/f
1137     */
1138    private function verifyToken()
1139    {
1140        global $DIC;
1141
1142        $ilUser = $DIC["ilUser"];
1143
1144        $ilDB = $DIC->database();
1145        ;
1146
1147        if (is_object($ilUser) && is_object($ilDB) && $ilUser->getId() > 0 &&
1148            $ilUser->getId() != ANONYMOUS_USER_ID) {
1149            if ($_GET["rtoken"] == "") {
1150                return false;
1151            }
1152
1153            $set = $ilDB->query("SELECT * FROM il_request_token WHERE " .
1154                " user_id = " . $ilDB->quote($ilUser->getId(), "integer") . " AND " .
1155                " token = " . $ilDB->quote($_GET[self::IL_RTOKEN_NAME]), "text");
1156            if ($ilDB->numRows($set) > 0) {
1157                // remove tokens from older sessions
1158                // if we do this immediately, working with multiple windows does not work:
1159                // - window one: open form (with token a)
1160                // - window two: open form (with token b)
1161                // - submit window one: a is verified, but b must not be deleted immediately, otherwise
1162                // - window two: submit results in invalid token
1163                // see also bug #13551
1164                $dt = new ilDateTime(time(), IL_CAL_UNIX);
1165                $dt->increment(IL_CAL_DAY, -1);
1166                $dt->increment(IL_CAL_HOUR, -12);
1167                $ilDB->manipulate("DELETE FROM il_request_token WHERE " .
1168                    " user_id = " . $ilDB->quote($ilUser->getId(), "integer") . " AND " .
1169                    " session_id != " . $ilDB->quote(session_id(), "text") . " AND " .
1170                    " stamp < " . $ilDB->quote($dt->get(IL_CAL_DATETIME), "timestamp"));
1171                return true;
1172            } else {
1173                return false;
1174            }
1175
1176            if ($_SESSION["rtokens"][$_GET[self::IL_RTOKEN_NAME]] != "") {
1177                // remove used token
1178                unset($_SESSION["rtokens"][$_GET[self::IL_RTOKEN_NAME]]);
1179
1180                // remove old tokens
1181                if (count($_SESSION["rtokens"]) > 100) {
1182                    $to_remove = array();
1183                    $sec = 7200;			// two hours
1184
1185                    foreach ($_SESSION["rtokens"] as $tok => $time) {
1186                        if (time() - $time > $sec) {
1187                            $to_remove[] = $tok;
1188                        }
1189                    }
1190                    foreach ($to_remove as $tok) {
1191                        unset($_SESSION["rtokens"][$tok]);
1192                    }
1193                }
1194
1195                return true;
1196            }
1197            return false;
1198        } else {
1199            return true;		// do not verify, if user or db object is missing
1200        }
1201
1202        return false;
1203    }
1204
1205    /**
1206     * Redirect to another command
1207     *
1208     * @param	object		gui object
1209     * @param	string		command
1210     * @param	string		anchor
1211     */
1212    public function redirect($a_gui_obj, $a_cmd = "", $a_anchor = "", $a_asynch = false)
1213    {
1214        $script = $this->getLinkTargetByClass(
1215            strtolower(get_class($a_gui_obj)),
1216            $a_cmd,
1217            "",
1218            $a_asynch,
1219            false
1220        );
1221        if ($a_anchor != "") {
1222            $script = $script . "#" . $a_anchor;
1223        }
1224        $this->redirectToURL($script);
1225    }
1226
1227
1228    /**
1229     * @param $a_script
1230     */
1231    public function redirectToURL($a_script)
1232    {
1233        global $DIC;
1234
1235        $ilPluginAdmin = null;
1236        if (isset($DIC["ilPluginAdmin"])) {
1237            $ilPluginAdmin = $DIC["ilPluginAdmin"];
1238        }
1239
1240        if (!is_int(strpos($a_script, "://"))) {
1241            if (substr($a_script, 0, 1) != "/" && defined("ILIAS_HTTP_PATH")) {
1242                if (is_int(strpos($_SERVER["PHP_SELF"], "/setup/"))) {
1243                    $a_script = "setup/" . $a_script;
1244                }
1245                $a_script = ILIAS_HTTP_PATH . "/" . $a_script;
1246            }
1247        }
1248
1249        // include the user interface hook
1250        if (is_object($ilPluginAdmin)) {
1251            $pl_names = $ilPluginAdmin->getActivePluginsForSlot(IL_COMP_SERVICE, "UIComponent", "uihk");
1252            foreach ($pl_names as $pl) {
1253                $ui_plugin = ilPluginAdmin::getPluginObject(IL_COMP_SERVICE, "UIComponent", "uihk", $pl);
1254                $gui_class = $ui_plugin->getUIClassInstance();
1255                $resp = $gui_class->getHTML("Services/Utilities", "redirect", array( "html" => $a_script ));
1256                if ($resp["mode"] != ilUIHookPluginGUI::KEEP) {
1257                    $a_script = $gui_class->modifyHTML($a_script, $resp);
1258                }
1259            }
1260        }
1261
1262        // Manually trigger to write and close the session. This has the advantage that if an exception is thrown
1263        // during the writing of the session (ILIAS writes the session into the database by default) we get an exception
1264        // if the session_write_close() is triggered by exit() then the exception will be dismissed but the session
1265        // is never written, which is a nightmare to develop with.
1266        session_write_close();
1267
1268        global $DIC;
1269        $http = $DIC->http();
1270        switch ($http->request()->getHeaderLine('Accept')) {
1271            case 'application/json':
1272                $stream = \ILIAS\Filesystem\Stream\Streams::ofString(json_encode([
1273                    'success' => true,
1274                    'message' => 'Called redirect after async fileupload request',
1275                    "redirect_url" => $a_script,
1276                ]));
1277                $http->saveResponse($http->response()->withBody($stream));
1278                break;
1279            default:
1280                $http->saveResponse($http->response()->withAddedHeader("Location", $a_script));
1281                break;
1282        }
1283        $http->sendResponse();
1284        exit;
1285    }
1286
1287
1288    /**
1289     * Redirect to other gui class using class name
1290     *
1291     * @param	string		command target class
1292     * @param	string		command
1293     */
1294    public function redirectByClass($a_class, $a_cmd = "", $a_anchor = "", $a_asynch = false)
1295    {
1296        $script = $this->getLinkTargetByClass($a_class, $a_cmd, "", $a_asynch, false);
1297        if ($a_anchor != "") {
1298            $script = $script . "#" . $a_anchor;
1299        }
1300        $this->redirectToURL($script);
1301    }
1302
1303    /**
1304     * Is current command an asynchronous command?
1305     *
1306     * @return	boolean		asynchronous t/f
1307     */
1308    public function isAsynch()
1309    {
1310        if (isset($_GET["cmdMode"]) && $_GET["cmdMode"] == "asynch") {
1311            return true;
1312        } else {
1313            return false;
1314        }
1315    }
1316
1317
1318    /**
1319     * Get link target for command using gui object
1320     *
1321     * @param	object		gui object (usually $this)
1322     * @param	string		command
1323     * @param	string		# anchor
1324     * @param	boolean		asynchronous mode
1325     * @param	boolean		xml style t/f
1326     *
1327     * @return	string		target link
1328     */
1329    public function getLinkTarget(
1330        $a_gui_obj,
1331        $a_cmd = "",
1332        $a_anchor = "",
1333        $a_asynch = false,
1334        $xml_style = false
1335    ) {
1336        $script = $this->getLinkTargetByClass(
1337            strtolower(get_class($a_gui_obj)),
1338            $a_cmd,
1339            $a_anchor,
1340            $a_asynch,
1341            $xml_style
1342        );
1343        return $script;
1344    }
1345
1346
1347    /**
1348     * Get link target for command using gui class name
1349     *
1350     * @param	string/array		command target class
1351     * @param	string		command
1352     * @param	string		# anchor
1353     * @param	boolean		asynchronous mode
1354     * @param	boolean		xml style t/f
1355     *
1356     * @return	string		target link
1357     */
1358    public function getLinkTargetByClass(
1359        $a_class,
1360        $a_cmd = "",
1361        $a_anchor = "",
1362        $a_asynch = false,
1363        $xml_style = false
1364    ) {
1365        if ($a_asynch) {
1366            $xml_style = false;
1367        }
1368
1369        $script = $this->getTargetScript();
1370        $script = $this->getUrlParameters($a_class, $script, $a_cmd, $xml_style);
1371
1372        if ($a_asynch) {
1373            $amp = "&";
1374            $script .= $amp . "cmdMode=asynch";
1375        }
1376
1377        if ($a_anchor != "") {
1378            $script = $script . "#" . $a_anchor;
1379        }
1380
1381        return $script;
1382    }
1383
1384    /**
1385     * Set return command
1386     */
1387    public function setReturn($a_gui_obj, $a_cmd)
1388    {
1389        $script = $this->getTargetScript();
1390        $script = $this->getUrlParameters(strtolower(get_class($a_gui_obj)), $script, $a_cmd);
1391        $this->return[strtolower(get_class($a_gui_obj))] = $script;
1392    }
1393
1394    /**
1395     * Set return command
1396     */
1397    public function setReturnByClass($a_class, $a_cmd)
1398    {
1399        // may not be an array!
1400        $a_class = strtolower($a_class);
1401
1402        $script = $this->getTargetScript();
1403        $script = $this->getUrlParameters($a_class, $script, $a_cmd);
1404        $this->return[strtolower($a_class)] = $script;
1405    }
1406
1407    /**
1408     * Redirects to next parent class that used setReturn.
1409     */
1410    public function returnToParent($a_gui_obj, $a_anchor = "")
1411    {
1412        $script = $this->getParentReturn($a_gui_obj);
1413
1414        $script = ilUtil::appendUrlParameterString(
1415            $script,
1416            "redirectSource=" . strtolower(get_class($a_gui_obj))
1417        );
1418        $script = ilUtil::appendUrlParameterString(
1419            $script,
1420            "cmdMode=" . $_GET["cmdMode"]
1421        );
1422        if ($a_anchor != "") {
1423            $script = $script . "#" . $a_anchor;
1424        }
1425
1426        $this->redirectToURL($script);
1427    }
1428
1429
1430    /**
1431     * Get return script url.
1432     *
1433     * Used in conjunction with ilTabs->setBackTarget and ilBlockGUI->addHeaderCommand.
1434     */
1435    public function getParentReturn($a_gui_obj)
1436    {
1437        return $this->getParentReturnByClass(strtolower(get_class($a_gui_obj)));
1438    }
1439
1440
1441    /**
1442     * Get return script url
1443     *
1444     * Only used in getParentReturn.
1445     */
1446    protected function getParentReturnByClass($a_class)
1447    {
1448        $a_class = strtolower($a_class);
1449        $ret_class = $this->searchReturnClass($a_class);
1450        if ($ret_class) {
1451            return $this->return[$ret_class];
1452        }
1453    }
1454
1455    /**
1456     * Get return class.
1457     *
1458     * Only used in COPage/ilPCParagraphGUI and COPage/ilPCPlaceHolderGUI
1459     *
1460     * @param	string|object	$class
1461     * @return	string|bool
1462     */
1463    public function getReturnClass($a_class)
1464    {
1465        if (is_object($a_class)) {
1466            $class = strtolower(get_class($a_class));
1467        } else {
1468            $class = strtolower($a_class);
1469        }
1470        return $this->searchReturnClass($class);
1471    }
1472
1473
1474    /**
1475     * Determine current return class
1476     */
1477    private function searchReturnClass($a_class)
1478    {
1479        $a_class = strtolower($a_class);
1480
1481        $node = $this->getNodeIdForTargetClass($this->current_node, $a_class);
1482        $node = $node["node_id"];
1483        $n_arr = explode(":", $node);
1484        for ($i = count($n_arr) - 2; $i >= 0; $i--) {
1485            if ($this->return[$this->getClassForCid($n_arr[$i])] != "") {
1486                return $this->getClassForCid($n_arr[$i]);
1487            }
1488        }
1489
1490        return false;
1491    }
1492
1493    /**
1494     * Get current redirect source
1495     *
1496     * @return	string		redirect source class
1497     */
1498    public function getRedirectSource()
1499    {
1500        return $_GET["redirectSource"];
1501    }
1502
1503    /**
1504     * Get URL parameters for a class and append them to a string
1505     * @param $a_class
1506     * @param $a_str
1507     * @param string $a_cmd command
1508     * @param bool $xml_style
1509     * @return string
1510     */
1511    public function getUrlParameters($a_class, $a_str, $a_cmd = "", $xml_style = false)
1512    {
1513        $params = $this->getParameterArrayByClass($a_class, $a_cmd);
1514
1515        foreach ($params as $par => $value) {
1516            if (strlen((string) $value)) {
1517                $a_str = ilUtil::appendUrlParameterString($a_str, $par . "=" . $value, $xml_style);
1518            }
1519        }
1520
1521        return $a_str;
1522    }
1523
1524    /**
1525     * Get all set/save parameters for a gui object
1526     */
1527    public function getParameterArray($a_gui_obj, $a_cmd = "")
1528    {
1529        $par_arr = $this->getParameterArrayByClass(strtolower(get_class($a_gui_obj)), $a_cmd);
1530
1531        return $par_arr;
1532    }
1533
1534    /**
1535     * Get all set/save parameters using gui class name
1536     *
1537     * @param	string		class name
1538     * @param	string		cmd
1539     $ @return	array		parameter array
1540     */
1541    public function getParameterArrayByClass($a_class, $a_cmd = "")
1542    {
1543        if ($a_class == "") {
1544            return array();
1545        }
1546
1547        if (!is_array($a_class)) {
1548            $a_class = array($a_class);
1549        }
1550
1551        $nr = $this->current_node;
1552        $new_baseclass = "";
1553        foreach ($a_class as $class) {
1554            $class = strtolower($class);
1555            $nr = $this->getNodeIdForTargetClass($nr, $class);
1556            if ($nr["base_class"] != "") {
1557                $new_baseclass = $nr["base_class"];
1558            }
1559            $nr = $nr["node_id"];
1560            $target_class = $class;
1561        }
1562
1563        $path = $this->getPathNew(1, $nr);
1564        $params = array();
1565
1566        // append parameters of parent classes
1567        foreach ($path as $node_id) {
1568            $class = ($node_id == "")
1569                ? strtolower($_GET["baseClass"])
1570                : $this->getClassForCid($this->getCurrentCidOfNode($node_id));
1571            if (isset($this->save_parameter[$class]) && is_array($this->save_parameter[$class])) {
1572                foreach ($this->save_parameter[$class] as $par) {
1573                    if (isset($_GET[$par])) {
1574                        $params[$par] = $_GET[$par];
1575                    } elseif (isset($_POST[$par])) {
1576                        $params[$par] = ilUtil::stripSlashesRecursive($_POST[$par]);
1577                    }
1578                }
1579            }
1580
1581            if (isset($this->parameter[$class]) && is_array($this->parameter[$class])) {
1582                foreach ($this->parameter[$class] as $par => $value) {
1583                    $params[$par] = $value;
1584                }
1585            }
1586        }
1587
1588        if ($a_cmd != "") {
1589            $params["cmd"] = $a_cmd;
1590        }
1591
1592        $params["cmdClass"] = $target_class;
1593        $params["cmdNode"] = $nr;
1594        if ($new_baseclass == "") {
1595            $params["baseClass"] = $_GET["baseClass"];
1596        } else {
1597            $params["baseClass"] = $new_baseclass;
1598        }
1599
1600        return $params;
1601    }
1602
1603    private function classCidUnknown($a_class)
1604    {
1605        return $this->class_cid[$a_class] == "";
1606    }
1607
1608    /**
1609     * Get class id for class after fetching and storing corresponding information, if necessary.
1610     */
1611    private function getCidForClass($a_class, $a_check = false)
1612    {
1613        if ($this->classCidUnknown($a_class)) {
1614            $this->readClassInfo($a_class);
1615        }
1616        if ($this->classCidUnknown($a_class)) {
1617            if ($a_check) {
1618                return false;
1619            }
1620            if (DEVMODE == 1) {
1621                $add = "<br><br>Please make sure your GUI class name ends with 'GUI' and that the filename is 'class.[YourClassName].php'. In exceptional cases you
1622					may solve the issue by putting an empty * @ilCtrl_Calls [YourClassName]: into your class header." .
1623                    " In both cases you need to reload the control structure in the setup.";
1624            }
1625            include_once("./Services/UICore/exceptions/class.ilCtrlException.php");
1626            throw new ilCtrlException("Cannot find cid for class " . $a_class . "." . $add);
1627        }
1628        return $this->class_cid[$a_class];
1629    }
1630
1631    private function cidClassUnknown($a_cid)
1632    {
1633        return $this->cid_class[$a_cid] == "";
1634    }
1635
1636
1637    /**
1638     * Get class for class id after fetching and storing corresponding information, if necessary.
1639     */
1640    private function getClassForCid($a_cid)
1641    {
1642        if ($this->cidClassUnknown($a_cid)) {
1643            $this->readCidInfo($a_cid);
1644        }
1645        if ($this->cidClassUnknown($a_cid)) {
1646            include_once("./Services/UICore/exceptions/class.ilCtrlException.php");
1647            throw new ilCtrlException("Cannot find class for cid " . $a_cid . ".");
1648        }
1649        return $this->cid_class[$a_cid];
1650    }
1651
1652    private function fetchCallsOfClassFromCache($a_class, ilCachedCtrl $a_cached_ctrl)
1653    {
1654        foreach ($a_cached_ctrl->lookupCall($a_class) as $call) {
1655            if ($call["child"] != "" && $this->callOfClassNotKnown($a_class, $call['child'])) {
1656                $this->calls[$a_class][] = $call["child"];
1657            }
1658        }
1659    }
1660
1661    /**
1662     * Save class respective to $a_cid and store corresponding
1663     * class calls for future reference.
1664     *
1665     * @param object $a_cid		cid
1666     */
1667    private function readCidInfo($a_cid)
1668    {
1669        if (isset($this->info_read_cid[$a_cid])) {
1670            return;
1671        }
1672
1673        $cached_ctrl = ilCachedCtrl::getInstance();
1674        $cid_info = $cached_ctrl->lookupCid($a_cid);
1675
1676        if ($cid_info) {
1677            $this->updateClassCidMap($cid_info['class'], $a_cid);
1678            $this->fetchCallsOfClassFromCache($cid_info['class'], $cached_ctrl);
1679            $this->info_read_class[$cid_info["class"]] = true;
1680        }
1681
1682        $this->info_read_cid[$a_cid] = true;
1683    }
1684
1685    /**
1686     * Save classes respective to the class id's of a node and store corresponding
1687     * class calls for future reference.
1688     *
1689     * @param	string	$a_node
1690     */
1691    private function readNodeInfo($a_node)
1692    {
1693        $class_ids = explode(":", $a_node);
1694        foreach ($class_ids as $cid) {
1695            $this->readCidInfo($cid);
1696        }
1697    }
1698
1699    /**
1700     * Save class id respective to $a_class and store corresponding
1701     * class calls for future reference.
1702     *
1703     * @param	object	$a_class	class name
1704     */
1705    private function readClassInfo($a_class)
1706    {
1707        $a_class = strtolower($a_class);
1708        if (isset($this->info_read_class[$a_class])) {
1709            return;
1710        }
1711
1712        $cached_ctrl = ilCachedCtrl::getInstance();
1713        $class_info = $cached_ctrl->lookupClassFile($a_class);
1714
1715        if ($class_info) {
1716            $this->updateClassCidMap($a_class, $class_info['cid']);
1717        }
1718        $this->fetchCallsOfClassFromCache($a_class, $cached_ctrl);
1719
1720        $this->info_read_class[$a_class] = true;
1721        $this->info_read_cid[$this->class_cid[$a_class]] = true;
1722    }
1723
1724    private function callOfClassNotKnown($a_class, $a_child)
1725    {
1726        return !isset($this->calls[$a_class])
1727                || !is_array($this->calls[$a_class])
1728                || !in_array($a_child, $this->calls[$a_class]);
1729    }
1730
1731    private function updateClassCidMap($a_class, $a_cid)
1732    {
1733        $this->cid_class[$a_cid] = $a_class;
1734        $this->class_cid[$a_class] = $a_cid;
1735    }
1736
1737    /**
1738     * Get 2nd to last class id of node
1739     */
1740    private function getParentCidOfNode($a_node)
1741    {
1742        $class_ids = explode(":", $a_node);
1743        return $class_ids[count($class_ids) - 2];
1744    }
1745
1746    /**
1747     * Remove the class id that comes at the beginning the sequence.
1748     */
1749    private function removeLastCid($a_node)
1750    {
1751        $lpos = strrpos($a_node, ":");
1752        return substr($a_node, 0, $lpos);
1753    }
1754
1755    /**
1756     * Get cid of node
1757     */
1758    private function getCurrentCidOfNode($a_node)
1759    {
1760        $n_arr = explode(":", $a_node);
1761        return $n_arr[count($n_arr) - 1];
1762    }
1763
1764    /**
1765     * Insert ctrl calls record
1766     *
1767     * @param
1768     * @return
1769     */
1770    public function insertCtrlCalls($a_parent, $a_child, $a_comp_prefix)
1771    {
1772        global $DIC;
1773
1774        $ilDB = $DIC->database();
1775        ;
1776
1777        $a_parent = strtolower($a_parent);
1778        $a_child = strtolower($a_child);
1779        $a_comp_prefix = strtolower($a_comp_prefix);
1780
1781        $set = $ilDB->query(
1782            "SELECT * FROM ctrl_calls WHERE " .
1783            " parent = " . $ilDB->quote($a_parent, "text") . " AND " .
1784            " child = " . $ilDB->quote($a_child, "text") . " AND " .
1785            " comp_prefix = " . $ilDB->quote($a_comp_prefix, "text")
1786            );
1787        if ($rec = $ilDB->fetchAssoc($set)) {
1788            return;
1789        }
1790        $ilDB->manipulate("INSERT INTO ctrl_calls " .
1791            "(parent, child, comp_prefix) VALUES (" .
1792            $ilDB->quote($a_parent, "text") . "," .
1793            $ilDB->quote($a_child, "text") . "," .
1794            $ilDB->quote($a_comp_prefix, "text") .
1795            ")");
1796    }
1797
1798    /**
1799     * Check if current path contains a certain gui class
1800     *
1801     * @param $gui_class
1802     * @return bool
1803     * @throws ilCtrlException
1804     */
1805    public function checkCurrentPathForClass($gui_class)
1806    {
1807        foreach (explode(":", $this->getCmdNode()) as $cid) {
1808            if ($cid != "" && strtolower($this->getClassForCid($cid)) == strtolower($gui_class)) {
1809                return true;
1810            }
1811        }
1812        return false;
1813    }
1814
1815    /**
1816     * Get current class path as array of class file names
1817     *
1818     * @return array
1819     * @throws ilCtrlException
1820     */
1821    public function getCurrentClassPath() : array
1822    {
1823        $path = [];
1824        foreach (explode(":", $this->getCmdNode()) as $cid) {
1825            if ($cid != "") {
1826                $path[] = $this->getClassForCid($cid);
1827            }
1828        }
1829        if ($this->getCmdNode() == "" && $_GET["baseClass"] != "") {
1830            $path[] = $_GET["baseClass"];
1831        }
1832        return $path;
1833    }
1834}
1835