PageRenderTime 60ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 0ms

/src/library/route/PwRoute.php

https://github.com/cuijinquan/nextwind
PHP | 643 lines | 434 code | 35 blank | 174 comment | 120 complexity | ef050c8d3220758903b8191860f1fa7a MD5 | raw file
  1. <?php
  2. Wind::import('LIB:route.AbstractPwRoute');
  3. /**
  4. * 前台路由
  5. *
  6. * @author Shi Long <long.shi@alibaba-inc.com>
  7. * @copyright ©2003-2103 phpwind.com
  8. * @license http://www.windframework.com
  9. * @version $Id: PwRoute.php 25816 2013-03-25 06:10:30Z long.shi $
  10. * @package library
  11. */
  12. class PwRoute extends AbstractPwRoute {
  13. private $entrance = 'CONF:entrance';
  14. /**
  15. * 特殊规则的rewrite
  16. */
  17. private $rewrite_special = false;
  18. /**
  19. * 普通规则的rewrite
  20. */
  21. private $rewrite_common = false;
  22. /**
  23. * 符合特殊情况时,url串省略mca参数
  24. */
  25. private $omit_mca = false;
  26. public $dynamicDomain = array();
  27. public $dynamic = array();
  28. public $dynamicHost = '';
  29. /**
  30. * 符合特殊二级域名时,后面皆可省略的情况
  31. */
  32. private $onlydomain = false;
  33. protected $params = array('a' => 3, 'c' => 2, 'm' => 1);
  34. protected $_init = false;
  35. protected $base = null;
  36. protected $origialBase = null;
  37. protected $scheme = 'http://';
  38. /*
  39. * (non-PHPdoc) @see WindRewriteRoute::match()
  40. */
  41. public function match($request) {
  42. $this->init(false, $request);
  43. $host = $request->getHostInfo();
  44. $args = $this->_matchDomain($host);
  45. $_args = $this->_matchScript($request->getScript(), $request->getScriptUrl());
  46. $path = trim(str_replace($request->getBaseUrl(true), '', $host . $request->getRequestUri()), '/');
  47. $rawPath = $this->_getRawPath($path);
  48. // 首页设置
  49. if (empty($rawPath) && empty($args) && empty($_args)) {
  50. return Wekit::C()->site->get('homeRouter', array());
  51. }
  52. $_args = $this->_matchPath($path) + $_args;
  53. if (empty($_args)) {
  54. $this->_filterIllegal($rawPath);
  55. }
  56. return $_args + $args;
  57. }
  58. /**
  59. * 解析url - 公开方法
  60. *
  61. * 返回false 表示外链
  62. * 返回array 为解析好的参数
  63. *
  64. * @param string $url
  65. * @return array
  66. */
  67. public function matchUrl($url) {
  68. Wind::import('SRV:domain.srv.helper.PwDomainHelper');
  69. $r = PwDomainHelper::parse_url($url);
  70. list($host, $isSecure, $script, $path, $scriptUrl) = $r;
  71. if ($host && !PwDomainHelper::isMyBrother($host,
  72. Wind::getApp()->getRequest()->getHostInfo())) {
  73. return false;
  74. }
  75. $this->init();
  76. $args = $this->_matchDomain($host);
  77. $_args = $this->_matchScript($script, $scriptUrl);
  78. return array_merge($this->_getDefault(), $args, $_args, $this->_matchPath($path, true));
  79. }
  80. /*
  81. * (non-PHPdoc) @see WindRewriteRoute::build()
  82. */
  83. public function build($router, $action, $args = array()) {
  84. $this->init(true);
  85. list($_m, $_c, $_a, $args) = $this->_resolveMca($router, $action, $args);
  86. $domain = $this->_buildDomain($_m, $_c, $_a, $args);
  87. if ($this->onlydomain) return $domain;
  88. $url = $this->_buildUrl($_m, $_c, $_a, $router, $args);
  89. if (3 < count($this->dynamicDomain)) {
  90. $this->dynamicDomain[] = $url;
  91. }
  92. return $domain . $url;
  93. }
  94. /**
  95. * 检查url绝对路径
  96. *
  97. * @param string $url
  98. * @return string
  99. */
  100. public function checkUrl($url) {
  101. if (false === strpos($url, '://')) {
  102. $appType = $this->_getAppType();
  103. $base = Wekit::url()->base;
  104. isset($appType['default']) && $base = $appType['default'] . $this->base;
  105. $url = $base . '/' . trim($url, '/');
  106. }
  107. return $url;
  108. }
  109. /**
  110. * 解析url - 普通伪静态
  111. *
  112. * @param WindHttpRequest $request
  113. * @param string $path
  114. * @return array null
  115. */
  116. private function _matchCommon($path) {
  117. $pattern = '/^(\w+)(-\w+)(-\w+)(.*)$/i';
  118. if (!preg_match($pattern, $path, $matches)) return array();
  119. $params = array();
  120. foreach ($this->params as $k => $v) {
  121. $value = isset($matches[$v]) ? $matches[$v] : '';
  122. $params[$k] = trim($value, '-/');
  123. }
  124. return array_merge($params, WindUrlHelper::urlToArgs(trim($matches[4], '?'), true));
  125. }
  126. /**
  127. * 解析url - 二级域名
  128. *
  129. * @param WindHttpRequest $request
  130. * @param string $path
  131. * @return array null
  132. */
  133. private function _matchDomain($host) {
  134. /* 解析二级域名 */
  135. if (empty($host)) return array();
  136. $appType = $this->_getAppType();
  137. if (isset($appType['default']) && $host == $appType['default']) return array();
  138. $space_root = Wekit::C()->site->get('domain.space.root', '');
  139. if ($space_root) {
  140. $rawHost = str_replace($this->scheme, '', $host);
  141. if ($pos = strpos($rawHost, $space_root)) {
  142. $uid = Wekit::load('domain.PwSpaceDomain')->getUidByDomain(substr($rawHost, 0, $pos - 1));
  143. if ($uid) {
  144. return array('m' => 'space', 'c' => 'index', 'a' => 'run', 'uid' => $uid);
  145. }
  146. }
  147. }
  148. $domain = Wekit::C()->site->get('domain', array());
  149. if (isset($domain[$host])) {
  150. list($a, $c, $m, $args) = WindUrlHelper::resolveAction($domain[$host]);
  151. return array_merge($args, array('m' => $m, 'c' => $c, 'a' => $a));
  152. }
  153. return array();
  154. }
  155. /**
  156. * 解析脚本
  157. *
  158. * @param WindHttpRequest $request
  159. * @return array
  160. */
  161. private function _matchScript($script, $scriptUrl) {
  162. if (empty($script) || $script == 'index.php') {
  163. return array();
  164. }
  165. $multi = $this->_getMulti();
  166. if (isset($multi[$script])) {
  167. $this->base = rtrim(str_replace($script, '', $scriptUrl), '\\/.');
  168. $route = explode('/', $multi[$script]);
  169. return array('m' => $route[0], 'c' => $route[1], 'a' => $route[2]);
  170. } else {
  171. foreach ($multi as $k => $v) {
  172. if (false !== ($pos = strpos($scriptUrl, $k))) {
  173. $this->base = rtrim(substr($scriptUrl, 0, $pos), '\\/.');
  174. $route = explode('/', $v);
  175. return array('m' => $route[0], 'c' => $route[1]) + ($route[2] == '*' ? array() : array('a' => $route[2]));
  176. }
  177. }
  178. }
  179. return array();
  180. }
  181. /**
  182. * 生成url - 普通伪静态
  183. *
  184. * @param array $route
  185. * @param
  186. * array WindRouter
  187. * @param array $args
  188. * @return string
  189. */
  190. private function _buildCommon($router, $route, $args) {
  191. $reverse = '/%s-%s-%s';
  192. $_args = array();
  193. $flag = 0;
  194. $methods = array(
  195. 'a' => 'getDefaultAction',
  196. 'c' => 'getDefaultController',
  197. 'm' => 'getDefaultModule');
  198. $flags = array('a' => 0, 'c' => 1, 'm' => 3);
  199. $consts = array('a' => 1, 'c' => 3, 'm' => 7);
  200. foreach ($this->params as $k => $v) {
  201. if ($route[$k] === $router->$methods[$k]() && $flag === $flags[$k]) $flag = $consts[$k];
  202. $_args[$v] = $route[$k];
  203. unset($args[$k]);
  204. }
  205. if ($flag == 7 && empty($args)) return '';
  206. $_args[0] = $reverse;
  207. ksort($_args);
  208. $url = call_user_func_array("sprintf", $_args);
  209. return $url . ($args ? '?' . WindUrlHelper::argsToUrl($args) : '');
  210. }
  211. /**
  212. * 生成url - 二级域名
  213. *
  214. * @param array $route
  215. * @param array $args
  216. * @return string
  217. */
  218. private function _buildDomain($_m, $_c, $_a, $args) {
  219. /* 二级域名 */
  220. $key = "$_m/$_c/$_a";
  221. list($domain, $type) = $this->_getDomainByType($key);
  222. if (!empty($domain)) {
  223. $domainKey = $this->_getDomainKey();
  224. if (isset($domainKey[$key])) {
  225. $id = $domainKey[$key];
  226. if (!isset($args[$id])) {
  227. return '';
  228. } else {
  229. if (isset($domain[$args[$id]])) {
  230. $this->omit_mca = true;
  231. 1 == count($args) && $this->onlydomain = true;
  232. return $domain[$args[$id]] . $this->base;
  233. } elseif ($type == 'forum') {
  234. $this->dynamicDomain[] = '<?php $__route_domain=' . var_export($domain,
  235. true) . ';';
  236. if (1 == count($args)) {
  237. $this->dynamicDomain[] = 'if($__route_domain[' . $args[$id] . ']){ echo $__route_domain[' . $args[$id] . '].\'' . $this->base . '\';}else{ ?>';
  238. } else {
  239. $this->dynamicDomain[] = 'if($__route_domain[' . $args[$id] . ']){ echo $__route_domain[' . $args[$id] . '].\'' . $this->base . '\';?>';
  240. $this->dynamicDomain[] = '<?php }else{ ?>';
  241. }
  242. $this->dynamicDomain[] = '<?php } ?>';
  243. }
  244. }
  245. }
  246. }
  247. return $this->_getModuleDomain($_m);
  248. }
  249. /**
  250. * 生成url串
  251. *
  252. * @param string $_m
  253. * @param string $_c
  254. * @param string $_a
  255. * @param WindRouter $router
  256. * @param array $args
  257. * @return string
  258. */
  259. private function _buildUrl($_m, $_c, $_a, $router, $args) {
  260. if ($this->rewrite_special) {
  261. $rule = $this->_getRule();
  262. if (!empty($rule)) {
  263. $_args = $args;
  264. foreach ($rule as $v) {
  265. if ($v['route'] == "$_m/$_c/$_a") {
  266. $format = array();
  267. preg_match_all('/\{(\w+)\}/', $v['format'], $matches);
  268. //if (empty($matches[1])) continue;
  269. $is_fname = strpos($v['format'], '{fname}') !== false;
  270. if (1 === count($matches[1]) && !$is_fname) {
  271. if (!isset($_args[$matches[1][0]])) continue;
  272. }
  273. if ($is_fname) {
  274. if ($this->dynamicDomain) continue;
  275. if (!isset($_args['fid'])) continue;
  276. $domain = $this->_getDomain('id', 'domain');
  277. $this->dynamic[] = '<?php $__route_rewrite=' . var_export($domain, true) . ';';
  278. $this->dynamic[] = $_args['fid'] ? '($__route_rewrite[' . $_args['fid'] . '] ? $__route_rewrite[' . $_args['fid'] . '] : \'fname\')' : '\'fname\'';
  279. if (is_numeric($_args['fid'])) {
  280. if (!$domain[$_args['fid']]) continue;
  281. $format['{fname}'] = $domain[$_args['fid']];
  282. }
  283. }
  284. if ($pos = strpos($v['format'], '{page}')) {
  285. if (!isset($_args['page'])) {
  286. $v['format'] = str_replace($v['format'][$pos - 1] . '{page}', '', $v['format']);
  287. }
  288. }
  289. foreach ($matches[1] as $code) {
  290. if ($code != 'fname') {
  291. $format['{' . $code . '}'] = isset($_args[$code]) ? rawurlencode($_args[$code]) : '';
  292. unset($_args[$code]);
  293. }
  294. }
  295. if ('forum' == $this->_getType("$_m/$_c/$_a")) unset($_args['fid']);
  296. return '/' . trim(strtr($v['format'], $format), '/-.*') . ($_args ? '?' . WindUrlHelper::argsToUrl(
  297. $_args) : '');
  298. }
  299. }
  300. }
  301. }
  302. if ($this->rewrite_common) {
  303. return $this->_buildCommon($router, array('m' => $_m, 'c' => $_c, 'a' => $_a), $args);
  304. } else {
  305. /* 非rewrite时获取脚本文件 */
  306. $script = $this->_buildScript($_m, $_c, $_a, $args);
  307. $url = '';
  308. if ($this->omit_mca)
  309. $url = $args ? $script . '?' . WindUrlHelper::argsToUrl($args) : $script;
  310. else {
  311. $_args = array();
  312. if ($_m !== $router->getDefaultModule()) $_args['m'] = $_m;
  313. if ($_c !== $router->getDefaultController()) $_args['c'] = $_c;
  314. if ($_a !== $router->getDefaultAction()) $_args['a'] = $_a;
  315. $args = array_merge($_args, $args);
  316. $url = $args ? $script . '?' . WindUrlHelper::argsToUrl($args) : '';
  317. }
  318. return $url;
  319. }
  320. }
  321. /**
  322. * 解析url串
  323. *
  324. * @param WindHttpRequest $request
  325. * @param string $path
  326. * @return array
  327. */
  328. private function _matchPath($path, $rawDecode = false) {
  329. $path = trim($path, '/');
  330. if (empty($path)) return array();
  331. if ($this->rewrite_special) {
  332. /* 解析特殊伪静态 */
  333. $rule = $this->_getRule();
  334. if (!empty($rule)) {
  335. if (false !== strpos($path, '?')) {
  336. list($rewritePath, $queryPath) = explode('?', $path . '?', 2);
  337. } else {
  338. list($rewritePath, $queryPath) = explode('&', $path . '&', 2);
  339. }
  340. foreach ($rule as $k => $v) {
  341. if ($k == 'default') continue;
  342. $rewritePath = rawurldecode($rewritePath);
  343. if (preg_match($v['pattern'], $rewritePath, $matches)) {
  344. $matches = array_diff_key($matches, range(0, intval(count($matches) / 2)));
  345. $args = WindUrlHelper::urlToArgs(trim($queryPath, '?'), true);
  346. if ($k == 'thread' || $k == 'cate') {
  347. if (!isset($matches['fid']) && isset($matches['fname'])) {
  348. $domain = $this->_getDomain('domain', 'domain_key');
  349. if (isset($domain[$matches['fname']])) {
  350. list($_a, $_c, $_m, $_args) = WindUrlHelper::resolveAction(
  351. $domain[$matches['fname']]);
  352. return array_merge($matches,
  353. array('m' => $_m, 'c' => $_c, 'a' => $_a), $_args + $args);
  354. }
  355. } elseif (isset($matches['fid'])) {
  356. $forum = Wekit::load('forum.PwForum')->getForum($matches['fid']);
  357. $action = array(
  358. 'category' => array('m' => 'bbs', 'c' => 'cate', 'a' => 'run'),
  359. 'forum' => array('m' => 'bbs', 'c' => 'thread', 'a' => 'run'),
  360. 'sub' => array('m' => 'bbs', 'c' => 'thread', 'a' => 'run'),
  361. 'sub2' => array('m' => 'bbs', 'c' => 'thread', 'a' => 'run'),
  362. );
  363. $forum_type = isset($forum['type']) ? $forum['type'] : 'forum';
  364. return array_merge($matches,
  365. $action[$forum_type], $args);
  366. }
  367. }
  368. $route = explode('/', $v['route']);
  369. return array_merge($matches,
  370. array('m' => $route[0], 'c' => $route[1], 'a' => $route[2]), $args);
  371. }
  372. }
  373. }
  374. }
  375. /* 解析普通伪静态 */
  376. if ($this->rewrite_common && strpos($path, '.php') === false) return $this->_matchCommon(
  377. $path);
  378. $r = (false !== strpos($path, '?')) ? WindUrlHelper::urlToArgs($path) : array();
  379. if ($rawDecode) return $r;
  380. $return = array();
  381. if (isset($r['m']) || isset($r['c']) || isset($r['a'])) {
  382. $return['m'] = isset($r['m']) ? $r['m'] : $this->default_m;
  383. $return['c'] = isset($r['c']) ? $r['c'] : 'index';
  384. $return['a'] = isset($r['a']) ? $r['a'] : 'run';
  385. }
  386. return $return;
  387. }
  388. /**
  389. * 万事俱备
  390. * @param $request WindHttpRequest
  391. */
  392. protected function init($build = false, $request = null) {
  393. if (!$this->_init) {
  394. $router = Wind::getComponent('router');
  395. $this->default_m || $this->default_m = Wind::getApp()->getConfig('default-module', '', $router->getDefaultModule());
  396. if ($this->getConfig('default')) {
  397. $router->setDefaultModule($this->default_m);
  398. }
  399. $rule = $this->_getRule();
  400. if (isset($rule['default'])) {
  401. $this->rewrite_common = true;
  402. unset($rule['default']);
  403. }
  404. $rule && $this->rewrite_special = true;
  405. if ($request) {
  406. $this->origialBase = $this->base = $request->getBaseUrl();
  407. $this->scheme = $request->getScheme() . '://';
  408. }
  409. $this->_init = true;
  410. }
  411. if ($build) {
  412. $this->omit_mca = $this->onlydomain = false;
  413. $this->dynamicDomain = $this->dynamic = array();
  414. $this->dynamicHost = '';
  415. $this->base === null && $this->base = Wind::getApp()->getRequest()->getBaseUrl();
  416. }
  417. }
  418. /**
  419. * 初始化配置
  420. *
  421. * @return array
  422. */
  423. private function _getRule() {
  424. static $conf = null;
  425. if ($conf === null) {
  426. $conf = Wekit::C()->site->get('rewrite', array());
  427. }
  428. return $conf;
  429. }
  430. /**
  431. * 初始化配置
  432. *
  433. * @return array
  434. */
  435. private function _getDomainKey() {
  436. return array(
  437. 'bbs/cate/run' => 'fid',
  438. 'bbs/thread/run' => 'fid',
  439. 'bbs/read/run' => 'fid',
  440. 'special/index/run' => 'id');
  441. }
  442. private function _getType($type) {
  443. $all = array(
  444. 'bbs/cate/run' => 'forum',
  445. 'bbs/thread/run' => 'forum',
  446. 'bbs/read/run' => 'forum',
  447. 'special/index/run' => 'special');
  448. return isset($all[$type]) ? $all[$type] : '';
  449. }
  450. /**
  451. * 根据类型获取域名
  452. *
  453. * @return array
  454. */
  455. private function _getDomainByType($type) {
  456. $domain_type = $this->_getType($type);
  457. if (!$domain_type) return array(array(), '');
  458. static $domain = array();
  459. if (!isset($domain[$domain_type])) {
  460. $temp = array();
  461. if (Wekit::C('site', "domain.{$domain_type}.isopen")) {
  462. $temp = $this->_getDomain('id', 'domain', $domain_type, true);
  463. }
  464. $domain[$domain_type] = $temp;
  465. }
  466. return array($domain[$domain_type], $domain_type);
  467. }
  468. /**
  469. * 获取域名
  470. *
  471. * @param string $key
  472. * @param string $value
  473. * @param string $type 域名类型 forum, special
  474. * @return array $key => $value 键值对
  475. */
  476. private function _getDomain($key = 'domain', $value = 'domain', $type = 'forum', $absolute = false) {
  477. static $result = array();
  478. static $domain = null;
  479. if (!isset($result[$type][$key][$value])) {
  480. //不论match,build,只查一次域名表,表中存放的是所有域名,不包括空间。数量大概在几十个
  481. $domain === null && $domain = Wekit::load('domain.PwDomain')->getAll();
  482. foreach ($domain as $v) {
  483. if ($v['domain_type'] == $type) {
  484. if ($value == 'domain' && $absolute) $v[$value] = $this->scheme . $v[$value] . '.' . $v['root'];
  485. $temp[$v[$key]] = $v[$value];
  486. }
  487. }
  488. $result[$type][$key][$value] = $temp;
  489. }
  490. return $result[$type][$key][$value];
  491. }
  492. /**
  493. * 初始化配置
  494. *
  495. * @return array
  496. */
  497. private function _getAppType() {
  498. static $conf = null;
  499. if ($conf === null) {
  500. $conf = Wekit::C()->site->get('domain.app', array());
  501. }
  502. return $conf;
  503. }
  504. /**
  505. * 初始化配置
  506. *
  507. * @return array
  508. */
  509. private function _getMulti() {
  510. static $conf = null;
  511. if ($conf === null) {
  512. $conf = @include (Wind::getRealPath($this->entrance));
  513. $conf = $conf ? $conf : array();
  514. }
  515. return $conf;
  516. }
  517. /**
  518. * 获取应用域名
  519. *
  520. * @param string $_m
  521. * @return string
  522. */
  523. private function _getModuleDomain($_m) {
  524. $appType = $this->_getAppType();
  525. if (isset($appType[$_m])) return $appType[$_m] . $this->base;
  526. isset($appType['default']) || $this->dynamicHost = Wekit::url()->base;
  527. return isset($appType['default']) ? $appType['default'] . $this->base : ($this->origialBase != $this->base ? Wekit::url()->base : '');
  528. }
  529. /**
  530. * 分析参数
  531. *
  532. * @param AbstractWindRouter $router
  533. * @param string $action
  534. * @param array $args
  535. * @return array
  536. */
  537. private function _resolveMca($router, $action, $args) {
  538. list($action, $_args) = explode('?', $action . '?');
  539. $args = array_merge($args, ($_args ? WindUrlHelper::urlToArgs($_args, false) : array()));
  540. $action = trim($action, '/');
  541. $tmp = explode('/', $action . '/');
  542. end($tmp);
  543. if (5 === count($tmp) && !strncasecmp('app/', $action, 4)) {
  544. list($_a, $_c, $_app_name, $_m) = array(prev($tmp), prev($tmp), prev($tmp), prev($tmp));
  545. $args['app'] = $_app_name;
  546. } else {
  547. list($_a, $_c, $_m) = array(prev($tmp), prev($tmp), prev($tmp));
  548. }
  549. $_m = $_m ? $_m : $router->getDefaultModule();
  550. $_c = $_c ? $_c : $router->getDefaultController();
  551. $_a = $_a ? $_a : $router->getDefaultAction();
  552. return array($_m, $_c, $_a, $args);
  553. }
  554. /**
  555. * 生成脚本文件
  556. *
  557. * @param string $_m
  558. * @param string $_c
  559. * @param string $_a
  560. * @return string
  561. */
  562. private function _buildScript($_m, $_c, $_a, &$args) {
  563. $pattern = "$_m/$_c/$_a";
  564. foreach ($this->_getMulti() as $k => $v) {
  565. if (strpos($v, '*')) {
  566. $v = str_replace(array('*', '/'), array('\w*', '\/'), $v);
  567. if (preg_match('/^' . $v . '$/i', $pattern)){
  568. $args['a'] = $_a;
  569. $this->omit_mca = true;
  570. return "/$k";
  571. }
  572. } elseif ($v == $pattern) {
  573. $this->omit_mca = true;
  574. return "/$k";
  575. }
  576. }
  577. return '/index.php';
  578. }
  579. /**
  580. * 获取待验证有效的url串
  581. *
  582. * @param string $path
  583. * @return string
  584. */
  585. private function _getRawPath($path) {
  586. $rawpath = $path;
  587. false !== ($pos = strpos($path, 'index.php')) && $rawpath = substr($path, $pos + 9);
  588. return trim($rawpath, '/');
  589. }
  590. /**
  591. * 过滤无效url
  592. *
  593. * @param string $rawpath
  594. * @throws WindException
  595. */
  596. private function _filterIllegal($rawpath) {
  597. foreach (array('#', '?') as $symbol) {
  598. list($rawpath) = explode($symbol, $rawpath, 2);
  599. }
  600. if ($rawpath) {
  601. throw new WindException('Unable to resolve the request!', 404);
  602. }
  603. }
  604. private function _getDefault() {
  605. return array('m' => $this->default_m, 'c' => 'index', 'a' => 'run');
  606. }
  607. }
  608. ?>