Beispiel #1
0
 public function __construct(array $tags = [])
 {
     $tags += [self::T_ERROR_HANDLER => function ($str, $args) {
         trigger_error(\ICanBoogie\format($str, $args));
     }];
     $this->error_handler = $tags[self::T_ERROR_HANDLER];
 }
Beispiel #2
0
 /**
  * Sets the response message.
  *
  * @param string $message
  *
  * @throws \InvalidArgumentException if the message is an array or an object that do not implement `__toString()`.
  */
 protected function set_message($message)
 {
     if (is_array($message) || is_object($message) && !method_exists($message, '__toString')) {
         throw new \InvalidArgumentException(\ICanBoogie\format('Invalid message type "{0}", should be a string or an object implementing "__toString()". Given: {1}', array(gettype($message), $message)));
     }
     $this->message = $message;
 }
Beispiel #3
0
    protected function validate(\ICanboogie\Errors $errors)
    {
        global $core;
        $request = $this->request;
        $username = $request[User::USERNAME];
        $password = $request[User::PASSWORD];
        $uid = $core->models['users']->select('uid')->where('username = ? OR email = ?', $username, $username)->rc;
        if (!$uid) {
            $errors[User::PASSWORD] = new FormattedString('Unknown username/password combination.');
            return false;
        }
        $user = $core->models['users'][$uid];
        $now = time();
        $login_unlock_time = $user->metas['login_unlock_time'];
        if ($login_unlock_time) {
            if ($login_unlock_time > $now) {
                throw new \ICanBoogie\HTTP\HTTPError(\ICanBoogie\format("The user account has been locked after multiple failed login attempts.\n\t\t\t\t\tAn e-mail has been sent to unlock the account. Login attempts are locked until %time,\n\t\t\t\t\tunless you unlock the account using the email sent.", ['%count' => $user->metas['failed_login_count'], '%time' => I18n\format_date($login_unlock_time, 'HH:mm')]), 403);
            }
            $user->metas['login_unlock_time'] = null;
        }
        if (!$user->verify_password($password)) {
            $errors[User::PASSWORD] = new FormattedString('Unknown username/password combination.');
            $user->metas['failed_login_count'] += 1;
            $user->metas['failed_login_time'] = $now;
            if ($user->metas['failed_login_count'] >= 10) {
                $token = \ICanBoogie\generate_token(40, \ICanBoogie\TOKEN_ALPHA . \ICanBoogie\TOKEN_NUMERIC);
                $user->metas['login_unlock_token'] = $token;
                $user->metas['login_unlock_time'] = $now + 3600;
                $until = I18n\format_date($now + 3600, 'HH:mm');
                $url = $core->site->url . '/api/users/unlock_login?' . http_build_query(['username' => $username, 'token' => $token, 'continue' => $request->uri]);
                $t = new Proxi(['scope' => [\ICanBoogie\normalize($user->constructor, '_'), 'connect', 'operation']]);
                $core->mail(['destination' => $user->email, 'from' => 'no-reply@' . $_SERVER['HTTP_HOST'], 'subject' => "Your account has been locked", 'body' => <<<EOT
You receive this message because your account has been locked.

After multiple failed login attempts your account has been locked until {$until}. You can use the
following link to unlock your account and try to login again:

<{$url}>

If you forgot your password, you'll be able to request a new one.

If you didn't try to login neither forgot your password, this message might be the result of an
attack attempt on the website. If you think this is the case, please contact its admin.

The remote address of the request was: {$request->ip}.
EOT
]);
                unset($errors[User::PASSWORD]);
                $errors[] = new FormattedString("Your account has been locked, a message has been sent to your e-mail address.");
            }
            return false;
        }
        if (!$user->is_admin && !$user->is_activated) {
            $errors[] = new FormattedString('User %username is not activated', ['%username' => $username]);
            return false;
        }
        $this->record = $user;
        return true;
    }
 /**
  * Adds the `status` and `notify` properties if they are not defined, they default to
  * `pending` and `no`.
  *
  * @throws \InvalidArgumentException if the value of the `notify` property is not one of `no`,
  * `yes`, `author` or `done`.
  */
 public function save(array $properties, $key = null, array $options = array())
 {
     $properties += array(Comment::CREATED => DateTime::now(), Comment::STATUS => 'pending', Comment::NOTIFY => 'no');
     if (!in_array($properties[Comment::NOTIFY], array('no', 'yes', 'author', 'done'))) {
         throw new \InvalidArgumentException(\ICanBoogie\format('Invalid value for property %property: %value', array('%property' => Comment::NOTIFY, '%value' => $properties[Comment::NOTIFY])));
     }
     return parent::save($properties, $key, $options);
 }
