PageRenderTime 46ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/pkp/classes/core/PKPRouter.inc.php

https://github.com/lib-uoguelph-ca/ocs
PHP | 486 lines | 211 code | 61 blank | 214 comment | 44 complexity | a72974e7fbc58db60c008c59152c1c2d MD5 | raw file
Possible License(s): GPL-2.0
  1. <?php
  2. /**
  3. * @file classes/core/PKPRouter.inc.php
  4. *
  5. * Copyright (c) 2000-2012 John Willinsky
  6. * Distributed under the GNU GPL v2. For full terms see the file docs/COPYING.
  7. *
  8. * @class PKPRouter
  9. * @see PKPPageRouter
  10. * @see PKPComponentRouter
  11. * @ingroup core
  12. *
  13. * @brief Basic router class that has functionality common to all routers.
  14. */
  15. // $Id$
  16. class PKPRouter {
  17. //
  18. // Internal state cache variables
  19. // NB: Please do not access directly but
  20. // only via their respective getters/setters
  21. //
  22. /** @var PKPApplication */
  23. var $_application;
  24. /** @var Dispatcher */
  25. var $_dispatcher;
  26. /** @var integer context depth */
  27. var $_contextDepth;
  28. /** @var integer context list */
  29. var $_contextList;
  30. /** @var integer context list with keys and values flipped */
  31. var $_flippedContextList;
  32. /** @var integer context paths */
  33. var $_contextPaths = array();
  34. /** @var integer contexts */
  35. var $_contexts = array();
  36. /**
  37. * get the application
  38. * @return PKPApplication
  39. */
  40. function &getApplication() {
  41. assert(is_a($this->_application, 'PKPApplication'));
  42. return $this->_application;
  43. }
  44. /**
  45. * set the application
  46. * @param $application PKPApplication
  47. */
  48. function setApplication(&$application) {
  49. $this->_application =& $application;
  50. // Retrieve context depth and list
  51. $this->_contextDepth = $application->getContextDepth();
  52. $this->_contextList = $application->getContextList();
  53. $this->_flippedContextList = array_flip($this->_contextList);
  54. }
  55. /**
  56. * get the dispatcher
  57. * @return PKPDispatcher
  58. */
  59. function &getDispatcher() {
  60. assert(is_a($this->_dispatcher, 'Dispatcher'));
  61. return $this->_dispatcher;
  62. }
  63. /**
  64. * set the dispatcher
  65. * @param $dispatcher PKPDispatcher
  66. */
  67. function setDispatcher(&$dispatcher) {
  68. $this->_dispatcher =& $dispatcher;
  69. }
  70. /**
  71. * Determines whether this router can route the given request.
  72. * @param $request PKPRequest
  73. * @return boolean true, if the router supports this request, otherwise false
  74. */
  75. function supports(&$request) {
  76. // Default implementation returns always true
  77. return true;
  78. }
  79. /**
  80. * Determine whether or not this request is cacheable
  81. * @param $request PKPRequest
  82. * @return boolean
  83. */
  84. function isCacheable(&$request) {
  85. // Default implementation returns always false
  86. return false;
  87. }
  88. /**
  89. * Determine the filename to use for a local cache file.
  90. * @param $request PKPRequest
  91. * @return string
  92. */
  93. function getCacheFilename(&$request) {
  94. // must be implemented by sub-classes
  95. assert(false);
  96. }
  97. /**
  98. * Routes a given request to a handler operation
  99. * @param $request PKPRequest
  100. */
  101. function route(&$request) {
  102. // must be implemented by sub-classes
  103. assert(false);
  104. }
  105. /**
  106. * Build a handler request URL into PKPApplication.
  107. * @param $request PKPRequest the request to be routed
  108. * @param $newContext mixed Optional contextual paths
  109. * @param $handler string Optional name of the handler to invoke
  110. * @param $op string Optional name of operation to invoke
  111. * @param $path mixed Optional string or array of args to pass to handler
  112. * @param $params array Optional set of name => value pairs to pass as user parameters
  113. * @param $anchor string Optional name of anchor to add to URL
  114. * @param $escape boolean Whether or not to escape ampersands for this URL; default false.
  115. * @return string the URL
  116. */
  117. function url(&$request, $newContext = null, $handler = null, $op = null, $path = null,
  118. $params = null, $anchor = null, $escape = false) {
  119. // must be implemented by sub-classes
  120. assert(false);
  121. }
  122. /**
  123. * A generic method to return an array of context paths (e.g. a Press or a Conference/SchedConf paths)
  124. * @param $request PKPRequest the request to be routed
  125. * @param $requestedContextLevel int (optional) the context level to return in the path
  126. * @return array of string (each element the path to one context element)
  127. */
  128. function getRequestedContextPaths(&$request) {
  129. // Handle context depth 0
  130. if (!$this->_contextDepth) return array();
  131. // Validate context parameters
  132. assert(isset($this->_contextDepth) && isset($this->_contextList));
  133. // Determine the context path
  134. if (empty($this->_contextPaths)) {
  135. if ($request->isPathInfoEnabled()) {
  136. // Retrieve context from the path info
  137. if (isset($_SERVER['PATH_INFO'])) {
  138. // Split the path info into its constituents. Save all non-context
  139. // path info in $this->_contextPaths[$this->_contextDepth]
  140. // by limiting the explode statement.
  141. $this->_contextPaths = explode('/', trim($_SERVER['PATH_INFO'], '/'), $this->_contextDepth + 1);
  142. // Remove the part of the path info that is not relevant for context (if present)
  143. unset($this->_contextPaths[$this->_contextDepth]);
  144. }
  145. } else {
  146. // Retrieve context from url query string
  147. foreach($this->_contextList as $key => $contextName) {
  148. $this->_contextPaths[$key] = $request->getUserVar($contextName);
  149. }
  150. }
  151. // Canonicalize and clean context paths
  152. for($key = 0; $key < $this->_contextDepth; $key++) {
  153. $this->_contextPaths[$key] = (
  154. isset($this->_contextPaths[$key]) && !empty($this->_contextPaths[$key]) ?
  155. $this->_contextPaths[$key] : 'index'
  156. );
  157. $this->_contextPaths[$key] = Core::cleanFileVar($this->_contextPaths[$key]);
  158. }
  159. HookRegistry::call('Router::getRequestedContextPaths', array(&$this->_contextPaths));
  160. }
  161. return $this->_contextPaths;
  162. }
  163. /**
  164. * A generic method to return a single context path (e.g. a Press or a SchedConf path)
  165. * @param $request PKPRequest the request to be routed
  166. * @param $requestedContextLevel int (optional) the context level to return
  167. * @return string
  168. */
  169. function getRequestedContextPath(&$request, $requestedContextLevel = 1) {
  170. // Handle context depth 0
  171. if (!$this->_contextDepth) return null;
  172. // Validate the context level
  173. assert(isset($this->_contextDepth) && isset($this->_contextList));
  174. assert($requestedContextLevel > 0 && $requestedContextLevel <= $this->_contextDepth);
  175. // Return the full context, then retrieve the requested context path
  176. $contextPaths = $this->getRequestedContextPaths($request);
  177. assert(isset($this->_contextPaths[$requestedContextLevel - 1]));
  178. return $this->_contextPaths[$requestedContextLevel - 1];
  179. }
  180. /**
  181. * A Generic call to a context defining object (e.g. a Press, a Conference, or a SchedConf)
  182. * @param $request PKPRequest the request to be routed
  183. * @param $requestedContextLevel int (optional) the desired context level
  184. * @return object
  185. */
  186. function &getContext(&$request, $requestedContextLevel = 1) {
  187. // Handle context depth 0
  188. if (!$this->_contextDepth) {
  189. $nullVar = null;
  190. return $nullVar;
  191. }
  192. if (!isset($this->_contexts[$requestedContextLevel])) {
  193. // Retrieve the requested context path (this validates the context level and the path)
  194. $path = $this->getRequestedContextPath($request, $requestedContextLevel);
  195. // Resolve the path to the context
  196. if ($path == 'index') {
  197. $this->_contexts[$requestedContextLevel] = null;
  198. } else {
  199. // Get the context name (this validates the context name)
  200. $requestedContextName = $this->_contextLevelToContextName($requestedContextLevel);
  201. // Get the DAO for the requested context.
  202. $contextClass = ucfirst($requestedContextName);
  203. $daoName = $contextClass.'DAO';
  204. $daoInstance =& DAORegistry::getDAO($daoName);
  205. // Retrieve the context from the DAO (by path)
  206. $daoMethod = 'get'.$contextClass.'ByPath';
  207. assert(method_exists($daoInstance, $daoMethod));
  208. $this->_contexts[$requestedContextLevel] = $daoInstance->$daoMethod($path);
  209. }
  210. }
  211. return $this->_contexts[$requestedContextLevel];
  212. }
  213. /**
  214. * Get the object that represents the desired context (e.g. Conference or Press)
  215. * @param $request PKPRequest the request to be routed
  216. * @param $requestedContextName string page context
  217. * @return object
  218. */
  219. function &getContextByName(&$request, $requestedContextName) {
  220. // Handle context depth 0
  221. if (!$this->_contextDepth) {
  222. $nullVar = null;
  223. return $nullVar;
  224. }
  225. // Convert the context name to a context level (this validates the context name)
  226. $requestedContextLevel = $this->_contextNameToContextLevel($requestedContextName);
  227. // Retrieve the requested context by level
  228. $returner = $this->getContext($request, $requestedContextLevel);
  229. return $returner;
  230. }
  231. /**
  232. * Get the URL to the index script.
  233. * @param $request PKPRequest the request to be routed
  234. * @return string
  235. */
  236. function getIndexUrl(&$request) {
  237. if (!isset($this->_indexUrl)) {
  238. if ($request->isRestfulUrlsEnabled()) {
  239. $this->_indexUrl = $request->getBaseUrl();
  240. } else {
  241. $this->_indexUrl = $request->getBaseUrl() . '/' . basename($_SERVER['SCRIPT_NAME']);
  242. }
  243. HookRegistry::call('Router::getIndexUrl', array(&$this->_indexUrl));
  244. }
  245. return $this->_indexUrl;
  246. }
  247. //
  248. // Private class helper methods
  249. //
  250. /**
  251. * Canonicalizes the new context.
  252. *
  253. * A new context can be given as a scalar. In this case only the
  254. * first context will be replaced. If the context depth of the
  255. * current application is higher than one than the context can also
  256. * be given as an array if more than the first context should
  257. * be replaced. We therefore canonicalize the new context to an array.
  258. *
  259. * When all entries are of the form 'contextName' => null or if
  260. * $newContext == null then we'll return an empty array.
  261. *
  262. * @param $newContext the raw context array
  263. * @return array the canonicalized context array
  264. */
  265. function _urlCanonicalizeNewContext($newContext) {
  266. // Create an empty array in case no new context was given.
  267. if (is_null($newContext)) $newContext = array();
  268. // If we got the new context as a scalar then transform
  269. // it into an array.
  270. if (is_scalar($newContext)) $newContext = array($newContext);
  271. // Check whether any new context has been provided.
  272. // If not then return an empty array.
  273. $newContextProvided = false;
  274. foreach($newContext as $contextElement) {
  275. if(isset($contextElement)) $newContextProvided = true;
  276. }
  277. if (!$newContextProvided) $newContext = array();
  278. return $newContext;
  279. }
  280. /**
  281. * Build the base URL and add the context part of the URL.
  282. *
  283. * The new URL will be based on the current request's context
  284. * if no new context is given.
  285. *
  286. * The base URL for a given primary context can be overridden
  287. * in the config file using the 'base_url[context]' syntax in the
  288. * config file's 'general' section.
  289. *
  290. * @param $request PKPRequest the request to be routed
  291. * @param $newContext mixed (optional) context that differs from
  292. * the current request's context
  293. * @return array An array consisting of the base url as the first
  294. * entry and the context as the remaining entries.
  295. */
  296. function _urlGetBaseAndContext(&$request, $newContext = array()) {
  297. $pathInfoEnabled = $request->isPathInfoEnabled();
  298. // Retrieve the context list.
  299. $contextList = $this->_contextList;
  300. // Determine URL context
  301. $context = array();
  302. foreach ($contextList as $contextKey => $contextName) {
  303. if ($pathInfoEnabled) {
  304. $contextParameter = '';
  305. } else {
  306. $contextParameter = $contextName.'=';
  307. }
  308. $newContextValue = array_shift($newContext);
  309. if (isset($newContextValue)) {
  310. // A new context has been set so use it.
  311. $contextValue = rawurlencode($newContextValue);
  312. } else {
  313. // No new context has been set so determine
  314. // the current request's context
  315. $contextObject =& $this->getContextByName($request, $contextName);
  316. if ($contextObject) $contextValue = $contextObject->getPath();
  317. else $contextValue = 'index';
  318. }
  319. // Check whether the base URL is overridden.
  320. if ($contextKey == 0) {
  321. $overriddenBaseUrl = Config::getVar('general', "base_url[$contextValue]");
  322. }
  323. $context[] = $contextParameter.$contextValue;;
  324. }
  325. // Generate the base url
  326. if (!empty($overriddenBaseUrl)) {
  327. $baseUrl = $overriddenBaseUrl;
  328. // Throw the overridden context away
  329. array_shift($context);
  330. array_shift($contextList);
  331. } else {
  332. $baseUrl = $this->getIndexUrl($request);
  333. }
  334. // Join base URL and context and return the result
  335. $baseUrlAndContext = array_merge(array($baseUrl), $context);
  336. return $baseUrlAndContext;
  337. }
  338. /**
  339. * Build the additional parameters part of the URL.
  340. * @param $request PKPRequest the request to be routed
  341. * @param $params array (optional) the parameter list to be
  342. * transformed to a url part.
  343. * @return array the encoded parameters or an empty array
  344. * if no parameters were given.
  345. */
  346. function _urlGetAdditionalParameters(&$request, $params = null) {
  347. $additionalParameters = array();
  348. if (!empty($params)) {
  349. assert(is_array($params));
  350. foreach ($params as $key => $value) {
  351. if (is_array($value)) {
  352. foreach($value as $element) {
  353. $additionalParameters[] = $key.'%5B%5D='.rawurlencode($element);
  354. }
  355. } else {
  356. $additionalParameters[] = $key.'='.rawurlencode($value);
  357. }
  358. }
  359. }
  360. return $additionalParameters;
  361. }
  362. /**
  363. * Creates a valid URL from parts.
  364. * @param $baseUrl string the protocol, domain and initial path/parameters, no anchors allowed here
  365. * @param $pathInfoArray array strings to be concatenated as path info
  366. * @param $queryParametersArray array strings to be concatenated as query string
  367. * @param $anchor string an additional anchor
  368. * @param $escape boolean whether to escape ampersands
  369. * @return string the URL
  370. */
  371. function _urlFromParts($baseUrl, $pathInfoArray = array(), $queryParametersArray = array(), $anchor = '', $escape = false) {
  372. // Parse the base url
  373. $baseUrlParts = parse_url($baseUrl);
  374. assert(isset($baseUrlParts['scheme']) && isset($baseUrlParts['host']) && !isset($baseUrlParts['fragment']));
  375. // Reconstruct the base url without path and query
  376. $baseUrl = $baseUrlParts['scheme'].'://';
  377. if (isset($baseUrlParts['user'])) {
  378. $baseUrl .= $baseUrlParts['user'];
  379. if (isset($baseUrlParts['pass'])) {
  380. $baseUrl .= ':'.$baseUrlParts['pass'];
  381. }
  382. $baseUrl .= '@';
  383. }
  384. $baseUrl .= $baseUrlParts['host'];
  385. if (isset($baseUrlParts['port'])) $baseUrl .= ':'.$baseUrlParts['port'];
  386. $baseUrl .= '/';
  387. // Add path info from the base URL
  388. // to the path info array (if any).
  389. if (isset($baseUrlParts['path'])) {
  390. $pathInfoArray = array_merge(explode('/', trim($baseUrlParts['path'], '/')), $pathInfoArray);
  391. }
  392. // Add query parameters from the base URL
  393. // to the query parameter array (if any).
  394. if (isset($baseUrlParts['query'])) {
  395. $queryParametersArray = array_merge(explode('&', $baseUrlParts['query']), $queryParametersArray);
  396. }
  397. // Expand path info
  398. $pathInfo = implode('/', $pathInfoArray);
  399. // Expand query parameters
  400. $amp = ($escape ? '&amp;' : '&');
  401. $queryParameters = implode($amp, $queryParametersArray);
  402. $queryParameters = (empty($queryParameters) ? '' : '?'.$queryParameters);
  403. // Assemble and return the final URL
  404. return $baseUrl.$pathInfo.$queryParameters.$anchor;
  405. }
  406. /**
  407. * Convert a context level to its corresponding context name.
  408. * @param $contextLevel integer
  409. * @return string context name
  410. */
  411. function _contextLevelToContextName($contextLevel) {
  412. assert(isset($this->_contextList[$contextLevel - 1]));
  413. return $this->_contextList[$contextLevel - 1];
  414. }
  415. /**
  416. * Convert a context name to its corresponding context level.
  417. * @param $contextName string
  418. * @return integer context level
  419. */
  420. function _contextNameToContextLevel($contextName) {
  421. assert(isset($this->_flippedContextList[$contextName]));
  422. return $this->_flippedContextList[$contextName] + 1;
  423. }
  424. }
  425. ?>