ILIAS  trunk Revision v11.0_alpha-1723-g8e69f309bab
All Data Structures Namespaces Files Functions Variables Enumerations Enumerator Modules Pages
class.ilCtrl.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
28 
35 class ilCtrl implements ilCtrlInterface
36 {
37  protected ?object $exec_object = null;
38  protected ?string $command = null;
39 
45  protected array $stacktrace = [];
46 
47  public function __construct(
49  protected ilCtrlTokenRepositoryInterface $token_repository,
50  protected ilCtrlPathFactoryInterface $path_factory,
52  protected ResponseSenderStrategy $response_sender,
53  protected ServerRequestInterface $server_request,
54  protected RequestWrapper $post_parameters,
55  protected RequestWrapper $get_parameters,
56  protected Refinery $refinery,
57  protected ilComponentFactory $component_factory,
58  protected ilCtrlSubject $subject,
59  protected ilCtrlQueryParserInterface $query_parser,
60  ) {
61  }
62 
63  public function __clone()
64  {
65  $this->structure = clone $this->structure;
66  }
67 
71  public function callBaseClass(?string $a_base_class = null): void
72  {
73  // prioritise the context's baseclass over the given one.
74  $a_base_class = $this->context->getBaseClass() ?? $a_base_class;
75 
76  // abort if no baseclass was provided.
77  if (null === $a_base_class) {
78  throw new ilCtrlException(__METHOD__ . " was not given a baseclass and the request doesn't include one either.");
79  }
80 
81  // abort if the provided baseclass is unknown.
82  if (!$this->structure->isBaseClass($a_base_class)) {
83  throw new ilCtrlException("Provided class '$a_base_class' is not a baseclass");
84  }
85 
86  // in case the baseclass was given by argument,
87  // set the context's baseclass.
88  $this->context->setBaseClass($a_base_class);
89 
90  // no null-check needed as previous isBaseClass() was true.
91  $obj_name = $this->structure->getObjNameByName($a_base_class);
92  $this->forwardCommand(new $obj_name());
93  }
94 
98  public function forwardCommand(object $a_gui_object)
99  {
100  $class_name = get_class($a_gui_object);
101 
102  // @TODO: remove this check once an interface for command classes exists.
103  if (!method_exists($a_gui_object, 'executeCommand')) {
104  throw new ilCtrlException("$class_name doesn't implement executeCommand().");
105  }
106 
107  $this->exec_object = $a_gui_object;
108  $this->populateCall($class_name, self::CMD_MODE_PROCESS);
109 
110  // with forward command we cannot progress, or set
111  // the current command class. Otherwise, the path-
112  // finding gets mixed up, as it can only be used in
113  // getHTML() method calls.
114  $this->context
115  ->setCmdMode(self::CMD_MODE_PROCESS);
116 
117  $this->subject->notify(ilCtrlEvent::COMMAND_CLASS_FORWARD, $class_name);
118 
119  return $a_gui_object->executeCommand();
120  }
121 
125  public function getHTML(object $a_gui_object, ?array $a_parameters = null): string
126  {
127  $class_name = get_class($a_gui_object);
128 
129  // @TODO: remove this check once an interface for command classes exists.
130  if (!method_exists($a_gui_object, 'getHTML')) {
131  throw new ilCtrlException("$class_name doesn't implement getHTML().");
132  }
133 
134  $isolatad_structure = $this->structure;
135  $isolated_context = $this->context;
136  $isolated_object = $this->exec_object;
137 
138  $this->exec_object = $a_gui_object;
139  $this->populateCall($class_name, self::CMD_MODE_HTML);
140  $this->context
141  ->setCmdClass($class_name)
142  ->setCmdMode(self::CMD_MODE_HTML);
143 
144  $html = (null !== $a_parameters) ?
145  $a_gui_object->getHTML($a_parameters) :
146  $a_gui_object->getHTML();
147 
148  $this->structure = $isolatad_structure;
149  $this->context = $isolated_context;
150  $this->exec_object = $isolated_object;
151 
152  return $html;
153  }
154 
158  public function getCmd(?string $fallback_command = null): ?string
159  {
160  $command = $this->getDeterminedCommand() ?? $fallback_command;
161  if (null !== $this->command && $command === $this->command) {
162  // don't broadcast command determination event on consecutive
163  // method calls.
164  return $command;
165  }
166 
167  $this->subject->notify(ilCtrlEvent::COMMAND_DETERMINATION, $command);
168  $this->command = $command;
169 
170  return $command ?? ''; // remove null-coalesce
171  }
172 
176  public function getCmdClass(): ?string
177  {
178  if (null !== ($cmd_class = $this->context->getCmdClass())) {
179  return strtolower($this->structure->getObjNameByName($cmd_class));
180  }
181 
182  return '';
183  }
184 
188  public function getNextClass($a_gui_class = null): ?string
189  {
190  if (null === $a_gui_class && null === $this->exec_object) {
191  return '';
192  }
193 
194  if (null === $this->context->getPath()) {
195  return '';
196  }
197 
198  $next_cid = $this->context->getPath()->getNextCid(
199  $this->getClassByObject($a_gui_class ?? $this->exec_object)
200  );
201 
202  if (null !== $next_cid) {
203  return strtolower($this->structure->getObjNameByCid($next_cid) ?? '');
204  }
205 
206  return '';
207  }
208 
212  public function saveParameter(object $a_gui_obj, $a_parameter): void
213  {
214  $this->saveParameterByClass($this->getClassByObject($a_gui_obj), $a_parameter);
215  }
216 
220  public function saveParameterByClass(string $a_class, $a_parameter): void
221  {
222  if (!empty($a_parameter)) {
223  if (is_array($a_parameter)) {
224  foreach ($a_parameter as $parameter) {
225  $this->structure->setPermanentParameterByClass($a_class, $parameter);
226  }
227  } else {
228  $this->structure->setPermanentParameterByClass($a_class, $a_parameter);
229  }
230  }
231  }
232 
236  public function setParameter(object $a_gui_obj, string $a_parameter, $a_value): void
237  {
238  $this->setParameterByClass($this->getClassByObject($a_gui_obj), $a_parameter, $a_value);
239  }
240 
244  public function setParameterByClass(string $a_class, string $a_parameter, $a_value): void
245  {
246  $this->structure->setTemporaryParameterByClass($a_class, $a_parameter, $a_value);
247  }
248 
252  public function getParameterArray(object $a_gui_obj): array
253  {
254  return $this->getParameterArrayByClass($this->getClassByObject($a_gui_obj));
255  }
256 
260  public function getParameterArrayByClass(string $a_class): array
261  {
262  if (null === $this->structure->getClassCidByName($a_class)) {
263  throw new ilCtrlException("Cannot find provided class '$a_class' in the control structure.");
264  }
265 
266  $parameters = [];
267  $permanent_parameters = $this->structure->getPermanentParametersByClass($a_class);
268  if (null !== $permanent_parameters) {
269  foreach ($permanent_parameters as $parameter) {
270  $parameters[$parameter] = $this->getQueryParam($parameter);
271  }
272  }
273 
274  $temporary_parameters = $this->structure->getTemporaryParametersByClass($a_class);
275  if (null !== $temporary_parameters) {
276  // override existing ones, as temporary parameters
277  // are prioritised over fetched ones.
278  foreach ($temporary_parameters as $key => $value) {
279  $parameters[$key] = $value;
280  }
281  }
282 
283  return $parameters;
284  }
285 
289  public function clearParameters(object $a_gui_obj): void
290  {
291  $this->clearParametersByClass($this->getClassByObject($a_gui_obj));
292  }
293 
297  public function clearParametersByClass(string $a_class): void
298  {
299  // apparently permanent parameters should not be removable,
300  // therefore the line below stays commented:
301  // $this->structure->removePermanentParametersByClass($a_class);
302  $this->structure->removeTemporaryParametersByClass($a_class);
303  }
304 
308  public function clearParameterByClass(string $a_class, string $a_parameter): void
309  {
310  $this->structure->removeSingleParameterByClass($a_class, $a_parameter);
311  }
312 
316  public function getLinkTarget(
317  object $a_gui_obj,
318  ?string $a_cmd = null,
319  ?string $a_anchor = null,
320  bool $is_async = false,
321  bool $has_xml_style = false
322  ): string {
323  return $this->getLinkTargetByClass(
324  $this->getClassByObject($a_gui_obj),
325  $a_cmd,
326  $a_anchor,
327  $is_async,
328  $has_xml_style
329  );
330  }
331 
335  public function getLinkTargetByClass(
336  $a_class,
337  ?string $a_cmd = null,
338  ?string $a_anchor = null,
339  bool $is_async = false,
340  bool $has_xml_style = false
341  ): string {
342  return $this->getTargetUrl(
343  $a_class,
344  $a_cmd,
345  $a_anchor,
346  $is_async,
347  $has_xml_style
348  ) ?? '';
349  }
350 
354  public function getFormAction(
355  object $a_gui_obj,
356  ?string $a_fallback_cmd = null,
357  ?string $a_anchor = null,
358  bool $is_async = false,
359  bool $has_xml_style = false
360  ): string {
361  return $this->getFormActionByClass(
362  $this->getClassByObject($a_gui_obj),
363  $a_fallback_cmd,
364  $a_anchor,
365  $is_async,
366  $has_xml_style
367  );
368  }
369 
373  public function getFormActionByClass(
374  $a_class,
375  ?string $a_fallback_cmd = null,
376  ?string $a_anchor = null,
377  bool $is_async = false,
378  bool $has_xml_style = false
379  ): string {
380  return $this->getTargetUrl(
381  $a_class,
382  $a_fallback_cmd,
383  $a_anchor,
384  $is_async,
385  $has_xml_style,
386  true
387  ) ?? '';
388  }
389 
393  public function redirect(
394  object $a_gui_obj,
395  ?string $a_cmd = null,
396  ?string $a_anchor = null,
397  bool $is_async = false
398  ): void {
399  $this->redirectByClass(
400  $this->getClassByObject($a_gui_obj),
401  $a_cmd,
402  $a_anchor,
403  $is_async
404  );
405  }
406 
410  public function redirectByClass(
411  $a_class,
412  ?string $a_cmd = null,
413  ?string $a_anchor = null,
414  bool $is_async = false
415  ): void {
416  $this->redirectToURL(
417  $this->getLinkTargetByClass(
418  $a_class,
419  $a_cmd,
420  $a_anchor,
421  $is_async
422  )
423  );
424  }
425 
429  public function redirectToURL(string $target_url): void
430  {
431  // prepend the ILIAS HTTP path if it wasn't already.
432  if (defined("ILIAS_HTTP_PATH") &&
433  strpos($target_url, "://") === false &&
434  strpos($target_url, "/") !== 0
435  ) {
436  $target_url = ILIAS_HTTP_PATH . "/" . $target_url;
437  }
438 
439  // this line can be dropped after discussion with TB or JF,
440  // it keeps the functionality of UI plugin hooks alive.
441  $target_url = $this->modifyUrlWithPluginHooks($target_url);
442 
443  // initialize http response object
444  $response = new Response();
445 
446  // there's an exceptional case for asynchronous file uploads
447  // where a json response is delivered.
448  if ('application/json' === $this->server_request->getHeaderLine('Accept')) {
449  try {
450  $body = Streams::ofString(
451  json_encode(
452  [
453  'redirect_url' => $target_url,
454  'success' => true,
455  'message' => 'called redirect after asynchronous file-upload request.',
456  ],
457  JSON_THROW_ON_ERROR
458  )
459  );
460  } catch (Throwable $exception) {
461  $body = Streams::ofString($exception->getMessage());
462  }
463 
464  $response = $response->withBody($body);
465  } else {
466  $response = $response->withAddedHeader('Location', $target_url);
467  }
468 
469  // manually trigger session_write_close() due to exceptions stored
470  // in the ILIAS database, otherwise this method is called by exit()
471  // which leads to the exceptions not being written to the database.
472  session_write_close();
473 
474  try {
475  $this->response_sender->sendResponse($response);
476  } catch (ResponseSendingException $e) {
477  header("Location: $target_url");
478  if ('application/json' === $this->server_request->getHeaderLine('Accept')) {
479  $content = (null !== $response->getBody()) ?
480  $response->getBody()->getContents() :
481  [];
482 
483  echo json_encode($content, JSON_THROW_ON_ERROR);
484  }
485  } catch (Throwable $t) {
486  header("Location: $target_url");
487  echo $t->getMessage();
488  }
489 
490  exit;
491  }
492 
496  public function setContextObject(int $obj_id, string $obj_type): void
497  {
498  // cannot process object without object type.
499  if (!empty($obj_type)) {
500  $this->context->setObjId($obj_id);
501  $this->context->setObjType($obj_type);
502  }
503  }
504 
508  public function getContextObjId(): ?int
509  {
510  return $this->context->getObjId();
511  }
512 
516  public function getContextObjType(): ?string
517  {
518  return $this->context->getObjType();
519  }
520 
524  public function getCallHistory(): array
525  {
526  return $this->stacktrace;
527  }
528 
532  public function lookupClassPath(string $a_class): string
533  {
534  $path = $this->structure->getRelativePathByName($a_class);
535  if (null === $path) {
536  throw new ilCtrlException("Class '$a_class' cannot be found in the control structure.");
537  }
538 
539  return $path;
540  }
541 
545  public function lookupOriginalClassName(string $a_class): ?string
546  {
547  return $this->structure->getObjNameByName($a_class);
548  }
549 
553  public function getClassForClasspath(string $a_class_path): string
554  {
555  $path_info = pathinfo($a_class_path);
556 
557  return substr($path_info['basename'], 6, -4);
558  }
559 
563  public function setTargetScript(string $a_target_script): void
564  {
565  $this->context->setTargetScript($a_target_script);
566  }
567 
571  public function isAsynch(): bool
572  {
573  return $this->context->isAsync();
574  }
575 
579  public function setReturn(object $a_gui_obj, ?string $a_cmd = null): void
580  {
581  $this->setReturnByClass($this->getClassByObject($a_gui_obj), $a_cmd);
582  }
583 
587  public function setReturnByClass(string $a_class, ?string $a_cmd = null): void
588  {
589  $this->structure->setReturnTargetByClass(
590  $a_class,
591  $this->getLinkTargetByClass(
592  $a_class,
593  $a_cmd
594  )
595  );
596  }
597 
601  public function returnToParent(object $a_gui_obj, ?string $a_anchor = null): void
602  {
603  $class_name = $this->getClassByObject($a_gui_obj);
604  $target_url = $this->getParentReturnByClass($class_name);
605 
606  // append redirect source to target url.
607  $target_url = $this->appendParameterString(
608  $target_url,
609  self::PARAM_REDIRECT,
610  $class_name
611  );
612 
613  // append the provided anchor if necessary.
614  if (null !== $a_anchor) {
615  $target_url .= "#$a_anchor";
616  }
617 
618  $this->redirectToURL($target_url);
619  }
620 
624  public function getParentReturn(object $a_gui_obj): ?string
625  {
626  return $this->getParentReturnByClass($this->getClassByObject($a_gui_obj));
627  }
628 
632  public function getParentReturnByClass(string $a_class): ?string
633  {
634  $path = $this->path_factory->find($this->context, $a_class);
635  if (null !== $path->getCidPath()) {
636  foreach ($path->getCidArray() as $cid) {
637  $current_class = $this->structure->getClassNameByCid($cid);
638  $return_target = $this->structure->getReturnTargetByClass($current_class);
639  if (null !== $return_target) {
640  return $return_target;
641  }
642  }
643  }
644 
645  return null;
646  }
647 
651  public function getRedirectSource(): ?string
652  {
653  return $this->context->getRedirectSource();
654  }
655 
659  public function insertCtrlCalls($a_parent, $a_child, string $a_comp_prefix): void
660  {
661  throw new ilCtrlException(__METHOD__ . " is deprecated and must not be used.");
662  }
663 
667  public function checkCurrentPathForClass(string $gui_class): bool
668  {
669  $class_cid = $this->structure->getClassCidByName($gui_class);
670  if (null === $class_cid) {
671  return false;
672  }
673 
674  return strpos(
675  $this->context->getPath()->getCidPath() ?? '',
676  $class_cid
677  ) !== false;
678  }
679 
683  public function getCurrentClassPath(): array
684  {
685  if (null === $this->context->getPath()->getCidPath()) {
686  return [];
687  }
688 
689  $class_paths = [];
690  foreach ($this->context->getPath()->getCidArray(SORT_ASC) as $cid) {
691  $class_paths[] = $this->structure->getObjNameByCid($cid);
692  }
693 
694  return $class_paths;
695  }
696 
700  public function attachObserver(ilCtrlObserver $observer, ilCtrlEvent $event = ilCtrlEvent::ALL): void
701  {
702  $this->subject->attach($observer, $event);
703  }
704 
708  public function detachObserver(ilCtrlObserver $observer, ilCtrlEvent $event = ilCtrlEvent::ALL): void
709  {
710  $this->subject->detach($observer, $event);
711  }
712 
713  protected function getDeterminedCommand(): ?string
714  {
715  // retrieve $_GET and $_POST parameters.
716  $post_command = $this->getPostCommand();
717  $get_command = $this->getQueryParam(self::PARAM_CMD);
718  $table_command = $this->getTableCommand();
719 
720  $is_post = (self::CMD_POST === $get_command);
721 
722  // if the $_GET command is 'post', either the $_POST
723  // command or $_GETs fallback command is used.
724  // for now, the table command is used as fallback as well,
725  // but this will be removed once the implementation of
726  // table actions change.
727  $command = ($is_post) ?
728  $post_command ?? $table_command ?? $this->getQueryParam(self::PARAM_CMD_FALLBACK) :
729  $get_command;
730 
731  // override the command that has been set during a
732  // request via ilCtrl::setCmd().
733  $context_command = $this->context->getCmd();
734  if (null !== $context_command && self::CMD_POST !== $context_command) {
735  $command = $context_command;
736  }
737 
738  if (null === $command) {
739  return $command;
740  }
741 
742  // if the command is for post requests, or the command
743  // is not considered safe, the csrf-validation must pass.
744  $cmd_class = $this->context->getCmdClass();
745  if (null !== $cmd_class && !$this->isCmdSecure($is_post, $cmd_class, $command)) {
746  $stored_token = $this->token_repository->getToken();
747  $sent_token = $this->getQueryParam(self::PARAM_CSRF_TOKEN);
748 
749  if (null !== $sent_token && $stored_token->verifyWith($sent_token)) {
750  return $command;
751  }
752  }
753 
754  return $command;
755  }
756 
763  private function getQueryParam(string $parameter_name): ?string
764  {
765  if ($this->get_parameters->has($parameter_name)) {
766  return $this->get_parameters->retrieve(
767  $parameter_name,
768  $this->refinery->to()->string()
769  );
770  }
771 
772  return null;
773  }
774 
778  private function getTableCommand(): ?string
779  {
780  if ($this->post_parameters->has('table_top_cmd')) {
781  return $this->post_parameters->retrieve(
782  'table_top_cmd',
783  $this->refinery->custom()->transformation(function ($item): ?string {
784  return is_array($item) ? key($item) : null;
785  })
786  );
787  }
788  // Button on top of the table
789  if ($this->post_parameters->has('select_cmd2')) {
790  return $this->post_parameters->has('selected_cmd2')
791  ? $this->post_parameters->retrieve('selected_cmd2', $this->refinery->to()->string())
792  : null;
793  }
794  // Button at bottom of the table
795  if ($this->post_parameters->has('select_cmd')) {
796  return $this->post_parameters->has('selected_cmd')
797  ? $this->post_parameters->retrieve('selected_cmd', $this->refinery->to()->string())
798  : null;
799  }
800 
801  return null;
802  }
803 
808  private function getPostCommand(): ?string
809  {
810  if ($this->post_parameters->has(self::PARAM_CMD)) {
811  return $this->post_parameters->retrieve(
812  self::PARAM_CMD,
813  $this->refinery->custom()->transformation(
814  static function ($value): ?string {
815  if (!empty($value)) {
816  if (is_array($value)) {
817  // this most likely only works by accident, but
818  // the selected or clicked command button will
819  // always be sent as first array entry. This
820  // should definitely be done differently.
821  return (string) array_key_first($value);
822  }
823 
824  return (string) $value;
825  }
826 
827  return null;
828  }
829  )
830  );
831  }
832 
833  return null;
834  }
835 
848  private function getTargetUrl(
849  $a_class,
850  ?string $a_cmd = null,
851  ?string $a_anchor = null,
852  bool $is_async = false,
853  bool $is_escaped = false,
854  bool $is_post = false
855  ): ?string {
856  if (empty($a_class)) {
857  throw new ilCtrlException(__METHOD__ . " was provided with an empty class or class-array.");
858  }
859 
860  $is_array = is_array($a_class);
861 
862  $path = $this->path_factory->find($this->context, $a_class);
863  if (null !== ($exception = $path->getException())) {
864  throw $exception;
865  }
866 
867  $base_class = $path->getBaseClass();
868  if (null === $base_class) {
869  throw new ilCtrlException("Cannot find a valid baseclass in the cid path '{$path->getCidPath()}'");
870  }
871 
872  $target_url = $this->context->getTargetScript();
873  $target_url = $this->appendParameterString(
874  $target_url,
875  self::PARAM_BASE_CLASS,
876  urlencode($base_class), // encode in case of namespaced classes
877  $is_escaped
878  );
879 
880  $cmd_class = ($is_array) ?
881  $a_class[array_key_last($a_class)] :
882  $a_class;
883 
884  // only append the cid path and command class params
885  // if they exist.
886  if (null !== $path->getNextCid($base_class)) {
887  $target_url = $this->appendParameterString(
888  $target_url,
889  self::PARAM_CID_PATH,
890  $path->getCidPath(),
891  $is_escaped
892  );
893 
894  $target_url = $this->appendParameterString(
895  $target_url,
896  self::PARAM_CMD_CLASS,
897  urlencode($cmd_class), // encode in case of namespaced classes
898  $is_escaped
899  );
900  }
901 
902  // if the target url is generated for form actions,
903  // the command must be set to 'post'.
904  if ($is_post) {
905  $target_url = $this->appendParameterString(
906  $target_url,
907  self::PARAM_CMD,
908  self::CMD_POST,
909  $is_escaped
910  );
911  }
912 
913  // the actual command is appended as fallback command
914  // for form actions and 'normal' get requests.
915  if (!empty($a_cmd)) {
916  $target_url = $this->appendParameterString(
917  $target_url,
918  ($is_post) ? self::PARAM_CMD_FALLBACK : self::PARAM_CMD,
919  $a_cmd,
920  $is_escaped
921  );
922  }
923 
924  // collect all parameters of classes within the current
925  // targets path and append them to the target url.
926  foreach ($path->getCidArray(SORT_ASC) as $cid) {
927  $class_name = $this->structure->getClassNameByCid($cid);
928  if (null === $class_name) {
929  throw new ilCtrlException("Classname for cid '$cid' in current path cannot be found.");
930  }
931 
932  $target_url = $this->appendParameterStringsByClass(
933  $class_name,
934  $target_url,
935  $is_escaped
936  );
937  }
938 
939  // append a csrf token if the command is considered
940  // unsafe or the link is for form actions.
941  if (!$this->isCmdSecure($is_post, $cmd_class, $a_cmd)) {
942  $token = $this->token_repository->getToken();
943  $target_url = $this->appendParameterString(
944  $target_url,
945  self::PARAM_CSRF_TOKEN,
946  $token->getToken(),
947  $is_escaped
948  );
949  }
950 
951  if ($is_async) {
952  $target_url = $this->appendParameterString(
953  $target_url,
954  self::PARAM_CMD_MODE,
955  self::CMD_MODE_ASYNC,
956  $is_escaped
957  );
958  }
959 
960  if (!empty($a_anchor)) {
961  $target_url .= "#$a_anchor";
962  }
963 
964  return $target_url;
965  }
966 
973  private function modifyUrlWithPluginHooks(string $target_url): string
974  {
975  $ui_plugins = $this->component_factory->getActivePluginsInSlot("uihk");
976  foreach ($ui_plugins as $plugin_instance) {
979  $html = $plugin_instance
980  ->getUIClassInstance()
981  ->getHTML(
982  'components/ILIAS/Utilities',
983  'redirect',
984  ["html" => $target_url]
985  );
986 
987  if (ilUIHookPluginGUI::KEEP !== $html['mode']) {
988  $target_url = $plugin_instance
989  ->getUIClassInstance()
990  ->modifyHTML(
991  $target_url,
992  $html
993  );
994  }
995  }
996 
997  return $target_url;
998  }
999 
1007  private function isCmdSecure(bool $is_post, string $cmd_class, ?string $cmd = null): bool
1008  {
1009  // if no command is specified, the command is
1010  // considered safe if it's not a POST command.
1011  if (null === $cmd) {
1012  return !$is_post;
1013  }
1014 
1015  // if the given command class doesn't exist, the
1016  // command is not considered safe as it might've been
1017  // tampered with.
1018  $obj_name = $this->structure->getObjNameByName($cmd_class);
1019  if (null === $obj_name) {
1020  return false;
1021  }
1022 
1023  // if the command class does not yet implement the
1024  // ilCtrlSecurityInterface, the command is considered
1025  // safe if it's not a POST command.
1026  if (!is_a($obj_name, ilCtrlSecurityInterface::class, true)) {
1027  return !$is_post;
1028  }
1029 
1030  // the post command is considered safe if it's contained
1031  // in the list of safe post commands.
1032  if ($is_post) {
1033  return in_array($cmd, $this->structure->getSafeCommandsByName($cmd_class), true);
1034  }
1035 
1036  // the get command is considered safe if it's not
1037  // contained in the list of unsafe get commands.
1038  return !in_array($cmd, $this->structure->getUnsafeCommandsByName($cmd_class), true);
1039  }
1040 
1050  string $class_name,
1051  string $target_url,
1052  bool $is_escaped = false
1053  ): string {
1054  $class_parameters = $this->getParameterArrayByClass($class_name);
1055  if (!empty($class_parameters)) {
1056  foreach ($class_parameters as $key => $value) {
1057  $target_url = $this->appendParameterString(
1058  $target_url,
1059  $key,
1060  $value,
1061  $is_escaped
1062  );
1063  }
1064  }
1065 
1066  return $target_url;
1067  }
1068 
1077  private function appendParameterString(
1078  string $url,
1079  string $parameter_name,
1080  $value,
1081  bool $is_escaped = false
1082  ): string {
1083  // transform value into a string, since null will fail we can
1084  // (temporarily) use the null coalescing operator.
1085  $value = $this->refinery->kindlyTo()->string()->transform($value ?? '');
1086 
1087  if ('' === $value) {
1088  return $url;
1089  }
1090 
1092  $parsed_url = parse_url(str_replace('&amp;', '&', $url));
1093 
1094  $query_parameters = $this->query_parser->parseQueriesOfURL($parsed_url['query'] ?? '');
1095 
1096  // update the given parameter or add it to the list.
1097  $query_parameters[$parameter_name] = $value;
1098 
1099  $new_url = $parsed_url['path'] ?? $this->context->getTargetScript();
1100  // we currently only escape ampersands (don't blame me).
1101  $ampersand = ($is_escaped) ? '&amp;' : '&';
1102 
1103  foreach ($query_parameters as $parameter => $parameter_value) {
1104  $new_url .= (strpos($new_url, '?') !== false) ?
1105  $ampersand . "$parameter=$parameter_value" :
1106  "?$parameter=$parameter_value";
1107  }
1108 
1109  return $new_url;
1110  }
1111 
1117  private function populateCall(string $class_name, string $cmd_mode): void
1118  {
1119  $obj_name = $this->structure->getObjNameByName($class_name);
1120 
1121  $this->stacktrace[] = [
1122  self::PARAM_CMD_CLASS => $obj_name,
1123  self::PARAM_CMD_MODE => $cmd_mode,
1124  self::PARAM_CMD => $this->getDeterminedCommand(),
1125  ];
1126  }
1127 
1134  private function getClassByObject($object): string
1135  {
1136  return (is_object($object)) ? get_class($object) : $object;
1137  }
1138 }
getPostCommand()
Returns the current $_POST command.
getFormAction(object $a_gui_obj, ?string $a_fallback_cmd=null, ?string $a_anchor=null, bool $is_async=false, bool $has_xml_style=false)
getContextObjId()
$context
Definition: webdav.php:31
getTargetUrl( $a_class, ?string $a_cmd=null, ?string $a_anchor=null, bool $is_async=false, bool $is_escaped=false, bool $is_post=false)
Helper function that returns a target URL string.
getClassByObject($object)
Helper function that returns the class name of a mixed (object or string) parameter.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
getCmd(?string $fallback_command=null)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
getLinkTarget(object $a_gui_obj, ?string $a_cmd=null, ?string $a_anchor=null, bool $is_async=false, bool $has_xml_style=false)
insertCtrlCalls($a_parent, $a_child, string $a_comp_prefix)
$response
Definition: xapitoken.php:93
checkCurrentPathForClass(string $gui_class)
lookupOriginalClassName(string $a_class)
setParameterByClass(string $a_class, string $a_parameter, $a_value)
$url
Definition: shib_logout.php:66
clearParameterByClass(string $a_class, string $a_parameter)
object $exec_object
redirectByClass( $a_class, ?string $a_cmd=null, ?string $a_anchor=null, bool $is_async=false)
forwardCommand(object $a_gui_object)
getRedirectSource()
event string being used if
$path
Definition: ltiservices.php:29
ilCtrlEvent
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
isCmdSecure(bool $is_post, string $cmd_class, ?string $cmd=null)
Returns whether a given command is considered safe or not.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
getTableCommand()
getDeterminedCommand()
getClassForClasspath(string $a_class_path)
getParentReturn(object $a_gui_obj)
getNextClass($a_gui_class=null)
getQueryParam(string $parameter_name)
Returns a parameter with the given name from the current GET request.
setReturnByClass(string $a_class, ?string $a_cmd=null)
redirect(object $a_gui_obj, ?string $a_cmd=null, ?string $a_anchor=null, bool $is_async=false)
__construct(protected ilCtrlStructureInterface $structure, protected ilCtrlTokenRepositoryInterface $token_repository, protected ilCtrlPathFactoryInterface $path_factory, protected ilCtrlContextInterface $context, protected ResponseSenderStrategy $response_sender, protected ServerRequestInterface $server_request, protected RequestWrapper $post_parameters, protected RequestWrapper $get_parameters, protected Refinery $refinery, protected ilComponentFactory $component_factory, protected ilCtrlSubject $subject, protected ilCtrlQueryParserInterface $query_parser,)
$token
Definition: xapitoken.php:70
setReturn(object $a_gui_obj, ?string $a_cmd=null)
clearParameters(object $a_gui_obj)
appendParameterStringsByClass(string $class_name, string $target_url, bool $is_escaped=false)
Appends all parameters for a given class to the given URL.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
$structure
TOTAL STRUCTURE.
lookupClassPath(string $a_class)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
clearParametersByClass(string $a_class)
getParentReturnByClass(string $a_class)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
getCurrentClassPath()
detachObserver(ilCtrlObserver $observer, ilCtrlEvent $event=ilCtrlEvent::ALL)
getParameterArrayByClass(string $a_class)
setTargetScript(string $a_target_script)
getHTML(object $a_gui_object, ?array $a_parameters=null)
getCallHistory()
saveParameter(object $a_gui_obj, $a_parameter)
getParameterArray(object $a_gui_obj)
getFormActionByClass( $a_class, ?string $a_fallback_cmd=null, ?string $a_anchor=null, bool $is_async=false, bool $has_xml_style=false)
saveParameterByClass(string $a_class, $a_parameter)
setParameter(object $a_gui_obj, string $a_parameter, $a_value)
redirectToURL(string $target_url)
attachObserver(ilCtrlObserver $observer, ilCtrlEvent $event=ilCtrlEvent::ALL)
returnToParent(object $a_gui_obj, ?string $a_anchor=null)
populateCall(string $class_name, string $cmd_mode)
Helper function that populates a call in the current stacktrace.
header()
expected output: > ILIAS shows the rendered Component.
Definition: header.php:29
array $stacktrace
exit
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
setContextObject(int $obj_id, string $obj_type)
callBaseClass(?string $a_base_class=null)
getContextObjType()
getLinkTargetByClass( $a_class, ?string $a_cmd=null, ?string $a_anchor=null, bool $is_async=false, bool $has_xml_style=false)
string $command