Beispiel #5
0
 /**
  * Format a string.
  *
  * @param string $format The format of the string.
  * @param array $args The arguments.
  * @param array $options Some options.
  *
  * @return \ICanBoogie\FormattedString|\ICanBoogie\I18n\FormattedString|string
  */
 private static function format($format, array $args = [], array $options = [])
 {
     if (class_exists(\ICanBoogie\I18n\FormattedString::class, true)) {
         return new \ICanBoogie\I18n\FormattedString($format, $args, $options);
         // @codeCoverageIgnore
     }
     if (class_exists(\ICanBoogie\FormattedString::class, true)) {
         return new \ICanBoogie\FormattedString($format, $args, $options);
     }
     return \ICanBoogie\format($format, $args);
     // @codeCoverageIgnore
 }
Beispiel #6
0
 private static function resolve_type($source)
 {
     if ($source instanceof \Icybee\Modules\Nodes\Module || $source instanceof \Icybee\Modules\Nodes\SaveOperation) {
         return 'node';
     }
     if ($source instanceof \Icybee\Modules\Users\Module || $source instanceof \Icybee\Modules\Users\SaveOperation) {
         return 'user';
     }
     if ($source instanceof \Icybee\Modules\Sites\Module || $source instanceof \Icybee\Modules\Sites\SaveOperation) {
         return 'site';
     }
     throw new \Exception(\ICanBoogie\format('Metadatas are not supported for instances of the given class: %class', ['%class' => get_class($source)]));
 }
Beispiel #7
0
 /**
  * Collects views defined by modules.
  *
  * After the views defined by modules have been collected {@link Collection\CollectEvent} is
  * fired.
  *
  * @param Core $app
  *
  * @return array[string]array
  *
  * @throws \UnexpectedValueException when the {@link ViewOptions::TITLE},
  * {@link ViewOptions::TYPE}, {@link ViewOptions::MODULE} or {@link ViewOptions::RENDERS}
  * properties are empty.
  */
 protected function collect(Core $app)
 {
     static $required = [ViewOptions::TITLE, ViewOptions::TYPE, ViewOptions::MODULE, ViewOptions::RENDERS];
     $collection = $app->configs['views'];
     new Collection\CollectEvent($this, $collection);
     foreach ($collection as $id => &$definition) {
         $definition = ViewOptions::normalize($definition);
         foreach ($required as $property) {
             if (empty($definition[$property])) {
                 throw new \UnexpectedValueException(\ICanBoogie\format('%property is empty for the view %id.', ['property' => $property, 'id' => $id]));
             }
         }
     }
     return $collection;
 }
 /**
  * Initializes the {@link $targetid} and {@link $model} properties.
  *
  * @param \ICanBoogie\ActiveRecord $target
  *
  * @throws Exception
  */
 public function __construct(\ICanBoogie\ActiveRecord $target)
 {
     if ($target instanceof \Icybee\Modules\Nodes\Node) {
         $this->targetid = $target->nid;
         $type = 'node';
     } else {
         if ($target instanceof \Icybee\Modules\Users\User) {
             $this->targetid = $target->uid;
             $type = 'user';
         } else {
             if ($target instanceof \Icybee\Modules\Sites\Site) {
                 $this->targetid = $target->siteid;
                 $type = 'site';
             } else {
                 throw new \Exception(\ICanBoogie\format('Metadatas are not supported for instances of %class', ['%class' => get_class($target)]));
             }
         }
     }
     if (empty(self::$models[$type])) {
         self::$models[$type] = \ICanBoogie\ActiveRecord\get_model('registry/' . $type);
     }
     $this->model = self::$models[$type];
 }
 protected function process()
 {
     global $core;
     $request = $this->request;
     $params =& $request->params;
     $params['src'] = null;
     // TODO-20101031: support for the 's' shorthand.
     $nid = (int) $params['nid'];
     if (function_exists('glob')) {
         $root = \ICanBoogie\DOCUMENT_ROOT;
         $files = glob(\ICanBoogie\REPOSITORY . 'files/*/' . $nid . '-*');
         if ($files) {
             $params['src'] = substr(array_shift($files), strlen($root));
         }
     } else {
         $root = \ICanBoogie\REPOSITORY . 'files/image';
         $nid .= '-';
         $nid_length = strlen($nid);
         $previous = getcwd();
         chdir($root);
         $dh = opendir($root);
         while (($file = readdir($dh)) !== false) {
             if ($file[0] == '.' || substr($file, 0, $nid_length) != $nid) {
                 continue;
             }
             $params['src'] = $path . '/' . $file;
             break;
         }
         closedir($dh);
         chdir($previous);
     }
     if (empty($params['src'])) {
         throw new \ICanBoogie\HTTP\HTTPError(\ICanBoogie\format('Unable to locate image resource for the given identifier: %nid.', array('%nid' => $nid)), 404);
     }
     return parent::process();
 }
