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 // +----------------------------------------------------------------------+
11 if(!class_exists('AkResponse')){
14 * @package ActionController
16 * @author Bermi Ferrer <bermi a.t akelos c.om>
17 * @copyright Copyright (c) 2002-2006, Akelos Media, S.L. http://www.akelos.org
18 * @license GNU Lesser General Public License <http://www.gnu.org/copyleft/lesser.html>
21 if(!defined('AK_DEFAULT_CONTROLLER')){
22 define('AK_DEFAULT_CONTROLLER', 'page');
24 if(!defined('AK_DEFAULT_ACTION')){
25 define('AK_DEFAULT_ACTION', 'index');
28 defined('AK_HIGH_LOAD_MODE') ?
null : define('AK_HIGH_LOAD_MODE', false);
29 defined('AK_AUTOMATIC_DB_CONNECTION') ?
null : define('AK_AUTOMATIC_DB_CONNECTION', !AK_HIGH_LOAD_MODE
);
30 defined('AK_AUTOMATIC_SESSION_START') ?
null : define('AK_AUTOMATIC_SESSION_START', !AK_HIGH_LOAD_MODE
);
32 // IIS does not provide a valid REQUEST_URI so we need to guess it from the script name + query string
33 $_SERVER['REQUEST_URI'] = (isset($_SERVER['REQUEST_URI']) ?
$_SERVER['REQUEST_URI'] : $_SERVER['SCRIPT_NAME'].(( isset($_SERVER['QUERY_STRING']) ?
'?' . $_SERVER['QUERY_STRING'] : '')));
37 * Class that handles incoming request.
39 * The Request Object handles user request (CLI, GET, POST, session or
40 * cookie requests), transforms it and sets it up for the
41 * ApplicationController class, who takes control of the data
44 * @author Bermi Ferrer <bermi@akelos.com>
45 * @copyright Copyright (c) 2002-2005, Akelos Media, S.L. http://www.akelos.org
46 * @license GNU Lesser General Public License <http://www.gnu.org/copyleft/lesser.html>
48 class AkRequest
extends AkObject
53 // --- Private properties --- //
56 * Array containing the request parameters.
58 * This property stores the parameters parsed from the
59 * parseRequest() method. This array is used by addParams()
63 * @var array $_request
65 var $_request = array();
67 var $_init_check = false;
68 var $__internationalization_support_enabled = false;
70 var $action = AK_DEFAULT_ACTION
;
71 var $controller = AK_DEFAULT_CONTROLLER
;
75 * Holds information about current environment. Initially a reference to $_SERVER
83 // ------ CLASS METHODS ------ //
88 // ---- Public methods ---- //
91 // {{{ _parseAkRequestString()
94 * String parse method.
96 * This method gets a petition as parameter, using the "Ruby
97 * on Rails" request format (see prettyURL in RoR documentation). The format is:
98 * file.php?ak=/controller/action/id¶mN=valueN
100 * This method requires for a previous execution of the _mergeRequest() method,
101 * in order to merge all the request all i one array.
103 * This method expands dynamically the class Request, adding a public property for
104 * every parameter sent in the request.
110 function _parseAkRequestString($ak_request_string, $pattern = '/')
113 $ak_request = trim($ak_request_string,$pattern);
114 if(strstr($ak_request,$pattern)){
115 $result = explode($pattern,$ak_request);
123 function __construct ()
129 // ---- Private methods ---- //
135 * Initialization method.
137 * Initialization method. Use this via the class constructor.
145 if(!$this->_init_check
){
147 $this->_fixGpcMagic();
150 $this->_mergeRequest();
152 if(is_array($this->_request
)){
153 foreach ($this->_request
as $k=>$v){
154 $this->_addParam($k, $v);
158 $this->_init_check
= true;
161 $this->env
=& $_SERVER;
163 if(defined('AK_LOG_EVENTS') && AK_LOG_EVENTS
){
164 $Logger =& Ak
::getLogger();
165 $Logger->message($Logger->formatText('Request','green').' from '.$this->getRemoteIp(), $this->getParams());
171 function get($var_name)
173 return $this->_request
[$var_name];
178 return array_merge(array('controller'=>$this->controller
,'action'=>$this->action
),$this->_request
);
183 return $this->action
;
186 function getController()
188 return $this->controller
;
193 $this->_request
= array();
194 $this->_init_check
= false;
197 function set($variable, $value)
199 $this->_addParam($variable, $value);
203 function checkForRoutedRequests(&$Router)
205 $ak_request = isset($this->_request
['ak']) ?
'/'.trim($this->_request
['ak'],'/').'/' : '/';
207 if($found = $Router->toParams($ak_request)){
208 if(!isset($found['controller'])){
209 trigger_error(Ak
::t('No controller was specified.'), E_USER_WARNING
);
211 if(!isset($found['action'])){
212 trigger_error(Ak
::t('No action was specified.'), E_USER_WARNING
);
215 if(isset($found['controller'])){
216 if($this->_addParam('controller',$found['controller'])){
217 $this->controller
= $this->_request
['controller'] = $found['controller'];
220 if(isset($found['action'])){
221 if($this->_addParam('action',$found['action'])){
222 $this->action
= $this->_request
['action'] = $found['action'];
225 if(isset($found['module'])){
226 if($this->_addParam('module',$found['module'])){
227 $this->module
= $this->_request
['module'] = $found['module'];
231 foreach ($found as $k=>$v){
232 if($this->_addParam($k,$v)){
233 $this->_request
[$k] = $v;
240 function isValidControllerName($controller_name)
242 return $this->_validateTechName($controller_name);
245 function isValidActionName($action_name)
247 return $this->_validateTechName($action_name);
250 function isValidModuleName($module_name)
252 return preg_match('/^[A-Za-z]{1,}[A-Za-z0-9_\/]*$/', $module_name);
258 * Returns both GET and POST parameters in a single array.
260 function getParameters()
262 if(empty($this->parameters
)){
263 $this->parameters
= $this->getParams();
265 return $this->parameters
;
268 function setPathParameters($parameters)
270 $this->_path_parameters
= $parameters;
273 function getPathParameters()
275 return empty($this->_path_parameters
) ?
array() : $this->_path_parameters
;
278 function getUrlParams()
284 * Must be implemented in the concrete request
286 function getQueryParameters ()
289 function getRequestParameters ()
294 * Returns the path minus the web server relative installation directory. This method returns null unless the web server is apache.
296 function getRelativeUrlRoot()
298 return str_replace('/index.php','', @$this->env
['PHP_SELF']);
302 * Returns the locale identifier of current URL
304 function getLocaleFromUrl()
306 $locale = Ak
::get_url_locale();
307 if(strstr(AK_CURRENT_URL
,AK_SITE_URL
.'/'.$locale)){
314 * Returns the HTTP request method as a lowercase symbol ('get, for example)
318 return strtolower($this->env
['REQUEST_METHOD']);
322 * Is this a GET request? Equivalent to $Request->getMethod() == 'get'
326 return $this->getMethod() == 'get';
330 * Is this a POST request? Equivalent to $Request->getMethod() == 'post'
334 return $this->getMethod() == 'post';
338 * Is this a PUT request? Equivalent to $Request->getMethod() == 'put'
342 return $this->getMethod() == 'put';
346 * Is this a DELETE request? Equivalent to $Request->getMethod() == 'delete'
350 return $this->getMethod() == 'delete';
354 * Is this a HEAD request? Equivalent to $Request->getMethod() == 'head'
358 return $this->getMethod() == 'head';
364 * Determine originating IP address. REMOTE_ADDR is the standard
365 * but will fail if( the user is behind a proxy. HTTP_CLIENT_IP and/or
366 * HTTP_X_FORWARDED_FOR are set by proxies so check for these before
367 * falling back to REMOTE_ADDR. HTTP_X_FORWARDED_FOR may be a comma-
368 * delimited list in the case of multiple chained proxies; the first is
369 * the originating IP.
371 function getRemoteIp()
373 if(!empty($this->env
['HTTP_CLIENT_IP'])){
374 return $this->env
['HTTP_CLIENT_IP'];
376 if(!empty($this->env
['HTTP_X_FORWARDED_FOR'])){
377 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){
378 if($remote_ip == 'unknown' ||
379 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) ||
380 preg_match('/^([0-9a-fA-F]{4}|0)(\:([0-9a-fA-F]{4}|0)){7}$/', $remote_ip)
386 return empty($this->env
['REMOTE_ADDR']) ?
'' : $this->env
['REMOTE_ADDR'];
391 * Returns the domain part of a host, such as akelos.com in 'www.akelos.com'. You can specify
392 * a different <tt>tld_length</tt>, such as 2 to catch akelos.co.uk in 'www.akelos.co.uk'.
394 function getDomain($tld_length = 1)
396 return preg_match('/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/',$this->getHost()) ?
398 join('.',array_slice(explode('.',$this->getHost()),(1 +
$tld_length)*-1));
402 * Returns all the subdomains as an array, so ['dev', 'www'] would be returned for 'dev.www.akelos.com'.
403 * You can specify a different <tt>tld_length</tt>, such as 2 to catch ['www'] instead of ['www', 'akelos']
404 * in 'www.akelos.co.uk'.
406 function getSubdomains($tld_length = 1)
408 return preg_match('/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/',$this->getHost()) ||
409 !strstr($this->getHost(),'.') ?
array() : (array)array_slice(explode('.',$this->getHost()),0,(1 +
$tld_length)*-1);
414 * Returns the request URI correctly
416 function getRequestUri()
418 return $this->getProtocol().$this->getHostWithPort();
422 * Return 'https://' if( this is an SSL request and 'http://' otherwise.
424 function getProtocol()
426 return $this->isSsl() ?
'https://' : 'http://';
430 * Is this an SSL request?
434 return isset($this->env
['HTTPS']) && ($this->env
['HTTPS'] === true ||
$this->env
['HTTPS'] == 'on');
438 * Returns the interpreted path to requested resource
442 return strstr($this->env
['REQUEST_URI'],'?') ?
substr($this->env
['REQUEST_URI'],0,strpos($this->env
['REQUEST_URI'],'?')) : $this->env
['REQUEST_URI'];
446 * Returns the port number of this request as an integer.
450 $this->port_as_int
= AK_WEB_REQUEST ? AK_SERVER_PORT
: 80;
451 return $this->port_as_int
;
455 * Returns the standard port number for this request's protocol
457 function getStandardPort()
459 return $this->isSsl() ?
443 : 80;
463 * Returns a port suffix like ':8080' if( the port number of this request
464 * is not the default HTTP port 80 or HTTPS port 443.
466 function getPortString()
468 $port = $this->getPort();
469 return $port == $this->getStandardPort() ?
'' : ($port ?
':'.$this->getPort() : '');
473 * Returns a host:port string for this request, such as example.com or
476 function getHostWithPort()
478 return $this->getHost() . $this->getPortString();
484 if(!empty($this->_host
)){
487 return AK_WEB_REQUEST ?
$this->env
['SERVER_NAME'] : 'localhost';
490 function &getSession()
495 function resetSession()
500 function &getCookies()
512 function getServerSoftware()
514 if(!empty($this->env
['SERVER_SOFTWARE'])){
515 if(preg_match('/^([a-zA-Z]+)/', $this->env
['SERVER_SOFTWARE'],$match)){
516 return strtolower($match[0]);
524 * Returns true if the request's 'X-Requested-With' header contains
525 * 'XMLHttpRequest'. (The Prototype Javascript library sends this header with
526 * every Ajax request.)
528 function isXmlHttpRequest()
530 return !empty($this->env
['HTTP_X_REQUESTED_WITH']) && strstr(strtolower($this->env
['HTTP_X_REQUESTED_WITH']),'xmlhttprequest');
534 return $this->isXmlHttpRequest();
539 return $this->isXmlHttpRequest();
544 * Receive the raw post data.
545 * This is useful for services such as REST, XMLRPC and SOAP
546 * which communicate over HTTP POST but don't use the traditional parameter format.
548 function getRawPost()
550 return empty($_ENV['RAW_POST_DATA']) ?
'' : $_ENV['RAW_POST_DATA'];
554 function _validateTechName($name)
556 return preg_match('/^[A-Za-z]{1,}[A-Za-z0-9_]*$/',$name);
561 // {{{ _mergeRequest()
564 * Populates $this->_request attribute with incoming request in the following precedence:
566 * $_SESSION['request'] <- This will override options provided by previous methods
570 * Command line params
573 * @return void Void returned. Modifies the private property "
575 function _mergeRequest()
577 $this->_request
= array();
579 $session_params = isset($_SESSION['request']) ?
$_SESSION['request'] : null;
580 $command_line_params = !empty($_REQUEST) ?
$_REQUEST : null;
582 $requests = array($command_line_params, $_GET, array_merge_recursive($_POST, $this->_getNormalizedFilesArray()), $_COOKIE, $session_params);
584 foreach ($requests as $request){
585 $this->_request
= (!is_null($request) && is_array($request)) ?
586 array_merge($this->_request
,$request) : $this->_request
;
592 function _getNormalizedFilesArray($params = null, $first_call = true)
594 $params = $first_call ?
$_FILES : $params;
597 $params = array_diff($params,array(''));
598 if(!empty($params) && is_array($params)){
599 foreach ($params as $name=>$details){
601 if(is_array($details) && !empty($details['name']) && !empty($details['tmp_name']) && !empty($details['size'])){
602 if(is_array($details['tmp_name'])){
603 foreach ($details['tmp_name'] as $item=>$item_details){
604 if(is_array($item_details)){
605 foreach (array_keys($item_details) as $k){
606 if(UPLOAD_ERR_NO_FILE
!= $details['error'][$item][$k]){
607 $result[$name][$item][$k] = array(
608 'name'=>$details['name'][$item][$k],
609 'tmp_name'=>$details['tmp_name'][$item][$k],
610 'size'=>$details['size'][$item][$k],
611 'type'=>$details['type'][$item][$k],
612 'error'=>$details['error'][$item][$k],
617 if(UPLOAD_ERR_NO_FILE
!= $details['error'][$item]){
618 $result[$name][$item] = array(
619 'name'=>$details['name'][$item],
620 'tmp_name'=>$details['tmp_name'][$item],
621 'size'=>$details['size'][$item],
622 'type'=>$details['type'][$item],
623 'error'=>$details['error'][$item],
628 }elseif ($first_call){
629 $result[$name] = $details;
631 $result[$name][] = $details;
633 }elseif(is_array($details)){
634 $_nested = $this->_getNormalizedFilesArray($details, false);
636 if(!empty($_nested)){
637 $result = array_merge(array($name=>$_nested), $result);
649 * Builds (i.e., "expands") the Request class for accessing
650 * the request parameters as public properties.
651 * For example, when the requests is "ak=/controller/action/id¶meter=value",
652 * once parsed, you can access the parameters of the request just like
655 * $value_to_get = $request->parameter
660 function _addParam($variable, $value)
662 if($variable[0] != '_'){
663 if( ( $variable == 'action' && !$this->isValidActionName($value)) ||
664 ( $variable == 'controller' && !$this->isValidControllerName($value)) ||
665 ( $variable == 'module' && !$this->isValidModuleName($value))
669 $this->$variable = $value;
679 * Correct double-escaping problems caused by "magic quotes" in some PHP
682 function _fixGpcMagic()
684 if(!defined('AK_GPC_MAGIC_FIXED')){
685 if (get_magic_quotes_gpc()) {
686 array_walk($_GET, array('AkRequest', '_fixGpc'));
687 array_walk($_POST, array('AkRequest', '_fixGpc'));
688 array_walk($_COOKIE, array('AkRequest', '_fixGpc'));
690 define('AK_GPC_MAGIC_FIXED',true);
694 function _fixGpc(&$item)
696 if (is_array($item)) {
697 array_walk($item, array('AkRequest', '_fixGpc'));
699 $item = stripslashes($item);
704 function _urlDecode()
706 if(!defined('AK_URL_DECODED')){
707 array_walk($_GET, array('AkRequest', '_performUrlDecode'));
708 define('AK_URL_DECODED',true);
712 function _performUrlDecode(&$item)
714 if (is_array($item)) {
715 array_walk($item, array('AkRequest', '_performUrlDecode'));
717 $item = urldecode($item);
725 * Recognizes a Request and returns the responsible controller instance
727 * @return AkActionController
729 function &recognize($Map = null)
731 AK_ENVIRONMENT
!= 'setup' ?
$this->_connectToDatabase() : null;
732 $this->_startSession();
733 $this->_enableInternationalizationSupport();
734 $this->_mapRoutes($Map);
736 $params = $this->getParams();
738 $module_path = $module_class_peffix = '';
739 if(!empty($params['module'])){
740 $module_path = trim(str_replace(array('/','\\'), DS
, Ak
::sanitize_include($params['module'], 'high')), DS
).DS
;
741 $module_shared_model = AK_CONTROLLERS_DIR
.DS
.trim($module_path,DS
).'_controller.php';
742 $module_class_peffix = str_replace(' ','_',AkInflector
::titleize(str_replace(DS
,' ', trim($module_path, DS
)))).'_';
745 $controller_file_name = AkInflector
::underscore($params['controller']).'_controller.php';
746 $controller_class_name = $module_class_peffix.AkInflector
::camelize($params['controller']).'Controller';
747 $controller_path = AK_CONTROLLERS_DIR
.DS
.$module_path.$controller_file_name;
748 include_once(AK_APP_DIR
.DS
.'application_controller.php');
750 if(!empty($module_path) && file_exists($module_shared_model)){
751 include_once($module_shared_model);
754 if(!is_file($controller_path) ||
!include_once($controller_path)){
755 if(AK_ENVIRONMENT
== 'development'){
756 trigger_error(Ak
::t('Could not find the file /app/controllers/<i>%controller_file_name</i> for '.
757 'the controller %controller_class_name',
758 array('%controller_file_name'=> $controller_file_name,
759 '%controller_class_name' => $controller_class_name)), E_USER_ERROR
);
760 }elseif(@include
(AK_PUBLIC_DIR
.DS
.'404.php')){
763 die('404 Page not found');
766 if(!class_exists($controller_class_name)){
768 if(AK_ENVIRONMENT
== 'development'){
769 trigger_error(Ak
::t('Controller <i>%controller_name</i> does not exist',
770 array('%controller_name' => $controller_class_name)), E_USER_ERROR
);
771 }elseif(@include
(AK_PUBLIC_DIR
.DS
.'404.php')){
774 die('404 Page not found');
777 $Controller =& new $controller_class_name(array('controller'=>true));
778 $Controller->_module_path
= $module_path;
779 isset($_SESSION) ?
$Controller->session
=& $_SESSION : null;
786 function _enableInternationalizationSupport()
788 if(AK_AVAILABLE_LOCALES
!= 'en'){
789 require_once(AK_LIB_DIR
.DS
.'AkLocaleManager.php');
791 $LocaleManager = new AkLocaleManager();
792 $LocaleManager->init();
793 $LocaleManager->initApplicationInternationalization($this);
794 $this->__internationalization_support_enabled
= true;
798 function _mapRoutes($Map = null)
800 require_once(AK_LIB_DIR
.DS
.'AkRouter.php');
801 if(is_file(AK_ROUTES_MAPPING_FILE
)){
805 include(AK_ROUTES_MAPPING_FILE
);
806 // Set this routes for being used via Ak::toUrl
807 Ak
::toUrl($Map,true);
808 $this->checkForRoutedRequests($Map);
813 function _connectToDatabase()
815 if(AK_AUTOMATIC_DB_CONNECTION
){
816 Ak
::db(AK_DEFAULT_DATABASE_PROFILE
);
820 function _startSession()
822 if(AK_AUTOMATIC_SESSION_START
){
823 if(!isset($_SESSION)){
824 if(AK_SESSION_HANDLER
== 1 && defined('AK_DATABASE_CONNECTION_AVAILABLE') && AK_DATABASE_CONNECTION_AVAILABLE
){
825 require_once(AK_LIB_DIR
.DS
.'AkDbSession.php');
827 $AkDbSession = new AkDbSession();
828 $AkDbSession->session_life
= AK_SESSION_EXPIRE
;
829 session_set_save_handler (
830 array(&$AkDbSession, '_open'),
831 array(&$AkDbSession, '_close'),
832 array(&$AkDbSession, '_read'),
833 array(&$AkDbSession, '_write'),
834 array(&$AkDbSession, '_destroy'),
835 array(&$AkDbSession, '_gc')
845 function &AkRequest()
848 $AkRequest =& Ak
::singleton('AkRequest', $null);