diff --git a/app/controllers/studip_controller.php b/app/controllers/studip_controller.php index 1b351eafa1da5d340741cac9215b169958aeab2e..3acd6eed440d83f1b34e467a2be3009cc98a8f37 100644 --- a/app/controllers/studip_controller.php +++ b/app/controllers/studip_controller.php @@ -22,6 +22,7 @@ abstract class StudipController extends Trails_Controller protected $with_session = false; //do we need to have a session for this controller protected $allow_nobody = true; //should 'nobody' allowed for this controller or redirected to login? protected $_autobind = false; + protected $reflection_cache = []; /** * @return false|void @@ -165,85 +166,12 @@ abstract class StudipController extends Trails_Controller */ public function validate_args(&$args, $types = null) { - $class_infos = []; - - if ($types === null) { - $types = array_fill(0, count($args), 'option'); - } - - if ($this->has_action($this->current_action)) { - $reflection = new ReflectionMethod($this, $this->current_action . '_action'); - $parameters = $reflection->getParameters(); - foreach ($parameters as $i => $parameter) { - $class_type = $parameter->getType(); - - if (!$class_type - || !class_exists($class_type->getName()) - || !is_a($class_type->getName(), SimpleORMap::class, true)) - { - continue; - } - - $types[$i] = 'sorm'; - $class_infos[$i] = [ - 'model' => $class_type->getName(), - 'var' => $parameter->getName(), - 'optional' => $parameter->isOptional(), - ]; - - if ($parameter->isOptional() && !isset($args[$i])) { - $args[$i] = $parameter->getDefaultValue(); - } - } + if (!is_array($types)) { + $types = (array) $types; } foreach ($args as $i => &$arg) { - $type = $types[$i] ?: 'option'; - switch ($type) { - case 'int': - $arg = (int) $arg; - break; - - case 'float': - $arg = (float) strtr($arg, ',', '.'); - break; - - case 'option': - if (preg_match('/[^\\w,-]/', $arg)) { - throw new Trails_Exception(400); - } - break; - - case 'sorm': - $info = $class_infos[$i]; - - $id = null; - if ($arg != -1) { - $id = $arg; - } - if (mb_strpos($id, SimpleORMap::ID_SEPARATOR) !== false) { - $id = explode(SimpleORMap::ID_SEPARATOR, $id); - } - - $reflection = new ReflectionClass($info['model']); - - $sorm = $reflection->newInstance($id); - if (!$info['optional'] && $sorm->isNew()) { - throw new Trails_Exception( - 404, - "Parameter {$info['var']} could not be resolved with value {$arg}" - ); - } - - $arg = $sorm; - if ($this->_autobind) { - $this->{$info['var']} = $arg; - } - break; - - default: - throw new Trails_Exception(500, 'Unknown type "' . $type . '"'); - } + $arg = $this->convertArgumentFromURL($arg, $i, $types[$i] ?? $this->getTypeForActionParameter($i)); } reset($args); @@ -289,7 +217,7 @@ abstract class StudipController extends Trails_Controller // Extract fragment (if any) if (strpos($to, '#') !== false) { - list($args[0], $fragment) = explode('#', $to); + [$args[0], $fragment] = explode('#', $to); } // Extract parameters (if any) @@ -299,12 +227,10 @@ abstract class StudipController extends Trails_Controller } // Map any sorm objects to their ids - $args = array_map(function ($arg) { - if (is_object($arg) && $arg instanceof SimpleORMap) { - return $arg->isNew() ? -1 : $arg->id; - } - return $arg; - }, $args); + $args = array_map( + [$this, 'convertArgumentForURL'], + $args + ); $url = parent::url_for(...$args); @@ -810,4 +736,146 @@ abstract class StudipController extends Trails_Controller return $body_id; } + + /** + * Converts an argument passed to url_for()/link_for() for usage in an url. + * + * @param mixed $argument + * @return mixed + */ + protected function convertArgumentForURL($argument) + { + if (is_object($argument) && $argument instanceof SimpleORMap) { + return $argument->isNew() ? -1 : $argument->id; + } + return $argument; + } + + /** + * @param mixed $argument + * @param int $index + * @param string $type + * + * @return mixed + */ + protected function convertArgumentFromURL($argument, int $index, string $type) + { + if ($type === 'int') { + return (int) $argument; + } + + if ($type === 'float') { + return (float) strtr($argument, ',', '.'); + } + + if ($type === 'sorm') { + $parameter = $this->getReflectedParameter($index); + if ($parameter->isOptional() && !isset($argument)) { + return $parameter->getDefaultValue(); + } + + $sorm = $this->loadSORMParameter($parameter->getType()->getName(), $argument); + + if ($sorm->isNew() && !$parameter->isOptional()) { + throw new Trails_Exception( + 404, + "Parameter {$parameter->getName()} could not be resolved with value {$argument}" + ); + } + + if ($this->_autobind) { + $this->{$parameter->getName()} = $sorm; + } + + return $sorm; + } + + if ($type !== 'option') { + throw new Trails_Exception(500, 'Unknown type "' . $type . '"'); + } + + if (preg_match('/[^\\w,-]/', $argument)) { + throw new Trails_Exception(400); + } + + return $argument; + } + + /** + * @param int $index + * + * @return ReflectionMethod + * @throws ReflectionException + */ + protected function getTypeForActionParameter(int $index, string $action = null): string + { + $parameter = $this->getReflectedParameter($index, $action); + if (!$parameter) { + return 'option'; + } + + $type = $parameter->getType(); + if (!$type) { + return 'option'; + } + + $type_name = $type->getName(); + + if (in_array($type_name, ['int', 'float'])) { + return $type_name; + } + + if (class_exists($type_name) && is_a($type_name, SimpleORMap::class, true)) { + return 'sorm'; + } + + return 'option'; + } + + /** + * @param int $index + * @param string|null $action + * + * @return ReflectionParameter|null + * @throws ReflectionException + */ + protected function getReflectedParameter(int $index, string $action = null): ?ReflectionParameter + { + if ($action === null) { + $action = $this->current_action; + } + + if (!$this->has_action($action)) { + return null; + } + + if (!isset($this->reflection_cache[$action])) { + $reflection = new ReflectionMethod( + $this, + "{$action}_action" + ); + $this->reflection_cache[$action] = $reflection->getParameters(); + } + + return $this->reflection_cache[$action][$index] ?? null; + } + + /** + * @param string $class_name + * @param mixed $argument + * + * @return SimpleORMap + * @throws ReflectionException + */ + protected function loadSORMParameter(string $class_name, $argument): SimpleORMap + { + $id = $argument != -1 ? $argument : null; + if (mb_strpos($id, SimpleORMap::ID_SEPARATOR) !== false) { + $id = explode(SimpleORMap::ID_SEPARATOR, $id); + } + + $reflection = new ReflectionClass($class_name); + + return $reflection->newInstance($id); + } }