Beispiel #10
0
 /**
  * Asserts that the specified width and height can be used with the specified resize method.
  *
  * @param string $m The resize method.
  * @param int $w The destination width.
  * @param int $h The destination height.
  *
  * @throws \InvalidArgumentException if the width or height is invalid for the resize method.
  *
  * @return boolean
  */
 public static function assert_sizes($m, $w, $h)
 {
     switch ($m) {
         case Image::RESIZE_FIXED_WIDTH:
             if (!$w) {
                 throw new \InvalidArgumentException(\ICanBoogie\format('Width is required for the %method resize method.', array('method' => $m)));
             }
             break;
         case Image::RESIZE_FIXED_HEIGHT:
             if (!$h) {
                 throw new \InvalidArgumentException(\ICanBoogie\format('Height is required for the %method resize method.', array('method' => $m)));
             }
             break;
         default:
             if (!$w || !$h) {
                 throw new \InvalidArgumentException(\ICanBoogie\format('Both width and height are required for the %method resize method.', array('method' => $m)));
             }
             break;
     }
     return true;
 }
 public function __construct($module_id, $class, $code = 500, \Exception $previous = null)
 {
     $this->module_id = $module_id;
     $this->class = $class;
     parent::__construct(\ICanBoogie\format('Missing class %class to instantiate module %id.', ['class' => $class, 'id' => $module_id]));
 }
Beispiel #12
0
 /**
  * Translate a native string in a locale string.
  *
  * @param string $native The native string to translate.
  * @param array $args
  * @param array $options
  *
  * @return string The translated string, or the same native string if no translation could be
  * found.
  */
 public function __invoke($native, array $args = array(), array $options = array())
 {
     $native = (string) $native;
     $messages = $this->messages;
     $translated = null;
     $suffix = null;
     if ($args && array_key_exists(':count', $args)) {
         $count = $args[':count'];
         if ($count == 0) {
             $suffix = '.none';
         } else {
             if ($count == 1) {
                 $suffix = '.one';
             } else {
                 $suffix = '.other';
             }
         }
     }
     $scope = I18n::get_scope();
     if (isset($options['scope'])) {
         if ($scope) {
             $scope .= '.';
         }
         $scope .= is_array($options['scope']) ? implode('.', $options['scope']) : $options['scope'];
     }
     $prefix = $scope;
     while ($scope) {
         $try = $scope . '.' . $native . $suffix;
         if (isset($messages[$try])) {
             $translated = $messages[$try];
             break;
         }
         $pos = strpos($scope, '.');
         if ($pos === false) {
             break;
         }
         $scope = substr($scope, $pos + 1);
     }
     if (!$translated) {
         if (isset($messages[$native . $suffix])) {
             $translated = $messages[$native . $suffix];
         }
     }
     if (!$translated) {
         self::$missing[] = ($prefix ? $prefix . '.' : '') . $native;
         if (!empty($options['default'])) {
             $default = $options['default'];
             if (!$default instanceof \Closure) {
                 return $default;
             }
             $native = $default($this, $native, $options, $args) ?: $native;
         }
         #
         # We couldn't find any translation for the native string provided, in order to avoid
         # another search for the same string, we store the native string as the translation in
         # the locale messages.
         #
         $this->messages[($prefix ? $prefix . '.' : '') . $native] = $native;
         $translated = $native;
     }
     if ($args) {
         $translated = \ICanBoogie\format($translated, $args);
     }
     return $translated;
 }
