DefaultRequest.php
1 <?php
2 /**
3  * wCMF - wemove Content Management Framework
4  * Copyright (C) 2005-2015 wemove digital solutions GmbH
5  *
6  * Licensed under the terms of the MIT License.
7  *
8  * See the LICENSE file distributed with this work for
9  * additional information.
10  */
12 
21 
22 /**
23  * Default Request implementation.
24  *
25  * @author ingo herwig <ingo@wemove.com>
26  */
28 
29  /**
30  * The format of the response (used for de-, serialization).
31  */
32  private $_responseFormat = null;
33 
34  /**
35  * The HTTP method of the request
36  */
37  private $_method = null;
38 
39  private static $_logger = null;
40 
41  /**
42  * Constructor
43  * @param $formatter
44  */
45  public function __construct(Formatter $formatter) {
46  parent::__construct($formatter);
47  if (self::$_logger == null) {
48  self::$_logger = LogManager::getLogger(__CLASS__);
49  }
50  // add header values to request
51  foreach (self::getAllHeaders() as $name => $value) {
52  $this->setHeader($name, $value);
53  }
54  // fix get request parameters
55  if (isset($_SERVER['QUERY_STRING'])) {
56  self::fix($_GET, $_SERVER['QUERY_STRING']);
57  }
58  $this->_method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : '';
59  }
60 
61  /**
62  * @see Request::initialize()
63  *
64  * The method tries to match the current request path against the routes
65  * defined in the configuration section 'routes' and constructs the request based on
66  * these parameters. It then adds all data contained in $_GET, $_POST, $_FILES and
67  * php://input (raw data from the request body).
68  *
69  * Examples for route definitions are:
70  * @code
71  * GET/ = action=cms
72  * GET,POST,PUT,DELETE/rest/{language}/{className} = action=restAction&collection=1
73  * GET,POST,PUT,DELETE/rest/{language}/{className}/{id|[0-9]+} = action=restAction&collection=0
74  * @endcode
75  */
76  public function initialize($controller=null, $context=null, $action=null) {
77  // get base request data from request path
78  $basePath = preg_replace('/\/?[^\/]*$/', '', $_SERVER['SCRIPT_NAME']);
79  $requestUri = preg_replace('/\?.*$/', '', $_SERVER['REQUEST_URI']);
80  $requestPath = preg_replace('/^'.StringUtil::escapeForRegex($basePath).'/', '', $requestUri);
81  if (self::$_logger->isDebugEnabled()) {
82  self::$_logger->debug("Request path: ".$requestPath);
83  }
84  $config = ObjectFactory::getInstance('configuration');
85 
86  $baseRequestValues = array();
87  $defaultValuePattern = '([^/]+)';
88  $method = $this->getMethod();
89  if ($config->hasSection('routes')) {
90  $routes = $config->getSection('routes');
91  foreach ($routes as $route => $requestDef) {
92  // extract allowed http methods
93  $allowedMethods = null;
94  if (strpos($route, '/') !== 0) {
95  list($methodStr, $route) = explode('/', $route, 2);
96  $allowedMethods = preg_split('/\s*,\s*/', trim($methodStr));
97  $route = '/'.trim($route);
98  }
99 
100  // prepare route match pattern and extract parameters
101  $params = array();
102  $route = preg_replace_callback('/\{([^\}]+)\}/', function ($match) use($defaultValuePattern, &$params) {
103  // a variabel may be either defined by {name} or by {name|pattern} where
104  // name is the variable's name and pattern is an optional regex pattern, the
105  // values should match
106  $paramParts = explode('|', $match[1]);
107  // add the variable name to the parameter list
108  $params[] = $paramParts[0];
109  // return the value match pattern (defaults to defaultValuePattern)
110  return sizeof($paramParts) > 1 ? '('.$paramParts[1].')' : $defaultValuePattern;
111  }, $route);
112  $route = '/^'.str_replace('/', '\/', $route).'\/?$/';
113 
114  // try to match the currrent request path
115  if (self::$_logger->isDebugEnabled()) {
116  self::$_logger->debug("Check path: ".$route);
117  }
118  $matches = array();
119  if (preg_match($route, $requestPath, $matches)) {
120  if (self::$_logger->isDebugEnabled()) {
121  self::$_logger->debug("Match");
122  }
123  // set parameters from request definition
124  parse_str($requestDef, $baseRequestValues);
125  // set parameters from request path
126  for ($i=0, $count=sizeof($params); $i<$count; $i++) {
127  $baseRequestValues[$params[$i]] = isset($matches[$i+1]) ? $matches[$i+1] : null;
128  }
129  break;
130  }
131  }
132  }
133 
134  // check if method is allowed
135  if ($allowedMethods != null && !in_array($method, $allowedMethods)) {
136  throw new ApplicationException($this, null,
137  ApplicationError::getGeneral('The request method does not match the allowed methods'));
138  }
139 
140  // get additional request data from parameters
141  $requestValues = array_merge($_GET, $_POST, $_FILES);
142  $rawPostBody = file_get_contents('php://input');
143  // add the raw post data if they are json encoded
144  $json = json_decode($rawPostBody, true);
145  if (is_array($json)) {
146  foreach ($json as $key => $value) {
147  $requestValues[$key] = $value;
148  }
149  }
150 
151  // get controller/context/action triple
152  $controller = isset($requestValues['controller']) ?
153  filter_var($requestValues['controller'], FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW) :
154  (isset($baseRequestValues['controller']) ? $baseRequestValues['controller'] : $controller);
155 
156  $context = isset($requestValues['context']) ?
157  filter_var($requestValues['context'], FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW) :
158  (isset($baseRequestValues['context']) ? $baseRequestValues['context'] : $context);
159 
160  $action = isset($requestValues['action']) ?
161  filter_var($requestValues['action'], FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW) :
162  (isset($baseRequestValues['action']) ? $baseRequestValues['action'] : $action);
163 
164  // setup request
165  $this->setSender($controller);
166  $this->setContext($context);
167  $this->setAction($action);
168  $this->setValues(array_merge($baseRequestValues, $requestValues));
169  }
170 
171  /**
172  * @see Request::getMethod()
173  */
174  public function getMethod() {
175  return $this->_method;
176  }
177 
178  /**
179  * @see Request::setResponseFormat()
180  */
181  public function setResponseFormat($format) {
182  $this->_responseFormat = $format;
183  }
184 
185  /**
186  * @see Request::getResponseFormat()
187  */
188  public function getResponseFormat() {
189  if ($this->_responseFormat == null) {
190  $this->_responseFormat = $this->getFormatter()->getFormatFromMimeType($this->getHeader('Accept'));
191  }
192  return $this->_responseFormat;
193  }
194 
195  /**
196  * Get a string representation of the message
197  * @return The string
198  */
199  public function __toString() {
200  $str = 'method='.$this->_method.', ';
201  $str .= 'responseformat='.$this->_responseFormat.', ';
202  $str .= parent::__toString();
203  return $str;
204  }
205 
206  /**
207  * Get all http headers
208  * @return Associative array
209  */
210  private static function getAllHeaders() {
211  $headers = array();
212  foreach ($_SERVER as $name => $value) {
213  if (substr($name, 0, 5) == 'HTTP_') {
214  $name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))));
215  $headers[$name] = $value;
216  }
217  else if ($name == "CONTENT_TYPE") {
218  $headers["Content-Type"] = $value;
219  }
220  else if ($name == "CONTENT_LENGTH") {
221  $headers["Content-Length"] = $value;
222  }
223  }
224  return $headers;
225  }
226 
227  /**
228  * Fix request parameters (e.g. PHP replaces dots by underscore)
229  * Code from http://stackoverflow.com/questions/68651/get-php-to-stop-replacing-characters-in-get-or-post-arrays/18163411#18163411
230  * @param $target
231  * @param $source
232  * @param $keep
233  */
234  private static function fix(&$target, $source, $keep=false) {
235  if (!$source) {
236  return;
237  }
238  $keys = array();
239 
240  $source = preg_replace_callback(
241  '/
242  # Match at start of string or &
243  (?:^|(?<=&))
244  # Exclude cases where the period is in brackets, e.g. foo[bar.blarg]
245  [^=&\[]*
246  # Affected cases: periods and spaces
247  (?:\.|%20)
248  # Keep matching until assignment, next variable, end of string or
249  # start of an array
250  [^=&\[]*
251  /x',
252  function ($key) use (&$keys) {
253  $keys[] = $key = base64_encode(urldecode($key[0]));
254  return urlencode($key);
255  },
256  $source
257  );
258 
259  if (!$keep) {
260  $target = array();
261  }
262 
263  parse_str($source, $data);
264  foreach ($data as $key => $val) {
265  // Only unprocess encoded keys
266  if (!in_array($key, $keys)) {
267  $target[$key] = $val;
268  continue;
269  }
270 
271  $key = base64_decode($key);
272  $target[$key] = $val;
273 
274  if ($keep) {
275  // Keep a copy in the underscore key version
276  $key = preg_replace('/(\.| )/', '_', $key);
277  $target[$key] = $val;
278  }
279  }
280  }
281 }
282 ?>
Default Request implementation.
static getGeneral($message)
Factory method for creating a general error instance.
static getLogger($name)
Get the logger with the given name.
Definition: LogManager.php:35
static getInstance($name, $dynamicConfiguration=array())
Formatter is the single entry point for request/response formatting.
Definition: Formatter.php:23
static escapeForRegex($string)
Escape characters of a string for use in a regular expression Code from http://php.net/manual/de/function.preg-replace.php.
Definition: StringUtil.php:282
Request holds the request values that are used as input to Controller instances.
Definition: Request.php:20
AbstractControllerMessage is the base class for request/response implementations. ...
__toString()
Get a string representation of the message.
__construct(Formatter $formatter)
Constructor.
ApplicationException signals a general application exception.
initialize($controller=null, $context=null, $action=null)