ILIAS  trunk Revision v11.0_alpha-2638-g80c1d007f79
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  return null;
753  }
754 
755  return $command;
756  }
757 
764  private function getQueryParam(string $parameter_name): ?string
765  {
766  if ($this->get_parameters->has($parameter_name)) {
767  return $this->get_parameters->retrieve(
768  $parameter_name,
769  $this->refinery->to()->string()
770  );
771  }
772 
773  return null;
774  }
775 
779  private function getTableCommand(): ?string
780  {
781  if ($this->post_parameters->has('table_top_cmd')) {
782  return $this->post_parameters->retrieve(
783  'table_top_cmd',
784  $this->refinery->custom()->transformation(function ($item): ?string {
785  return is_array($item) ? key($item) : null;
786  })
787  );
788  }
789  // Button on top of the table
790  if ($this->post_parameters->has('select_cmd2')) {
791  return $this->post_parameters->has('selected_cmd2')
792  ? $this->post_parameters->retrieve('selected_cmd2', $this->refinery->to()->string())
793  : null;
794  }
795  // Button at bottom of the table
796  if ($this->post_parameters->has('select_cmd')) {
797  return $this->post_parameters->has('selected_cmd')
798  ? $this->post_parameters->retrieve('selected_cmd', $this->refinery->to()->string())
799  : null;
800  }
801 
802  return null;
803  }
804 
809  private function getPostCommand(): ?string
810  {
811  if ($this->post_parameters->has(self::PARAM_CMD)) {
812  return $this->post_parameters->retrieve(
813  self::PARAM_CMD,
814  $this->refinery->custom()->transformation(
815  static function ($value): ?string {
816  if (!empty($value)) {
817  if (is_array($value)) {
818  // this most likely only works by accident, but
819  // the selected or clicked command button will
820  // always be sent as first array entry. This
821  // should definitely be done differently.
822  return (string) array_key_first($value);
823  }
824 
825  return (string) $value;
826  }
827 
828  return null;
829  }
830  )
831  );
832  }
833 
834  return null;
835  }
836 
849  private function getTargetUrl(
850  $a_class,
851  ?string $a_cmd = null,
852  ?string $a_anchor = null,
853  bool $is_async = false,
854  bool $is_escaped = false,
855  bool $is_post = false
856  ): ?string {
857  if (empty($a_class)) {
858  throw new ilCtrlException(__METHOD__ . " was provided with an empty class or class-array.");
859  }
860 
861  $is_array = is_array($a_class);
862 
863  $path = $this->path_factory->find($this->context, $a_class);
864  if (null !== ($exception = $path->getException())) {
865  throw $exception;
866  }
867 
868  $base_class = $path->getBaseClass();
869  if (null === $base_class) {
870  throw new ilCtrlException("Cannot find a valid baseclass in the cid path '{$path->getCidPath()}'");
871  }
872 
873  $target_url = $this->context->getTargetScript();
874  $target_url = $this->appendParameterString(
875  $target_url,
876  self::PARAM_BASE_CLASS,
877  urlencode($base_class), // encode in case of namespaced classes
878  $is_escaped
879  );
880 
881  $cmd_class = ($is_array) ?
882  $a_class[array_key_last($a_class)] :
883  $a_class;
884 
885  // only append the cid path and command class params
886  // if they exist.
887  if (null !== $path->getNextCid($base_class)) {
888  $target_url = $this->appendParameterString(
889  $target_url,
890  self::PARAM_CID_PATH,
891  $path->getCidPath(),
892  $is_escaped
893  );
894 
895  $target_url = $this->appendParameterString(
896  $target_url,
897  self::PARAM_CMD_CLASS,
898  urlencode($cmd_class), // encode in case of namespaced classes
899  $is_escaped
900  );
901  }
902 
903  // if the target url is generated for form actions,
904  // the command must be set to 'post'.
905  if ($is_post) {
906  $target_url = $this->appendParameterString(
907  $target_url,
908  self::PARAM_CMD,
909  self::CMD_POST,
910  $is_escaped
911  );
912  }
913 
914  // the actual command is appended as fallback command
915  // for form actions and 'normal' get requests.
916  if (!empty($a_cmd)) {
917  $target_url = $this->appendParameterString(
918  $target_url,
919  ($is_post) ? self::PARAM_CMD_FALLBACK : self::PARAM_CMD,
920  $a_cmd,
921  $is_escaped
922  );
923  }
924 
925  // collect all parameters of classes within the current
926  // targets path and append them to the target url.
927  foreach ($path->getCidArray(SORT_ASC) as $cid) {
928  $class_name = $this->structure->getClassNameByCid($cid);
929  if (null === $class_name) {
930  throw new ilCtrlException("Classname for cid '$cid' in current path cannot be found.");
931  }
932 
933  $target_url = $this->appendParameterStringsByClass(
934  $class_name,
935  $target_url,
936  $is_escaped
937  );
938  }
939 
940  // append a csrf token if the command is considered
941  // unsafe or the link is for form actions.
942  if (!$this->isCmdSecure($is_post, $cmd_class, $a_cmd)) {
943  $token = $this->token_repository->getToken();
944  $target_url = $this->appendParameterString(
945  $target_url,
946  self::PARAM_CSRF_TOKEN,
947  $token->getToken(),
948  $is_escaped
949  );
950  }
951 
952  if ($is_async) {
953  $target_url = $this->appendParameterString(
954  $target_url,
955  self::PARAM_CMD_MODE,
956  self::CMD_MODE_ASYNC,
957  $is_escaped
958  );
959  }
960 
961  if (!empty($a_anchor)) {
962  $target_url .= "#$a_anchor";
963  }
964 
965  return $target_url;
966  }
967 
974  private function modifyUrlWithPluginHooks(string $target_url): string
975  {
976  $ui_plugins = $this->component_factory->getActivePluginsInSlot("uihk");
977  foreach ($ui_plugins as $plugin_instance) {
980  $html = $plugin_instance
981  ->getUIClassInstance()
982  ->getHTML(
983  'components/ILIAS/Utilities',
984  'redirect',
985  ["html" => $target_url]
986  );
987 
988  if (ilUIHookPluginGUI::KEEP !== $html['mode']) {
989  $target_url = $plugin_instance
990  ->getUIClassInstance()
991  ->modifyHTML(
992  $target_url,
993  $html
994  );
995  }
996  }
997 
998  return $target_url;
999  }
1000 
1008  private function isCmdSecure(bool $is_post, string $cmd_class, ?string $cmd = null): bool
1009  {
1010  // if no command is specified, the command is
1011  // considered safe if it's not a POST command.
1012  if (null === $cmd) {
1013  return !$is_post;
1014  }
1015 
1016  // if the given command class doesn't exist, the
1017  // command is not considered safe as it might've been
1018  // tampered with.
1019  $obj_name = $this->structure->getObjNameByName($cmd_class);
1020  if (null === $obj_name) {
1021  return false;
1022  }
1023 
1024  // if the command class does not yet implement the
1025  // ilCtrlSecurityInterface, the command is considered
1026  // safe if it's not a POST command.
1027  if (!is_a($obj_name, ilCtrlSecurityInterface::class, true)) {
1028  return !$is_post;
1029  }
1030 
1031  // the post command is considered safe if it's contained
1032  // in the list of safe post commands.
1033  if ($is_post) {
1034  return in_array($cmd, $this->structure->getSafeCommandsByName($cmd_class), true);
1035  }
1036 
1037  // the get command is considered safe if it's not
1038  // contained in the list of unsafe get commands.
1039  return !in_array($cmd, $this->structure->getUnsafeCommandsByName($cmd_class), true);
1040  }
1041 
1051  string $class_name,
1052  string $target_url,
1053  bool $is_escaped = false
1054  ): string {
1055  $class_parameters = $this->getParameterArrayByClass($class_name);
1056  if (!empty($class_parameters)) {
1057  foreach ($class_parameters as $key => $value) {
1058  $target_url = $this->appendParameterString(
1059  $target_url,
1060  $key,
1061  $value,
1062  $is_escaped
1063  );
1064  }
1065  }
1066 
1067  return $target_url;
1068  }
1069 
1078  private function appendParameterString(
1079  string $url,
1080  string $parameter_name,
1081  $value,
1082  bool $is_escaped = false
1083  ): string {
1084  // transform value into a string, since null will fail we can
1085  // (temporarily) use the null coalescing operator.
1086  $value = $this->refinery->kindlyTo()->string()->transform($value ?? '');
1087 
1088  if ('' === $value) {
1089  return $url;
1090  }
1091 
1093  $parsed_url = parse_url(str_replace('&amp;', '&', $url));
1094 
1095  $query_parameters = $this->query_parser->parseQueriesOfURL($parsed_url['query'] ?? '');
1096 
1097  // update the given parameter or add it to the list.
1098  $query_parameters[$parameter_name] = $value;
1099 
1100  $new_url = $parsed_url['path'] ?? $this->context->getTargetScript();
1101  // we currently only escape ampersands (don't blame me).
1102  $ampersand = ($is_escaped) ? '&amp;' : '&';
1103 
1104  foreach ($query_parameters as $parameter => $parameter_value) {
1105  $new_url .= (strpos($new_url, '?') !== false) ?
1106  $ampersand . "$parameter=$parameter_value" :
1107  "?$parameter=$parameter_value";
1108  }
1109 
1110  return $new_url;
1111  }
1112 
1118  private function populateCall(string $class_name, string $cmd_mode): void
1119  {
1120  $obj_name = $this->structure->getObjNameByName($class_name);
1121 
1122  $this->stacktrace[] = [
1123  self::PARAM_CMD_CLASS => $obj_name,
1124  self::PARAM_CMD_MODE => $cmd_mode,
1125  self::PARAM_CMD => $this->getDeterminedCommand(),
1126  ];
1127  }
1128 
1135  private function getClassByObject($object): string
1136  {
1137  return (is_object($object)) ? get_class($object) : $object;
1138  }
1139 }
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:68
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)
exit
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
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