Beispiel #13
0
 /**
  * @param string $source
  *
  * @return object
  * @throws \Exception
  */
 protected function parse_expression($source)
 {
     $escape = true;
     if ($source[strlen($source) - 1] == '=') {
         $escape = false;
         $source = substr($source, 0, -1);
     }
     preg_match('/^(([a-z]+):)?(.+)$/', $source, $matches);
     $type = $matches[2];
     $expression = $matches[3];
     $types = ['' => 'Patron\\EvaluateNode', 't' => 'Patron\\TranslateNode', 'url' => 'Patron\\URLNode'];
     if (!isset($types[$type])) {
         throw new \Exception(\ICanBoogie\format("Unknown expression type %type for expression %expression", ['type' => $type, 'expression' => $expression]));
     }
     $class = $types[$type];
     return new $class($expression, $escape);
 }
Beispiel #14
0
 /**
  * Formats exception message.
  *
  * @param string $location
  *
  * @return string
  */
 protected function format_message($location)
 {
     return \ICanBoogie\format("Location: %location", ['location' => $location]);
 }
Beispiel #15
0
 /**
  * Asserts that a prototype method name is valid.
  *
  * @param string $method
  * @param string $pathname
  *
  * @throws \InvalidArgumentException if a method definition is missing the '::' separator.
  */
 private static function assert_valid_prototype_method_name($method, $pathname)
 {
     if (strpos($method, '::') === false) {
         throw new \InvalidArgumentException(\ICanBoogie\format('Invalid method name %method in %pathname, expected %expected.', ['method' => $method, 'pathname' => $pathname, 'expected' => "{class_name}::{method_name}"]));
     }
 }
Beispiel #16
0
 /**
  * Alters the module descriptor.
  *
  * @param array $descriptor Descriptor of the module to index.
  *
  * @return array The altered descriptor.
  */
 protected function alter_descriptor(array $descriptor)
 {
     $id = $descriptor[Descriptor::ID];
     $namespace = $descriptor[Descriptor::NS];
     # models and active records
     foreach ($descriptor[Descriptor::MODELS] as $model_id => &$definition) {
         if (!is_array($definition)) {
             throw new \InvalidArgumentException(\ICanBoogie\format('Model definition must be array, given: %value.', ['value' => $definition]));
         }
         $basename = $id;
         $separator_position = strrpos($basename, '.');
         if ($separator_position) {
             $basename = substr($basename, $separator_position + 1);
         }
         if (empty($definition[Model::NAME])) {
             $definition[Model::NAME] = self::format_model_name($id, $model_id);
         }
         if (empty($definition[Model::ACTIVERECORD_CLASS])) {
             $definition[Model::ACTIVERECORD_CLASS] = $namespace . '\\' . \ICanBoogie\camelize(\ICanBoogie\singularize($model_id == 'primary' ? $basename : $model_id));
         }
         if (empty($definition[Model::CLASSNAME])) {
             $definition[Model::CLASSNAME] = $definition[Model::ACTIVERECORD_CLASS] . 'Model';
         }
     }
     return $descriptor;
 }
 /**
  * Formats exception message.
  *
  * @param string $id
  *
  * @return string
  */
 protected function format_message($id)
 {
     return \ICanBoogie\format("Connection not defined: %id.", ['id' => $id]);
 }
