ILIAS  release_9 Revision v9.13-25-g2c18ec4c24f
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 setCmd(?string $a_cmd): void
177  {
178  if (!empty($a_cmd)) {
179  $this->context->setCmd($a_cmd);
180  $this->command = $a_cmd;
181  } else {
182  $this->context->setCmd(null);
183  $this->command = null;
184  }
185  }
186 
190  public function getCmdClass(): ?string
191  {
192  if (null !== ($cmd_class = $this->context->getCmdClass())) {
193  return strtolower($this->structure->getObjNameByName($cmd_class));
194  }
195 
196  return '';
197  }
198 
202  public function setCmdClass($a_cmd_class): void
203  {
204  if (!empty($a_cmd_class)) {
205  $this->context->setCmdClass($a_cmd_class);
206  } else {
207  $this->context->setCmdClass(null);
208  }
209  }
210 
214  public function getNextClass($a_gui_class = null): ?string
215  {
216  if (null === $a_gui_class && null === $this->exec_object) {
217  return '';
218  }
219 
220  if (null === $this->context->getPath()) {
221  return '';
222  }
223 
224  $next_cid = $this->context->getPath()->getNextCid(
225  $this->getClassByObject($a_gui_class ?? $this->exec_object)
226  );
227 
228  if (null !== $next_cid) {
229  return strtolower($this->structure->getObjNameByCid($next_cid) ?? '');
230  }
231 
232  return '';
233  }
234 
238  public function saveParameter(object $a_gui_obj, $a_parameter): void
239  {
240  $this->saveParameterByClass($this->getClassByObject($a_gui_obj), $a_parameter);
241  }
242 
246  public function saveParameterByClass(string $a_class, $a_parameter): void
247  {
248  if (!empty($a_parameter)) {
249  if (is_array($a_parameter)) {
250  foreach ($a_parameter as $parameter) {
251  $this->structure->setPermanentParameterByClass($a_class, $parameter);
252  }
253  } else {
254  $this->structure->setPermanentParameterByClass($a_class, $a_parameter);
255  }
256  }
257  }
258 
262  public function setParameter(object $a_gui_obj, string $a_parameter, $a_value): void
263  {
264  $this->setParameterByClass($this->getClassByObject($a_gui_obj), $a_parameter, $a_value);
265  }
266 
270  public function setParameterByClass(string $a_class, string $a_parameter, $a_value): void
271  {
272  $this->structure->setTemporaryParameterByClass($a_class, $a_parameter, $a_value);
273  }
274 
278  public function getParameterArray(object $a_gui_obj): array
279  {
280  return $this->getParameterArrayByClass($this->getClassByObject($a_gui_obj));
281  }
282 
286  public function getParameterArrayByClass(string $a_class): array
287  {
288  if (null === $this->structure->getClassCidByName($a_class)) {
289  throw new ilCtrlException("Cannot find provided class '$a_class' in the control structure.");
290  }
291 
292  $parameters = [];
293  $permanent_parameters = $this->structure->getPermanentParametersByClass($a_class);
294  if (null !== $permanent_parameters) {
295  foreach ($permanent_parameters as $parameter) {
296  $parameters[$parameter] = $this->getQueryParam($parameter);
297  }
298  }
299 
300  $temporary_parameters = $this->structure->getTemporaryParametersByClass($a_class);
301  if (null !== $temporary_parameters) {
302  // override existing ones, as temporary parameters
303  // are prioritised over fetched ones.
304  foreach ($temporary_parameters as $key => $value) {
305  $parameters[$key] = $value;
306  }
307  }
308 
309  return $parameters;
310  }
311 
315  public function clearParameters(object $a_gui_obj): void
316  {
317  $this->clearParametersByClass($this->getClassByObject($a_gui_obj));
318  }
319 
323  public function clearParametersByClass(string $a_class): void
324  {
325  // apparently permanent parameters should not be removable,
326  // therefore the line below stays commented:
327  // $this->structure->removePermanentParametersByClass($a_class);
328  $this->structure->removeTemporaryParametersByClass($a_class);
329  }
330 
334  public function clearParameterByClass(string $a_class, string $a_parameter): void
335  {
336  $this->structure->removeSingleParameterByClass($a_class, $a_parameter);
337  }
338 
342  public function getLinkTarget(
343  object $a_gui_obj,
344  string $a_cmd = null,
345  string $a_anchor = null,
346  bool $is_async = false,
347  bool $has_xml_style = false
348  ): string {
349  return $this->getLinkTargetByClass(
350  $this->getClassByObject($a_gui_obj),
351  $a_cmd,
352  $a_anchor,
353  $is_async,
354  $has_xml_style
355  );
356  }
357 
361  public function getLinkTargetByClass(
362  $a_class,
363  string $a_cmd = null,
364  string $a_anchor = null,
365  bool $is_async = false,
366  bool $has_xml_style = false
367  ): string {
368  return $this->getTargetUrl(
369  $a_class,
370  $a_cmd,
371  $a_anchor,
372  $is_async,
373  $has_xml_style
374  ) ?? '';
375  }
376 
380  public function getFormAction(
381  object $a_gui_obj,
382  string $a_fallback_cmd = null,
383  string $a_anchor = null,
384  bool $is_async = false,
385  bool $has_xml_style = false
386  ): string {
387  return $this->getFormActionByClass(
388  $this->getClassByObject($a_gui_obj),
389  $a_fallback_cmd,
390  $a_anchor,
391  $is_async,
392  $has_xml_style
393  );
394  }
395 
399  public function getFormActionByClass(
400  $a_class,
401  string $a_fallback_cmd = null,
402  string $a_anchor = null,
403  bool $is_async = false,
404  bool $has_xml_style = false
405  ): string {
406  return $this->getTargetUrl(
407  $a_class,
408  $a_fallback_cmd,
409  $a_anchor,
410  $is_async,
411  $has_xml_style,
412  true
413  ) ?? '';
414  }
415 
419  public function redirect(
420  object $a_gui_obj,
421  string $a_cmd = null,
422  string $a_anchor = null,
423  bool $is_async = false
424  ): void {
425  $this->redirectByClass(
426  $this->getClassByObject($a_gui_obj),
427  $a_cmd,
428  $a_anchor,
429  $is_async
430  );
431  }
432 
436  public function redirectByClass(
437  $a_class,
438  string $a_cmd = null,
439  string $a_anchor = null,
440  bool $is_async = false
441  ): void {
442  $this->redirectToURL(
443  $this->getLinkTargetByClass(
444  $a_class,
445  $a_cmd,
446  $a_anchor,
447  $is_async
448  )
449  );
450  }
451 
455  public function redirectToURL(string $target_url): void
456  {
457  // prepend the ILIAS HTTP path if it wasn't already.
458  if (defined("ILIAS_HTTP_PATH") &&
459  strpos($target_url, "://") === false &&
460  strpos($target_url, "/") !== 0
461  ) {
462  $target_url = ILIAS_HTTP_PATH . "/" . $target_url;
463  }
464 
465  // this line can be dropped after discussion with TB or JF,
466  // it keeps the functionality of UI plugin hooks alive.
467  $target_url = $this->modifyUrlWithPluginHooks($target_url);
468 
469  // initialize http response object
470  $response = new Response();
471 
472  // there's an exceptional case for asynchronous file uploads
473  // where a json response is delivered.
474  if ('application/json' === $this->server_request->getHeaderLine('Accept')) {
475  try {
476  $body = Streams::ofString(
477  json_encode(
478  [
479  'redirect_url' => $target_url,
480  'success' => true,
481  'message' => 'called redirect after asynchronous file-upload request.',
482  ],
483  JSON_THROW_ON_ERROR
484  )
485  );
486  } catch (Throwable $exception) {
487  $body = Streams::ofString($exception->getMessage());
488  }
489 
490  $response = $response->withBody($body);
491  } else {
492  $response = $response->withAddedHeader('Location', $target_url);
493  }
494 
495  // manually trigger session_write_close() due to exceptions stored
496  // in the ILIAS database, otherwise this method is called by exit()
497  // which leads to the exceptions not being written to the database.
498  session_write_close();
499 
500  try {
501  $this->response_sender->sendResponse($response);
502  } catch (ResponseSendingException $e) {
503  header("Location: $target_url");
504  if ('application/json' === $this->server_request->getHeaderLine('Accept')) {
505  $content = (null !== $response->getBody()) ?
506  $response->getBody()->getContents() :
507  [];
508 
509  echo json_encode($content, JSON_THROW_ON_ERROR);
510  }
511  } catch (Throwable $t) {
512  header("Location: $target_url");
513  echo $t->getMessage();
514  }
515 
516  exit;
517  }
518 
522  public function setContextObject(int $obj_id, string $obj_type): void
523  {
524  // cannot process object without object type.
525  if (!empty($obj_type)) {
526  $this->context->setObjId($obj_id);
527  $this->context->setObjType($obj_type);
528  }
529  }
530 
534  public function getContextObjId(): ?int
535  {
536  return $this->context->getObjId();
537  }
538 
542  public function getContextObjType(): ?string
543  {
544  return $this->context->getObjType();
545  }
546 
550  public function getCallHistory(): array
551  {
552  return $this->stacktrace;
553  }
554 
558  public function lookupClassPath(string $a_class): string
559  {
560  $path = $this->structure->getRelativePathByName($a_class);
561  if (null === $path) {
562  throw new ilCtrlException("Class '$a_class' cannot be found in the control structure.");
563  }
564 
565  return $path;
566  }
567 
571  public function lookupOriginalClassName(string $a_class): ?string
572  {
573  return $this->structure->getObjNameByName($a_class);
574  }
575 
579  public function getClassForClasspath(string $a_class_path): string
580  {
581  $path_info = pathinfo($a_class_path);
582 
583  return substr($path_info['basename'], 6, -4);
584  }
585 
589  public function setTargetScript(string $a_target_script): void
590  {
591  $this->context->setTargetScript($a_target_script);
592  }
593 
597  public function isAsynch(): bool
598  {
599  return $this->context->isAsync();
600  }
601 
605  public function setReturn(object $a_gui_obj, string $a_cmd = null): void
606  {
607  $this->setReturnByClass($this->getClassByObject($a_gui_obj), $a_cmd);
608  }
609 
613  public function setReturnByClass(string $a_class, string $a_cmd = null): void
614  {
615  $this->structure->setReturnTargetByClass(
616  $a_class,
617  $this->getLinkTargetByClass(
618  $a_class,
619  $a_cmd
620  )
621  );
622  }
623 
627  public function returnToParent(object $a_gui_obj, string $a_anchor = null): void
628  {
629  $class_name = $this->getClassByObject($a_gui_obj);
630  $target_url = $this->getParentReturnByClass($class_name);
631 
632  // append redirect source to target url.
633  $target_url = $this->appendParameterString(
634  $target_url,
635  self::PARAM_REDIRECT,
636  $class_name
637  );
638 
639  // append the provided anchor if necessary.
640  if (null !== $a_anchor) {
641  $target_url .= "#$a_anchor";
642  }
643 
644  $this->redirectToURL($target_url);
645  }
646 
650  public function getParentReturn(object $a_gui_obj): ?string
651  {
652  return $this->getParentReturnByClass($this->getClassByObject($a_gui_obj));
653  }
654 
658  public function getParentReturnByClass(string $a_class): ?string
659  {
660  $path = $this->path_factory->find($this->context, $a_class);
661  if (null !== $path->getCidPath()) {
662  foreach ($path->getCidArray() as $cid) {
663  $current_class = $this->structure->getClassNameByCid($cid);
664  $return_target = $this->structure->getReturnTargetByClass($current_class);
665  if (null !== $return_target) {
666  return $return_target;
667  }
668  }
669  }
670 
671  return null;
672  }
673 
677  public function getRedirectSource(): ?string
678  {
679  return $this->context->getRedirectSource();
680  }
681 
685  public function insertCtrlCalls($a_parent, $a_child, string $a_comp_prefix): void
686  {
687  throw new ilCtrlException(__METHOD__ . " is deprecated and must not be used.");
688  }
689 
693  public function checkCurrentPathForClass(string $gui_class): bool
694  {
695  $class_cid = $this->structure->getClassCidByName($gui_class);
696  if (null === $class_cid) {
697  return false;
698  }
699 
700  return strpos(
701  $this->context->getPath()->getCidPath() ?? '',
702  $class_cid
703  ) !== false;
704  }
705 
709  public function getCurrentClassPath(): array
710  {
711  if (null === $this->context->getPath()->getCidPath()) {
712  return [];
713  }
714 
715  $class_paths = [];
716  foreach ($this->context->getPath()->getCidArray(SORT_ASC) as $cid) {
717  $class_paths[] = $this->structure->getObjNameByCid($cid);
718  }
719 
720  return $class_paths;
721  }
722 
726  public function attachObserver(ilCtrlObserver $observer, ilCtrlEvent $event = ilCtrlEvent::ALL): void
727  {
728  $this->subject->attach($observer, $event);
729  }
730 
734  public function detachObserver(ilCtrlObserver $observer, ilCtrlEvent $event = ilCtrlEvent::ALL): void
735  {
736  $this->subject->detach($observer, $event);
737  }
738 
739  protected function getDeterminedCommand(): ?string
740  {
741  // retrieve $_GET and $_POST parameters.
742  $post_command = $this->getPostCommand();
743  $get_command = $this->getQueryParam(self::PARAM_CMD);
744  $table_command = $this->getTableCommand();
745 
746  $is_post = (self::CMD_POST === $get_command);
747 
748  // if the $_GET command is 'post', either the $_POST
749  // command or $_GETs fallback command is used.
750  // for now, the table command is used as fallback as well,
751  // but this will be removed once the implementation of
752  // table actions change.
753  $command = ($is_post) ?
754  $post_command ?? $table_command ?? $this->getQueryParam(self::PARAM_CMD_FALLBACK) :
755  $get_command;
756 
757  // override the command that has been set during a
758  // request via ilCtrl::setCmd().
759  $context_command = $this->context->getCmd();
760  if (null !== $context_command && self::CMD_POST !== $context_command) {
761  $command = $context_command;
762  }
763 
764  if (null === $command) {
765  return $command;
766  }
767 
768  // if the command is for post requests, or the command
769  // is not considered safe, the csrf-validation must pass.
770  $cmd_class = $this->context->getCmdClass();
771  if (null !== $cmd_class && !$this->isCmdSecure($is_post, $cmd_class, $command)) {
772  $stored_token = $this->token_repository->getToken();
773  $sent_token = $this->getQueryParam(self::PARAM_CSRF_TOKEN);
774 
775  if (null !== $sent_token && $stored_token->verifyWith($sent_token)) {
776  return $command;
777  }
778  return null;
779  }
780 
781  return $command;
782  }
783 
790  private function getQueryParam(string $parameter_name): ?string
791  {
792  if ($this->get_parameters->has($parameter_name)) {
793  return $this->get_parameters->retrieve(
794  $parameter_name,
795  $this->refinery->to()->string()
796  );
797  }
798 
799  return null;
800  }
801 
805  private function getTableCommand(): ?string
806  {
807  if ($this->post_parameters->has('table_top_cmd')) {
808  return $this->post_parameters->retrieve(
809  'table_top_cmd',
810  $this->refinery->custom()->transformation(function ($item): ?string {
811  return is_array($item) ? key($item) : null;
812  })
813  );
814  }
815  // Button on top of the table
816  if ($this->post_parameters->has('select_cmd2')) {
817  return $this->post_parameters->has('selected_cmd2')
818  ? $this->post_parameters->retrieve('selected_cmd2', $this->refinery->to()->string())
819  : null;
820  }
821  // Button at bottom of the table
822  if ($this->post_parameters->has('select_cmd')) {
823  return $this->post_parameters->has('selected_cmd')
824  ? $this->post_parameters->retrieve('selected_cmd', $this->refinery->to()->string())
825  : null;
826  }
827 
828  return null;
829  }
830 
835  private function getPostCommand(): ?string
836  {
837  if ($this->post_parameters->has(self::PARAM_CMD)) {
838  return $this->post_parameters->retrieve(
839  self::PARAM_CMD,
840  $this->refinery->custom()->transformation(
841  static function ($value): ?string {
842  if (!empty($value)) {
843  if (is_array($value)) {
844  // this most likely only works by accident, but
845  // the selected or clicked command button will
846  // always be sent as first array entry. This
847  // should definitely be done differently.
848  return (string) array_key_first($value);
849  }
850 
851  return (string) $value;
852  }
853 
854  return null;
855  }
856  )
857  );
858  }
859 
860  return null;
861  }
862 
875  private function getTargetUrl(
876  $a_class,
877  string $a_cmd = null,
878  string $a_anchor = null,
879  bool $is_async = false,
880  bool $is_escaped = false,
881  bool $is_post = false
882  ): ?string {
883  if (empty($a_class)) {
884  throw new ilCtrlException(__METHOD__ . " was provided with an empty class or class-array.");
885  }
886 
887  $is_array = is_array($a_class);
888 
889  $path = $this->path_factory->find($this->context, $a_class);
890  if (null !== ($exception = $path->getException())) {
891  throw $exception;
892  }
893 
894  $base_class = $path->getBaseClass();
895  if (null === $base_class) {
896  throw new ilCtrlException("Cannot find a valid baseclass in the cid path '{$path->getCidPath()}'");
897  }
898 
899  $target_url = $this->context->getTargetScript();
900  $target_url = $this->appendParameterString(
901  $target_url,
902  self::PARAM_BASE_CLASS,
903  urlencode($base_class), // encode in case of namespaced classes
904  $is_escaped
905  );
906 
907  $cmd_class = ($is_array) ?
908  $a_class[array_key_last($a_class)] :
909  $a_class;
910 
911  // only append the cid path and command class params
912  // if they exist.
913  if (null !== $path->getNextCid($base_class)) {
914  $target_url = $this->appendParameterString(
915  $target_url,
916  self::PARAM_CID_PATH,
917  $path->getCidPath(),
918  $is_escaped
919  );
920 
921  $target_url = $this->appendParameterString(
922  $target_url,
923  self::PARAM_CMD_CLASS,
924  urlencode($cmd_class), // encode in case of namespaced classes
925  $is_escaped
926  );
927  }
928 
929  // if the target url is generated for form actions,
930  // the command must be set to 'post'.
931  if ($is_post) {
932  $target_url = $this->appendParameterString(
933  $target_url,
934  self::PARAM_CMD,
935  self::CMD_POST,
936  $is_escaped
937  );
938  }
939 
940  // the actual command is appended as fallback command
941  // for form actions and 'normal' get requests.
942  if (!empty($a_cmd)) {
943  $target_url = $this->appendParameterString(
944  $target_url,
945  ($is_post) ? self::PARAM_CMD_FALLBACK : self::PARAM_CMD,
946  $a_cmd,
947  $is_escaped
948  );
949  }
950 
951  // collect all parameters of classes within the current
952  // targets path and append them to the target url.
953  foreach ($path->getCidArray(SORT_ASC) as $cid) {
954  $class_name = $this->structure->getClassNameByCid($cid);
955  if (null === $class_name) {
956  throw new ilCtrlException("Classname for cid '$cid' in current path cannot be found.");
957  }
958 
959  $target_url = $this->appendParameterStringsByClass(
960  $class_name,
961  $target_url,
962  $is_escaped
963  );
964  }
965 
966  // append a csrf token if the command is considered
967  // unsafe or the link is for form actions.
968  if (!$this->isCmdSecure($is_post, $cmd_class, $a_cmd)) {
969  $token = $this->token_repository->getToken();
970  $target_url = $this->appendParameterString(
971  $target_url,
972  self::PARAM_CSRF_TOKEN,
973  $token->getToken(),
974  $is_escaped
975  );
976  }
977 
978  if ($is_async) {
979  $target_url = $this->appendParameterString(
980  $target_url,
981  self::PARAM_CMD_MODE,
982  self::CMD_MODE_ASYNC,
983  $is_escaped
984  );
985  }
986 
987  if (!empty($a_anchor)) {
988  $target_url .= "#$a_anchor";
989  }
990 
991  return $target_url;
992  }
993 
1000  private function modifyUrlWithPluginHooks(string $target_url): string
1001  {
1002  $ui_plugins = $this->component_factory->getActivePluginsInSlot("uihk");
1003  foreach ($ui_plugins as $plugin_instance) {
1006  $html = $plugin_instance
1007  ->getUIClassInstance()
1008  ->getHTML(
1009  'Services/Utilities',
1010  'redirect',
1011  ["html" => $target_url]
1012  );
1013 
1014  if (ilUIHookPluginGUI::KEEP !== $html['mode']) {
1015  $target_url = $plugin_instance
1016  ->getUIClassInstance()
1017  ->modifyHTML(
1018  $target_url,
1019  $html
1020  );
1021  }
1022  }
1023 
1024  return $target_url;
1025  }
1026 
1034  private function isCmdSecure(bool $is_post, string $cmd_class, string $cmd = null): bool
1035  {
1036  // if no command is specified, the command is
1037  // considered safe if it's not a POST command.
1038  if (null === $cmd) {
1039  return !$is_post;
1040  }
1041 
1042  // if the given command class doesn't exist, the
1043  // command is not considered safe as it might've been
1044  // tampered with.
1045  $obj_name = $this->structure->getObjNameByName($cmd_class);
1046  if (null === $obj_name) {
1047  return false;
1048  }
1049 
1050  // if the command class does not yet implement the
1051  // ilCtrlSecurityInterface, the command is considered
1052  // safe if it's not a POST command.
1053  if (!is_a($obj_name, ilCtrlSecurityInterface::class, true)) {
1054  return !$is_post;
1055  }
1056 
1057  // the post command is considered safe if it's contained
1058  // in the list of safe post commands.
1059  if ($is_post) {
1060  return in_array($cmd, $this->structure->getSafeCommandsByName($cmd_class), true);
1061  }
1062 
1063  // the get command is considered safe if it's not
1064  // contained in the list of unsafe get commands.
1065  return !in_array($cmd, $this->structure->getUnsafeCommandsByName($cmd_class), true);
1066  }
1067 
1077  string $class_name,
1078  string $target_url,
1079  bool $is_escaped = false
1080  ): string {
1081  $class_parameters = $this->getParameterArrayByClass($class_name);
1082  if (!empty($class_parameters)) {
1083  foreach ($class_parameters as $key => $value) {
1084  $target_url = $this->appendParameterString(
1085  $target_url,
1086  $key,
1087  $value,
1088  $is_escaped
1089  );
1090  }
1091  }
1092 
1093  return $target_url;
1094  }
1095 
1104  private function appendParameterString(
1105  string $url,
1106  string $parameter_name,
1107  $value,
1108  bool $is_escaped = false
1109  ): string {
1110  // transform value into a string, since null will fail we can
1111  // (temporarily) use the null coalescing operator.
1112  $value = $this->refinery->kindlyTo()->string()->transform($value ?? '');
1113 
1114  if ('' === $value) {
1115  return $url;
1116  }
1117 
1119  $parsed_url = parse_url(str_replace('&amp;', '&', $url));
1120 
1121  $query_parameters = $this->query_parser->parseQueriesOfURL($parsed_url['query'] ?? '');
1122 
1123  // update the given parameter or add it to the list.
1124  $query_parameters[$parameter_name] = $value;
1125 
1126  $new_url = $parsed_url['path'] ?? $this->context->getTargetScript();
1127  // we currently only escape ampersands (don't blame me).
1128  $ampersand = ($is_escaped) ? '&amp;' : '&';
1129 
1130  foreach ($query_parameters as $parameter => $parameter_value) {
1131  $new_url .= (strpos($new_url, '?') !== false) ?
1132  $ampersand . "$parameter=$parameter_value" :
1133  "?$parameter=$parameter_value";
1134  }
1135 
1136  return $new_url;
1137  }
1138 
1144  private function populateCall(string $class_name, string $cmd_mode): void
1145  {
1146  $obj_name = $this->structure->getObjNameByName($class_name);
1147 
1148  $this->stacktrace[] = [
1149  self::PARAM_CMD_CLASS => $obj_name,
1150  self::PARAM_CMD_MODE => $cmd_mode,
1151  self::PARAM_CMD => $this->getDeterminedCommand(),
1152  ];
1153  }
1154 
1161  private function getClassByObject($object): string
1162  {
1163  return (is_object($object)) ? get_class($object) : $object;
1164  }
1165 }
getPostCommand()
Returns the current $_POST command.
getContextObjId()
exit
Definition: login.php:29
getLinkTargetByClass( $a_class, string $a_cmd=null, string $a_anchor=null, bool $is_async=false, bool $has_xml_style=false)
$context
Definition: webdav.php:31
redirectByClass( $a_class, string $a_cmd=null, string $a_anchor=null, bool $is_async=false)
getHTML(object $a_gui_object, array $a_parameters=null)
setCmd(?string $a_cmd)
callBaseClass(string $a_base_class=null)
redirect(object $a_gui_obj, string $a_cmd=null, string $a_anchor=null, bool $is_async=false)
getClassByObject($object)
Helper function that returns the class name of a mixed (object or string) parameter.
getCmd(string $fallback_command=null)
setCmdClass($a_cmd_class)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
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)
isCmdSecure(bool $is_post, string $cmd_class, string $cmd=null)
Returns whether a given command is considered safe or not.
clearParameterByClass(string $a_class, string $a_parameter)
setReturnByClass(string $a_class, string $a_cmd=null)
returnToParent(object $a_gui_obj, string $a_anchor=null)
object $exec_object
forwardCommand(object $a_gui_object)
getRedirectSource()
event string being used if
$path
Definition: ltiservices.php:32
ilCtrlEvent
Interface ilCtrlTokenRepositoryInterface describes an ilCtrl token.
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.
__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
getFormActionByClass( $a_class, string $a_fallback_cmd=null, string $a_anchor=null, bool $is_async=false, bool $has_xml_style=false)
Interface RequestWrapper.
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.
string $key
Consumer key/client ID value.
Definition: System.php:193
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
$structure
TOTAL STRUCTURE.
$url
Definition: ltiregstart.php:35
lookupClassPath(string $a_class)
Interface ilCtrlPathFactoryInterface describes the ilCtrl Path factory.
clearParametersByClass(string $a_class)
getParentReturnByClass(string $a_class)
ilCtrl exceptions
getCurrentClassPath()
getLinkTarget(object $a_gui_obj, string $a_cmd=null, string $a_anchor=null, bool $is_async=false, bool $has_xml_style=false)
detachObserver(ilCtrlObserver $observer, ilCtrlEvent $event=ilCtrlEvent::ALL)
getParameterArrayByClass(string $a_class)
setTargetScript(string $a_target_script)
getCallHistory()
saveParameter(object $a_gui_obj, $a_parameter)
getParameterArray(object $a_gui_obj)
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)
populateCall(string $class_name, string $cmd_mode)
Helper function that populates a call in the current stacktrace.
getFormAction(object $a_gui_obj, string $a_fallback_cmd=null, string $a_anchor=null, bool $is_async=false, bool $has_xml_style=false)
array $stacktrace
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.
setReturn(object $a_gui_obj, string $a_cmd=null)
setContextObject(int $obj_id, string $obj_type)
getContextObjType()
Refinery Factory $refinery
string $command