2 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4 // +----------------------------------------------------------------------+
5 // | Akelos Framework - http://www.akelos.org |
6 // +----------------------------------------------------------------------+
7 // | Copyright (c) 2002-2006, Akelos Media, S.L. & Bermi Ferrer Martinez |
8 // | Released under the GNU Lesser General Public License, see LICENSE.txt|
9 // +----------------------------------------------------------------------+
12 * @package ActionController
14 * @author Bermi Ferrer <bermi a.t akelos c.om>
15 * @copyright Copyright (c) 2002-2006, Akelos Media, S.L. http://www.akelos.org
16 * @license GNU Lesser General Public License <http://www.gnu.org/copyleft/lesser.html>
19 if(!defined('AK_DEFAULT_CONTROLLER')){
20 define('AK_DEFAULT_CONTROLLER', 'page');
22 if(!defined('AK_DEFAULT_ACTION')){
23 define('AK_DEFAULT_ACTION', 'index');
26 defined('AK_HIGH_LOAD_MODE') ?
null : define('AK_HIGH_LOAD_MODE', false);
27 defined('AK_AUTOMATIC_DB_CONNECTION') ?
null : define('AK_AUTOMATIC_DB_CONNECTION', !AK_HIGH_LOAD_MODE
);
28 defined('AK_AUTOMATIC_SESSION_START') ?
null : define('AK_AUTOMATIC_SESSION_START', !AK_HIGH_LOAD_MODE
);
30 // IIS does not provide a valid REQUEST_URI so we need to guess it from the script name + query string
31 $_SERVER['REQUEST_URI'] = (isset($_SERVER['REQUEST_URI']) ?
$_SERVER['REQUEST_URI'] : $_SERVER['SCRIPT_NAME'].(( isset($_SERVER['QUERY_STRING']) ?
'?' . $_SERVER['QUERY_STRING'] : '')));
35 * Class that handles incoming request.
37 * The Request Object handles user request (CLI, GET, POST, session or
38 * cookie requests), transforms it and sets it up for the
39 * ApplicationController class, who takes control of the data
42 * @author Bermi Ferrer <bermi@akelos.com>
43 * @copyright Copyright (c) 2002-2005, Akelos Media, S.L. http://www.akelos.org
44 * @license GNU Lesser General Public License <http://www.gnu.org/copyleft/lesser.html>
46 class AkRequest
extends AkObject
50 * Array containing the request parameters.
52 * This property stores the parameters parsed from the
53 * parseRequest() method. This array is used by addParams()
57 * @var array $_request
59 var $_request = array();
61 var $_init_check = false;
62 var $__internationalization_support_enabled = false;
64 var $action = AK_DEFAULT_ACTION
;
65 var $controller = AK_DEFAULT_CONTROLLER
;
69 * Holds information about current environment. Initially a reference to $_SERVER
77 * String parse method.
79 * This method gets a petition as parameter, using the "Ruby
80 * on Rails" request format (see prettyURL in RoR documentation). The format is:
81 * file.php?ak=/controller/action/id¶mN=valueN
83 * This method requires for a previous execution of the _mergeRequest() method,
84 * in order to merge all the request all i one array.
86 * This method expands dynamically the class Request, adding a public property for
87 * every parameter sent in the request.
93 function _parseAkRequestString($ak_request_string, $pattern = '/')
96 $ak_request = trim($ak_request_string,$pattern);
97 if(strstr($ak_request,$pattern)){
98 $result = explode($pattern,$ak_request);
104 function __construct ()
112 * Initialization method.
114 * Initialization method. Use this via the class constructor.
122 if(!$this->_init_check
){
123 $this->env
=& $_SERVER;
124 $this->_fixGpcMagic();
127 $this->_mergeRequest();
129 if(is_array($this->_request
)){
130 foreach ($this->_request
as $k=>$v){
131 $this->_addParam($k, $v);
135 $this->_init_check
= true;
138 if(defined('AK_LOG_EVENTS') && AK_LOG_EVENTS
){
139 $this->Logger
=& Ak
::getLogger();
140 $this->Logger
->message($this->Logger
->formatText('Request','green').' from '.$this->getRemoteIp(), $this->getParams());
144 function get($var_name)
146 return $this->_request
[$var_name];
151 return array_merge(array('controller'=>$this->controller
,'action'=>$this->action
),$this->_request
);
156 return $this->action
;
159 function getController()
161 return $this->controller
;
166 $this->_request
= array();
167 $this->_init_check
= false;
170 function set($variable, $value)
172 $this->_addParam($variable, $value);
176 function checkForRoutedRequests(&$Router)
178 $ak_request = isset($this->_request
['ak']) ?
'/'.trim($this->_request
['ak'],'/').'/' : '/';
180 if($found = $Router->toParams($ak_request)){
181 if(!isset($found['controller'])){
182 trigger_error(Ak
::t('No controller was specified.'), E_USER_WARNING
);
184 if(!isset($found['action'])){
185 trigger_error(Ak
::t('No action was specified.'), E_USER_WARNING
);
188 if(isset($found['controller'])){
189 if($this->_addParam('controller',$found['controller'])){
190 $this->controller
= $this->_request
['controller'] = $found['controller'];
193 if(isset($found['action'])){
194 if($this->_addParam('action',$found['action'])){
195 $this->action
= $this->_request
['action'] = $found['action'];
198 if(isset($found['module'])){
199 if($this->_addParam('module',$found['module'])){
200 $this->module
= $this->_request
['module'] = $found['module'];
204 foreach ($found as $k=>$v){
205 if($this->_addParam($k,$v)){
206 $this->_request
[$k] = $v;
213 function isValidControllerName($controller_name)
215 return $this->_validateTechName($controller_name);
218 function isValidActionName($action_name)
220 return $this->_validateTechName($action_name);
223 function isValidModuleName($module_name)
225 return preg_match('/^[A-Za-z]{1,}[A-Za-z0-9_\/]*$/', $module_name);
231 * Returns both GET and POST parameters in a single array.
233 function getParameters()
235 if(empty($this->parameters
)){
236 $this->parameters
= $this->getParams();
238 return $this->parameters
;
241 function setPathParameters($parameters)
243 $this->_path_parameters
= $parameters;
246 function getPathParameters()
248 return empty($this->_path_parameters
) ?
array() : $this->_path_parameters
;
251 function getUrlParams()
257 * Must be implemented in the concrete request
259 function getQueryParameters ()
262 function getRequestParameters ()
267 * Returns the path minus the web server relative installation directory. This method returns null unless the web server is apache.
269 function getRelativeUrlRoot()
271 return str_replace('/index.php','', @$this->env
['PHP_SELF']);
275 * Returns the locale identifier of current URL
277 function getLocaleFromUrl()
279 $locale = Ak
::get_url_locale();
280 if(strstr(AK_CURRENT_URL
,AK_SITE_URL
.'/'.$locale)){
287 * Returns the HTTP request method as a lowercase symbol ('get, for example)
291 return strtolower($this->env
['REQUEST_METHOD']);
295 * Is this a GET request? Equivalent to $Request->getMethod() == 'get'
299 return $this->getMethod() == 'get';
303 * Is this a POST request? Equivalent to $Request->getMethod() == 'post'
307 return $this->getMethod() == 'post';
311 * Is this a PUT request? Equivalent to $Request->getMethod() == 'put'
315 return isset($this->env
['REQUEST_METHOD']) ?
$this->getMethod() == 'put' : false;
319 * Is this a DELETE request? Equivalent to $Request->getMethod() == 'delete'
323 return $this->getMethod() == 'delete';
327 * Is this a HEAD request? Equivalent to $Request->getMethod() == 'head'
331 return $this->getMethod() == 'head';
337 * Determine originating IP address. REMOTE_ADDR is the standard
338 * but will fail if( the user is behind a proxy. HTTP_CLIENT_IP and/or
339 * HTTP_X_FORWARDED_FOR are set by proxies so check for these before
340 * falling back to REMOTE_ADDR. HTTP_X_FORWARDED_FOR may be a comma-
341 * delimited list in the case of multiple chained proxies; the first is
342 * the originating IP.
344 function getRemoteIp()
346 if(!empty($this->env
['HTTP_CLIENT_IP'])){
347 return $this->env
['HTTP_CLIENT_IP'];
349 if(!empty($this->env
['HTTP_X_FORWARDED_FOR'])){
350 foreach ((strstr($this->env
['HTTP_X_FORWARDED_FOR'],',') ?
split(',',$this->env
['HTTP_X_FORWARDED_FOR']) : array($this->env
['HTTP_X_FORWARDED_FOR'])) as $remote_ip){
351 if($remote_ip == 'unknown' ||
352 preg_match('/^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/', $remote_ip) ||
353 preg_match('/^([0-9a-fA-F]{4}|0)(\:([0-9a-fA-F]{4}|0)){7}$/', $remote_ip)
359 return empty($this->env
['REMOTE_ADDR']) ?
'' : $this->env
['REMOTE_ADDR'];
364 * Returns the domain part of a host, such as akelos.com in 'www.akelos.com'. You can specify
365 * a different <tt>tld_length</tt>, such as 2 to catch akelos.co.uk in 'www.akelos.co.uk'.
367 function getDomain($tld_length = 1)
369 return preg_match('/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/',$this->getHost()) ?
371 join('.',array_slice(explode('.',$this->getHost()),(1 +
$tld_length)*-1));
375 * Returns all the subdomains as an array, so ['dev', 'www'] would be returned for 'dev.www.akelos.com'.
376 * You can specify a different <tt>tld_length</tt>, such as 2 to catch ['www'] instead of ['www', 'akelos']
377 * in 'www.akelos.co.uk'.
379 function getSubdomains($tld_length = 1)
381 return preg_match('/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/',$this->getHost()) ||
382 !strstr($this->getHost(),'.') ?
array() : (array)array_slice(explode('.',$this->getHost()),0,(1 +
$tld_length)*-1);
387 * Returns the request URI correctly
389 function getRequestUri()
391 return $this->getProtocol().$this->getHostWithPort();
395 * Return 'https://' if( this is an SSL request and 'http://' otherwise.
397 function getProtocol()
399 return $this->isSsl() ?
'https://' : 'http://';
403 * Is this an SSL request?
407 return isset($this->env
['HTTPS']) && ($this->env
['HTTPS'] === true ||
$this->env
['HTTPS'] == 'on');
411 * Returns the interpreted path to requested resource
415 return strstr($this->env
['REQUEST_URI'],'?') ?
substr($this->env
['REQUEST_URI'],0,strpos($this->env
['REQUEST_URI'],'?')) : $this->env
['REQUEST_URI'];
419 * Returns the port number of this request as an integer.
423 $this->port_as_int
= AK_WEB_REQUEST ? AK_SERVER_PORT
: 80;
424 return $this->port_as_int
;
428 * Returns the standard port number for this request's protocol
430 function getStandardPort()
432 return $this->isSsl() ?
443 : 80;
436 * Returns a port suffix like ':8080' if( the port number of this request
437 * is not the default HTTP port 80 or HTTPS port 443.
439 function getPortString()
441 $port = $this->getPort();
442 return $port == $this->getStandardPort() ?
'' : ($port ?
':'.$this->getPort() : '');
446 * Returns a host:port string for this request, such as example.com or
449 function getHostWithPort()
451 return $this->getHost() . $this->getPortString();
457 if(!empty($this->_host
)){
460 return AK_WEB_REQUEST ?
$this->env
['SERVER_NAME'] : 'localhost';
463 function &getSession()
468 function resetSession()
473 function &getCookies()
485 function getServerSoftware()
487 if(!empty($this->env
['SERVER_SOFTWARE'])){
488 if(preg_match('/^([a-zA-Z]+)/', $this->env
['SERVER_SOFTWARE'],$match)){
489 return strtolower($match[0]);
497 * Returns true if the request's 'X-Requested-With' header contains
498 * 'XMLHttpRequest'. (The Prototype Javascript library sends this header with
499 * every Ajax request.)
501 function isXmlHttpRequest()
503 return !empty($this->env
['HTTP_X_REQUESTED_WITH']) && strstr(strtolower($this->env
['HTTP_X_REQUESTED_WITH']),'xmlhttprequest');
507 return $this->isXmlHttpRequest();
512 return $this->isXmlHttpRequest();
517 * Receive the raw post data.
518 * This is useful for services such as REST, XMLRPC and SOAP
519 * which communicate over HTTP POST but don't use the traditional parameter format.
521 function getRawPost()
523 return empty($_ENV['RAW_POST_DATA']) ?
'' : $_ENV['RAW_POST_DATA'];
527 function _validateTechName($name)
529 return preg_match('/^[A-Za-z]{1,}[A-Za-z0-9_]*$/',$name);
534 // {{{ _mergeRequest()
537 * Populates $this->_request attribute with incoming request in the following precedence:
539 * $_SESSION['request'] <- This will override options provided by previous methods
543 * Command line params
546 * @return void Void returned. Modifies the private property "
548 function _mergeRequest()
550 $this->_request
= array();
552 $session_params = isset($_SESSION['request']) ?
$_SESSION['request'] : null;
553 $command_line_params = !empty($_REQUEST) ?
$_REQUEST : null;
555 $requests = array($command_line_params, $_GET, array_merge_recursive($_POST, $this->getPutParams(), $this->_getNormalizedFilesArray()), $_COOKIE, $session_params);
557 foreach ($requests as $request){
558 $this->_request
= (!is_null($request) && is_array($request)) ?
559 array_merge($this->_request
,$request) : $this->_request
;
565 function _getNormalizedFilesArray($params = null, $first_call = true)
567 $params = $first_call ?
$_FILES : $params;
570 $params = array_diff($params,array(''));
571 if(!empty($params) && is_array($params)){
572 foreach ($params as $name=>$details){
574 if(is_array($details) && !empty($details['name']) && !empty($details['tmp_name']) && !empty($details['size'])){
575 if(is_array($details['tmp_name'])){
576 foreach ($details['tmp_name'] as $item=>$item_details){
577 if(is_array($item_details)){
578 foreach (array_keys($item_details) as $k){
579 if(UPLOAD_ERR_NO_FILE
!= $details['error'][$item][$k]){
580 $result[$name][$item][$k] = array(
581 'name'=>$details['name'][$item][$k],
582 'tmp_name'=>$details['tmp_name'][$item][$k],
583 'size'=>$details['size'][$item][$k],
584 'type'=>$details['type'][$item][$k],
585 'error'=>$details['error'][$item][$k],
590 if(UPLOAD_ERR_NO_FILE
!= $details['error'][$item]){
591 $result[$name][$item] = array(
592 'name'=>$details['name'][$item],
593 'tmp_name'=>$details['tmp_name'][$item],
594 'size'=>$details['size'][$item],
595 'type'=>$details['type'][$item],
596 'error'=>$details['error'][$item],
601 }elseif ($first_call){
602 $result[$name] = $details;
604 $result[$name][] = $details;
606 }elseif(is_array($details)){
607 $_nested = $this->_getNormalizedFilesArray($details, false);
609 if(!empty($_nested)){
610 $result = array_merge(array($name=>$_nested), $result);
622 * Builds (i.e., "expands") the Request class for accessing
623 * the request parameters as public properties.
624 * For example, when the requests is "ak=/controller/action/id¶meter=value",
625 * once parsed, you can access the parameters of the request just like
628 * $value_to_get = $request->parameter
633 function _addParam($variable, $value)
635 if($variable[0] != '_'){
636 if( ( $variable == 'action' && !$this->isValidActionName($value)) ||
637 ( $variable == 'controller' && !$this->isValidControllerName($value)) ||
638 ( $variable == 'module' && !$this->isValidModuleName($value))
642 $this->$variable = $value;
652 * Correct double-escaping problems caused by "magic quotes" in some PHP
655 function _fixGpcMagic()
657 if(!defined('AK_GPC_MAGIC_FIXED')){
658 if (get_magic_quotes_gpc()) {
659 array_walk($_GET, array('AkRequest', '_fixGpc'));
660 array_walk($_POST, array('AkRequest', '_fixGpc'));
661 array_walk($_COOKIE, array('AkRequest', '_fixGpc'));
663 define('AK_GPC_MAGIC_FIXED',true);
667 function _fixGpc(&$item)
669 if (is_array($item)) {
670 array_walk($item, array('AkRequest', '_fixGpc'));
672 $item = stripslashes($item);
677 function _urlDecode()
679 if(!defined('AK_URL_DECODED')){
680 array_walk($_GET, array('AkRequest', '_performUrlDecode'));
681 define('AK_URL_DECODED',true);
685 function _performUrlDecode(&$item)
687 if (is_array($item)) {
688 array_walk($item, array('AkRequest', '_performUrlDecode'));
690 $item = urldecode($item);
698 * Recognizes a Request and returns the responsible controller instance
700 * @return AkActionController
702 function &recognize($Map = null)
704 AK_ENVIRONMENT
!= 'setup' ?
$this->_connectToDatabase() : null;
705 $this->_startSession();
706 $this->_enableInternationalizationSupport();
707 $this->_mapRoutes($Map);
709 $params = $this->getParams();
711 $module_path = $module_class_peffix = '';
712 if(!empty($params['module'])){
713 $module_path = trim(str_replace(array('/','\\'), DS
, Ak
::sanitize_include($params['module'], 'high')), DS
).DS
;
714 $module_shared_model = AK_CONTROLLERS_DIR
.DS
.trim($module_path,DS
).'_controller.php';
715 $module_class_peffix = str_replace(' ','_',AkInflector
::titleize(str_replace(DS
,' ', trim($module_path, DS
)))).'_';
718 $controller_file_name = AkInflector
::underscore($params['controller']).'_controller.php';
719 $controller_class_name = $module_class_peffix.AkInflector
::camelize($params['controller']).'Controller';
720 $controller_path = AK_CONTROLLERS_DIR
.DS
.$module_path.$controller_file_name;
721 include_once(AK_APP_DIR
.DS
.'application_controller.php');
723 if(!empty($module_path) && file_exists($module_shared_model)){
724 include_once($module_shared_model);
727 if(!is_file($controller_path) ||
!include_once($controller_path)){
728 defined('AK_LOG_EVENTS') && AK_LOG_EVENTS
&& $this->Logger
->error('Controller '.$controller_path.' not found.');
729 if(AK_ENVIRONMENT
== 'development'){
730 trigger_error(Ak
::t('Could not find the file /app/controllers/<i>%controller_file_name</i> for '.
731 'the controller %controller_class_name',
732 array('%controller_file_name'=> $controller_file_name,
733 '%controller_class_name' => $controller_class_name)), E_USER_ERROR
);
734 }elseif(@include
(AK_PUBLIC_DIR
.DS
.'404.php')){
737 header("HTTP/1.1 404 Not Found");
738 die('404 Not found');
741 if(!class_exists($controller_class_name)){
742 defined('AK_LOG_EVENTS') && AK_LOG_EVENTS
&& $this->Logger
->error('Controller '.$controller_path.' does not implement '.$controller_class_name.' class.');
743 if(AK_ENVIRONMENT
== 'development'){
744 trigger_error(Ak
::t('Controller <i>%controller_name</i> does not exist',
745 array('%controller_name' => $controller_class_name)), E_USER_ERROR
);
746 }elseif(@include
(AK_PUBLIC_DIR
.DS
.'405.php')){
749 header("HTTP/1.1 405 Method Not Allowed");
750 die('405 Method Not Allowed');
753 $Controller =& new $controller_class_name(array('controller'=>true));
754 $Controller->_module_path
= $module_path;
755 isset($_SESSION) ?
$Controller->session
=& $_SESSION : null;
762 function _enableInternationalizationSupport()
764 if(AK_AVAILABLE_LOCALES
!= 'en'){
765 require_once(AK_LIB_DIR
.DS
.'AkLocaleManager.php');
767 $LocaleManager = new AkLocaleManager();
768 $LocaleManager->init();
769 $LocaleManager->initApplicationInternationalization($this);
770 $this->__internationalization_support_enabled
= true;
774 function _mapRoutes($Map = null)
776 require_once(AK_LIB_DIR
.DS
.'AkRouter.php');
777 if(is_file(AK_ROUTES_MAPPING_FILE
)){
781 include(AK_ROUTES_MAPPING_FILE
);
782 // Set this routes for being used via Ak::toUrl
783 Ak
::toUrl($Map,true);
784 $this->checkForRoutedRequests($Map);
789 function _connectToDatabase()
791 if(AK_AUTOMATIC_DB_CONNECTION
){
792 Ak
::db(AK_DEFAULT_DATABASE_PROFILE
);
796 function _startSession()
798 if(AK_AUTOMATIC_SESSION_START
){
799 if(!isset($_SESSION)){
800 if(AK_SESSION_HANDLER
== 1 && defined('AK_DATABASE_CONNECTION_AVAILABLE') && AK_DATABASE_CONNECTION_AVAILABLE
){
801 require_once(AK_LIB_DIR
.DS
.'AkDbSession.php');
803 $AkDbSession = new AkDbSession();
804 $AkDbSession->session_life
= AK_SESSION_EXPIRE
;
805 session_set_save_handler (
806 array(&$AkDbSession, '_open'),
807 array(&$AkDbSession, '_close'),
808 array(&$AkDbSession, '_read'),
809 array(&$AkDbSession, '_write'),
810 array(&$AkDbSession, '_destroy'),
811 array(&$AkDbSession, '_gc')
819 function getPutParams()
821 if(!isset($this->put
) && $this->isPut() && $data = $this->getPutRequestData()){
822 $this->put
= array();
823 parse_str(urldecode($data), $this->put
);
825 return isset($this->put
) ?
$this->put
: array();
828 function getPutRequestData()
830 if(!empty($_SERVER['CONTENT_LENGTH'])){
831 $putdata = fopen('php://input', 'r');
832 $result = fread($putdata, $_SERVER['CONTENT_LENGTH']);
841 function &AkRequest()
844 $AkRequest =& Ak
::singleton('AkRequest', $null);