Beispiel #18
0
 public function __construct($module_id, $code = Status::INTERNAL_SERVER_ERROR, \Exception $previous = null)
 {
     $this->module_id = $module_id;
     parent::__construct(\ICanBoogie\format('Module is not defined: %module_id', ['module_id' => $module_id]), $code, $previous);
 }
 /**
  * Formats exception message.
  *
  * @param int $mode
  *
  * @return string
  */
 protected function format_message($mode)
 {
     return \ICanBoogie\format("Unable to set fetch mode: %mode", ['mode' => $mode]);
 }
Beispiel #20
0
 /**
  * Resolves the name of the class that should be used to instantiate the view.
  *
  * If `module` is specified in the view definition, the name is resolved according to the
  * module's hierarchy.
  *
  * @param array $definition
  *
  * @return string The class that should be used to instantiate the view.
  */
 private function resolve_view_classname(array $definition)
 {
     $app = $this->app;
     $classname = empty($definition[ViewOptions::CLASSNAME]) ? null : $definition[ViewOptions::CLASSNAME];
     if (!empty($definition[ViewOptions::MODULE])) {
         $resolved_classname = $app->modules->resolve_classname('View', $definition[ViewOptions::MODULE]);
         if (!$classname) {
             $classname = $resolved_classname;
         } else {
             if ($classname === $resolved_classname) {
                 $app->logger->debug(\ICanBoogie\format("The view class %class can be resolved from the module, it is recommended to avoid its definition: :definition", ['class' => $classname, 'definition' => $definition]));
             }
         }
     }
     return $classname ?: 'Icybee\\Modules\\Views\\View';
 }
Beispiel #21
0
 /**
  * Operation interface to the {@link get()} method.
  *
  * The function uses the {@link get()} method to obtain the location of the image version.
  * A HTTP redirection is made to the location of the image.
  *
  * A HTTPException exception with code 404 is thrown if the function fails to obtain the
  * location of the image version.
  *
  * @throws HTTPError
  */
 protected function process()
 {
     $this->rescue_uri();
     $path = $this->get($this->request->params);
     if (!$path) {
         throw new HTTPError(\ICanBoogie\format('Unable to create thumbnail for: %src', array('%src' => $this->request['src'])), 404);
     }
     $request = $this->request;
     $response = $this->response;
     $server_location = \ICanBoogie\DOCUMENT_ROOT . $path;
     $stat = stat($server_location);
     $etag = md5($path);
     #
     # The expiration date is set to seven days.
     #
     session_cache_limiter('public');
     $response->cache_control->cacheable = 'public';
     $response->etag = $etag;
     $response->expires = '+1 week';
     $response->headers['X-Generated-By'] = 'Icybee/Thumbnailer';
     if ($request->cache_control->cacheable != 'no-cache') {
         $if_none_match = $request->headers['If-None-Match'];
         $if_modified_since = $request->headers['If-Modified-Since'];
         if ($if_modified_since && $if_modified_since->timestamp >= $stat['mtime'] && $if_none_match && trim($if_none_match) == $etag) {
             $response->status = 304;
             #
             # WARNING: do *not* send any data after that
             #
             return true;
         }
     }
     $pos = strrpos($path, '.');
     $type = substr($path, $pos + 1);
     $response->last_modified = $stat['mtime'];
     $response->content_type = "image/{$type}";
     $response->content_length = $stat['size'];
     return function () use($server_location) {
         $fh = fopen($server_location, 'rb');
         fpassthru($fh);
         fclose($fh);
     };
 }
Beispiel #22
0
 /**
  * Get a block.
  *
  * @param string $name The name of the block to get.
  *
  * @return mixed Depends on the implementation. Should return a string or an object
  * implementing `__toString`.
  *
  * @throws \RuntimeException if the block is not defined.
  */
 public function getBlock($name)
 {
     $args = func_get_args();
     array_shift($args);
     $callback = 'block_' . $name;
     if (!method_exists($this, $callback)) {
         throw new \RuntimeException(\ICanBoogie\format('The %method method is missing from the %module module to create block %type.', ['%method' => $callback, '%module' => $this->id, '%type' => $name]));
     }
     return call_user_func_array([$this, $callback], $args);
 }
 /**
  * Formats exception message.
  *
  * @param string $class
  *
  * @return string
  */
 protected function format_message($class)
 {
     return \ICanBoogie\format("ActiveRecord class is not valid: %class", ['class' => $class]);
 }
Beispiel #24
0
 /**
  * Resolves the name of the class that should be used to instantiate the view.
  *
  * If `module` is specified in the view definition, the name is resolved according to the
  * module's hierarchy.
  *
  * @return string The class that should be used to instantiate the view.
  *
  * @throws \Exception
  */
 private function resolve_provider_classname()
 {
     $options = $this->options;
     $classname = empty($options[ViewOptions::PROVIDER_CLASSNAME]) ? null : $options[ViewOptions::PROVIDER_CLASSNAME];
     if (!$classname) {
         return null;
     }
     $app = $this->app;
     if (!empty($options[ViewOptions::MODULE])) {
         $resolved_classname = $app->modules->resolve_classname('ViewProvider', $options[ViewOptions::MODULE]);
         if ($classname === ViewOptions::PROVIDER_CLASSNAME_AUTO) {
             if (!$resolved_classname) {
                 throw new \Exception(\ICanBoogie\format("Unable to resolve view provider class name for view %id.", [$this->id]));
             }
             $classname = $resolved_classname;
         } else {
             if ($classname && $classname === $resolved_classname) {
                 $app->logger->debug(\ICanBoogie\format("The provider class %class can be resolved from the module, it is recommended to PROVIDER_CLASSNAME_AUTO in the options: :options", ['class' => $classname, 'options' => $options]));
             }
         }
     }
     return $classname;
 }
Beispiel #25
0
 /**
  * Asserts that the a body is valid.
  *
  * @param $body
  *
  * @throws \UnexpectedValueException if the specified body doesn't meet the requirements.
  */
 protected function assert_body_is_valid($body)
 {
     if ($body === null || $body instanceof \Closure || is_numeric($body) || is_string($body) || is_object($body) && method_exists($body, '__toString')) {
         return;
     }
     throw new \UnexpectedValueException(\ICanBoogie\format('The Response body must be a string, an object implementing the __toString() method or be callable, %type given. !value', ['type' => gettype($body), 'value' => $body]));
 }
 /**
  * Formats exception message.
  *
  * @param string $method
  * @param string $class
  *
  * @return string
  */
 protected function format_message($method, $class)
 {
     return \ICanBoogie\format('The method %method is not defined by the prototype of class %class.', ['method' => $method, 'class' => $class]);
 }
Beispiel #27
0
 public function __invoke($template, $bind = null, array $options = [])
 {
     if (!$template) {
         return null;
     }
     if ($bind !== null) {
         $this->context['this'] = $bind;
     }
     $file = null;
     foreach ($options as $option => $value) {
         switch ((string) $option) {
             case 'variables':
                 foreach ($value as $k => $v) {
                     $this->context[$k] = $v;
                 }
                 break;
             case 'file':
                 $file = $value;
                 break;
             default:
                 trigger_error(\ICanBoogie\format('Suspicious option: %option :value', ['%option' => $option, ':value' => $value]));
                 break;
         }
     }
     if (!$template instanceof Template) {
         if (is_array($template) && isset($template['file'])) {
             $file = $template['file'];
             unset($template['file']);
         }
         if (!is_array($template)) {
             $template = $this->get_compiled($template);
         }
         $template = new Template($template, ['file' => $file]);
     }
     if ($template->file) {
         $this->trace_enter(['file', $template->file]);
     }
     $rc = '';
     foreach ($template as $node) {
         if (!$node instanceof Node) {
             continue;
         }
         try {
             $rc .= $node($this, $this->context);
         } catch (\Exception $e) {
             if (class_exists('ICanBoogie\\Debug')) {
                 $rc .= Debug::format_alert($e);
             } else {
                 $rc .= $e;
             }
         }
         $rc .= $this->fetchErrors();
     }
     $rc .= $this->fetchErrors();
     #
     #
     #
     if ($file) {
         array_shift($this->trace);
     }
     return $rc;
 }
 /**
  * Formats exception message.
  *
  * @param string $method
  * @param object $instance
  *
  * @return string
  */
 protected function format_message($method, $instance)
 {
     return \ICanBoogie\format('The method %method is out of scope for class %class.', ['method' => $method, 'class' => get_class($instance)]);
 }
Beispiel #29
0
 /**
  * Formats exception message.
  *
  * @param int $status_code
  *
  * @return string
  */
 protected function format_message($status_code)
 {
     return \ICanBoogie\format("Status code not valid: %status_code.", ['status_code' => $status_code]);
 }
Beispiel #30
0
 protected function evaluate($context, $expression, $tokens, $silent)
 {
     $expression_path = [];
     foreach ($tokens as $i => $part) {
         $identifier = $part[self::TOKEN_VALUE];
         $expression_path[] = $identifier;
         switch ($part[self::TOKEN_TYPE]) {
             case self::TOKEN_TYPE_IDENTIFIER:
                 if (!is_array($context) && !is_object($context)) {
                     throw new \InvalidArgumentException(\ICanBoogie\format('Unexpected variable type: %type (%value) for %identifier in expression %expression, should be either an array or an object', ['%type' => gettype($context), '%value' => $context, '%identifier' => $identifier, '%expression' => $expression]));
                 }
                 $exists = false;
                 $next_value = $this->extract_value($context, $identifier, $exists);
                 if (!$exists) {
                     if ($silent) {
                         return null;
                     }
                     throw new ReferenceError(\ICanBoogie\format('Reference to undefined property %path of expression %expression (defined: :keys) in: :value', ['path' => implode('.', $expression_path), 'expression' => $expression, 'keys' => implode(', ', $context instanceof Context ? $context->keys() : array_keys((array) $context)), 'value' => \ICanBoogie\dump($context)]));
                 }
                 $context = $next_value;
                 break;
             case self::TOKEN_TYPE_FUNCTION:
                 $method = $identifier;
                 $args = $part[self::TOKEN_ARGS];
                 $args_evaluate = $part[self::TOKEN_ARGS_EVALUATE];
                 if ($args_evaluate) {
                     $this->engine->error('we should evaluate %eval', ['%eval' => $args_evaluate]);
                 }
                 #
                 # if value is an object, we check if the object has the method
                 #
                 if (is_object($context) && method_exists($context, $method)) {
                     $context = call_user_func_array([$context, $method], $args);
                     break;
                 }
                 #
                 # well, the object didn't have the method,
                 # we check internal functions
                 #
                 $callback = $this->engine->functions->find($method);
                 #
                 # if no internal function matches, we try string and array functions
                 # depending on the type of the value
                 #
                 if (!$callback) {
                     if (is_string($context)) {
                         if (function_exists('str' . $method)) {
                             $callback = 'str' . $method;
                         } else {
                             if (function_exists('str_' . $method)) {
                                 $callback = 'str_' . $method;
                             }
                         }
                     } else {
                         if (is_array($context) || is_object($context)) {
                             if (function_exists('ICanBoogie\\array_' . $method)) {
                                 $callback = 'ICanBoogie\\array_' . $method;
                             } else {
                                 if (function_exists('array_' . $method)) {
                                     $callback = 'array_' . $method;
                                 }
                             }
                         }
                     }
                 }
                 #
                 # our last hope is to try the function "as is"
                 #
                 if (!$callback) {
                     if (function_exists($method)) {
                         $callback = $method;
                     }
                 }
                 if (!$callback) {
                     if (is_object($context) && method_exists($context, '__call')) {
                         $context = call_user_func_array([$context, $method], $args);
                         break;
                     }
                 }
                 #
                 #
                 #
                 if (!$callback) {
                     throw new \Exception(\ICanBoogie\format('Unknown method %method for expression %expression.', ['%method' => $method, '%expression' => $expression]));
                 }
                 #
                 # create evaluation
                 #
                 array_unshift($args, $context);
                 if (PHP_MAJOR_VERSION > 5 || PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION > 2) {
                     if ($callback == 'array_shift') {
                         $context = array_shift($context);
                     } else {
                         $context = call_user_func_array($callback, $args);
                     }
                 } else {
                     $context = call_user_func_array($callback, $args);
                 }
                 break;
         }
     }
     return $context;
 }