redirect.module 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674
  1. <?php
  2. /**
  3. * @defgroup redirect_api Redirection API
  4. * @{
  5. * Functions related to URL redirects.
  6. *
  7. * @} End of "defgroup redirect_api".
  8. */
  9. /**
  10. * Modules should return this value from hook_redirect_access() to allow access
  11. * to a redirect.
  12. */
  13. define('REDIRECT_ACCESS_ALLOW', 'allow');
  14. /**
  15. * Modules should return this value from hook_redirect_access() to deny access
  16. * to a redirect.
  17. */
  18. define('REDIRECT_ACCESS_DENY', 'deny');
  19. /**
  20. * Modules should return this value from hook_redirect_access() to not affect
  21. * redirect access.
  22. */
  23. define('REDIRECT_ACCESS_IGNORE', NULL);
  24. /**
  25. * Implements hook_entity_info().
  26. */
  27. function redirect_entity_info() {
  28. $info['redirect'] = array(
  29. 'label' => t('Redirect'),
  30. 'base table' => 'redirect',
  31. 'controller class' => 'RedirectController',
  32. 'entity keys' => array(
  33. 'id' => 'rid',
  34. 'bundle' => 'type',
  35. ),
  36. 'fieldable' => FALSE,
  37. 'uuid' => FALSE,
  38. 'redirect' => FALSE,
  39. );
  40. return $info;
  41. }
  42. /**
  43. * Implements hook_hook_info().
  44. */
  45. function redirect_hook_info() {
  46. $hooks = array(
  47. 'redirect_load',
  48. 'redirect_load_by_source_alter',
  49. 'redirect_access',
  50. 'redirect_prepare',
  51. 'redirect_validate',
  52. 'redirect_presave',
  53. 'redirect_insert',
  54. 'redirect_update',
  55. 'redirect_delete',
  56. 'redirect_alter',
  57. );
  58. return array_fill_keys($hooks, array('group' => 'redirect'));
  59. }
  60. /**
  61. * Implements hook_permission().
  62. */
  63. function redirect_permission() {
  64. $permissions['administer redirects'] = array(
  65. 'title' => t('Administer URL redirections'),
  66. );
  67. return $permissions;
  68. }
  69. /**
  70. * Implements hook_help().
  71. */
  72. function redirect_help($path, $arg) {
  73. $output = '';
  74. switch ($path) {
  75. case 'admin/config/search/redirect/404':
  76. $output = '<p>' . t('This page lists all paths that have resulted in 404 errors and do not yet have any redirects assigned to them.') . '</p>';
  77. break;
  78. case 'admin/reports/page-not-found':
  79. break;
  80. }
  81. return $output;
  82. }
  83. /**
  84. * Implements hook_menu().
  85. */
  86. function redirect_menu() {
  87. $items['admin/config/search/redirect'] = array(
  88. 'title' => 'URL redirects',
  89. 'description' => 'Redirect users from one URL to another.',
  90. 'page callback' => 'drupal_get_form',
  91. 'page arguments' => array('redirect_list_form'),
  92. 'access arguments' => array('administer redirects'),
  93. 'file' => 'redirect.admin.inc',
  94. );
  95. $items['admin/config/search/redirect/list'] = array(
  96. 'title' => 'List',
  97. 'type' => MENU_DEFAULT_LOCAL_TASK,
  98. 'weight' => -10,
  99. );
  100. $items['admin/config/search/redirect/add'] = array(
  101. 'title' => 'Add redirect',
  102. 'page callback' => 'drupal_get_form',
  103. 'page arguments' => array('redirect_edit_form'),
  104. 'access callback' => 'redirect_access',
  105. 'access arguments' => array('create', 'redirect'),
  106. 'file' => 'redirect.admin.inc',
  107. 'type' => MENU_LOCAL_ACTION,
  108. );
  109. $items['admin/config/search/redirect/edit/%redirect'] = array(
  110. 'title' => 'Edit redirect',
  111. 'page callback' => 'drupal_get_form',
  112. 'page arguments' => array('redirect_edit_form', 5),
  113. 'access callback' => 'redirect_access',
  114. 'access arguments' => array('update', 5),
  115. 'file' => 'redirect.admin.inc',
  116. );
  117. $items['admin/config/search/redirect/delete/%redirect'] = array(
  118. 'title' => 'Delete redirect',
  119. 'page callback' => 'drupal_get_form',
  120. 'page arguments' => array('redirect_delete_form', 5),
  121. 'access callback' => 'redirect_access',
  122. 'access arguments' => array('delete', 5),
  123. 'file' => 'redirect.admin.inc',
  124. );
  125. $items['admin/config/search/redirect/settings'] = array(
  126. 'title' => 'Settings',
  127. 'description' => 'Configure behavior for URL redirects.',
  128. 'page callback' => 'drupal_get_form',
  129. 'page arguments' => array('redirect_settings_form'),
  130. 'access arguments' => array('administer redirects'),
  131. 'file' => 'redirect.admin.inc',
  132. 'type' => MENU_LOCAL_TASK,
  133. 'weight' => 50,
  134. );
  135. // If the database logging module is enabled, add special 404 listing pages.
  136. if (module_exists('dblog')) {
  137. $items['admin/config/search/redirect/404'] = array(
  138. 'title' => 'Fix 404 pages',
  139. 'description' => 'Add redirects for 404 pages.',
  140. 'page callback' => 'redirect_404_list',
  141. 'access arguments' => array('administer redirects'),
  142. 'file' => 'redirect.admin.inc',
  143. 'type' => MENU_LOCAL_TASK,
  144. 'weight' => 20,
  145. );
  146. $items['admin/reports/page-not-found/redirect'] = array(
  147. 'title' => 'Fix 404 pages with URL redirects',
  148. 'page callback' => 'drupal_goto',
  149. 'page arguments' => array('admin/config/search/redirect/404'),
  150. 'access arguments' => array('administer redirects'),
  151. 'type' => MENU_LOCAL_ACTION,
  152. );
  153. }
  154. // Devel generate integration.
  155. if (module_exists('devel_generate')) {
  156. $items['admin/config/development/generate/redirects'] = array(
  157. 'title' => 'Generate redirects',
  158. 'description' => 'Generate a given number of redirects. Optionally delete current redirects.',
  159. 'page callback' => 'drupal_get_form',
  160. 'page arguments' => array('redirect_generate_form'),
  161. 'access arguments' => array('administer redirects'),
  162. 'file' => 'redirect.generate.inc',
  163. );
  164. $items['admin/config/search/redirect/generate'] = $items['admin/config/development/generate/redirects'];
  165. $items['admin/config/search/redirect/generate']['type'] = MENU_LOCAL_ACTION;
  166. }
  167. return $items;
  168. }
  169. function redirect_set_current_redirect($redirect) {
  170. $static = &drupal_static(__FUNCTION__);
  171. $static = $redirect;
  172. }
  173. function redirect_get_current_redirect() {
  174. $redirect = drupal_static('redirect_set_current_redirect', NULL);
  175. // If a redirect has not been set with redirect_set_current_redirect(), then
  176. // attempt to find a redirect matching the current path, query string, and
  177. // language code.
  178. if (!isset($redirect)) {
  179. $redirect = redirect_load_by_source(current_path(), $GLOBALS['language']->language, drupal_get_query_parameters());
  180. }
  181. // @todo Add an alter hook here?
  182. return $redirect;
  183. }
  184. /**
  185. * Implements hook_url_inbound_alter().
  186. */
  187. function redirect_url_inbound_alter(&$path, $original_path, $path_language) {
  188. // If the current path global does not exist, then drupal_get_path_alias()
  189. // will fail. This condition only happens when $path is the front page.
  190. // @todo Remove when http://drupal.org/node/1329914 is fixed in core.
  191. if (empty($_GET['q'])) {
  192. $_GET['q'] = variable_get('site_frontpage', 'node');
  193. return;
  194. }
  195. // If the inbound path has been changed, then attempt to find a redirect
  196. // matching the original path and save it for processing later in
  197. // redirect_init(). For example, if the Sub-pathauto module changes the path
  198. // 'foo/redirect' to 'node/1/redirect', and there is a redirect enabled for
  199. // the path 'foo/redirect', then redirect_init() would normally fail since it
  200. // would not find a match.
  201. if ($path != $original_path && $original_path == current_path()) {
  202. $current_langcode = !empty($path_language) ? $path_language : $GLOBALS['language']->language;
  203. $current_query = drupal_get_query_parameters();
  204. if ($redirect = redirect_load_by_source($original_path, $current_langcode, $current_query)) {
  205. redirect_set_current_redirect($redirect);
  206. }
  207. }
  208. // Redirect to canonical URLs.
  209. // Commented out per https://www.drupal.org/node/2048137.
  210. //if ($path && variable_get('redirect_canonical', 1)) {
  211. //$alias = drupal_get_path_alias($path, $path_language);
  212. //if ($alias != $path && $alias != $original_path) {
  213. //return redirect_redirect(array('redirect' => $alias, 'type' => 'global'));
  214. //}
  215. // Redirect from default entity paths to the proper entity path.
  216. //if ($path_entity = redirect_load_entity_from_path($path)) {
  217. // if ($uri = entity_uri($path_entity['entity_type'], $path_entity['entity'])) {
  218. // if ($path != $uri['path']) {
  219. // return redirect_redirect(array('redirect' => $uri['path'], 'redirect_options' => $uri['options'], 'type' => 'global'));
  220. // }
  221. // }
  222. //}
  223. //}
  224. }
  225. /**
  226. * Implements hook_entity_info_alter().
  227. */
  228. function redirect_entity_info_alter(&$info) {
  229. $default_paths = array(
  230. 'node' => 'node/%node',
  231. 'user' => 'user/%user',
  232. 'taxonomy_term' => 'taxonomy/term/%taxonomy_term',
  233. );
  234. foreach ($default_paths as $entity_type => $default_path) {
  235. if (isset($info[$entity_type]) && !isset($info[$entity_type]['default path'])) {
  236. $info[$entity_type]['default path'] = $default_path;
  237. }
  238. }
  239. // Disable support for some entity types that cause problems.
  240. $unsupported_entity_types = array(
  241. 'comment',
  242. 'media',
  243. );
  244. foreach ($unsupported_entity_types as $unsupported_entity_type) {
  245. if (isset($info[$unsupported_entity_type])) {
  246. $info[$unsupported_entity_type]['redirect'] = FALSE;
  247. }
  248. }
  249. }
  250. /**
  251. * Check if an entity type supports redirects.
  252. *
  253. * @param $entity_type
  254. * An entity type.
  255. *
  256. * @return
  257. * TRUE if the entity type has an uri callback and supports redirects, or
  258. * FALSE otherwise.
  259. */
  260. function redirect_entity_type_supports_redirects($entity_type) {
  261. $types = &drupal_static(__FUNCTION__);
  262. if (!isset($types)) {
  263. $types = array();
  264. foreach (entity_get_info() as $type => $entity_info) {
  265. $types[$type] = !empty($entity_info['uri callback']) && (!isset($entity_info['redirect']) || !empty($entity_info['redirect']));
  266. }
  267. }
  268. return isset($types[$entity_type]) ? $types[$entity_type] : FALSE;
  269. }
  270. /**
  271. * Implements hook_init().
  272. */
  273. function redirect_init() {
  274. if (!redirect_can_redirect()) {
  275. return;
  276. }
  277. // Fetch the current redirect.
  278. if ($redirect = redirect_get_current_redirect()) {
  279. redirect_redirect($redirect);
  280. }
  281. $redirect_global = FALSE;
  282. $request_uri = $original_uri = ltrim(request_uri(), '/');
  283. // Redirect from non-clean URLs to clean URLs.
  284. if (variable_get('redirect_global_clean', 1) && variable_get('clean_url', 0) && strpos($request_uri, '?q=') !== FALSE) {
  285. //$redirect_global = TRUE;
  286. //$request_uri = str_replace('?q=', '', $request_uri);
  287. }
  288. if (strpos($request_uri, 'index.php') !== FALSE) {
  289. //$redirect_global = TRUE;
  290. //$request_uri = str_replace('index.php', '', $request_uri);
  291. }
  292. //$request_uri = ltrim($request_uri, '/');
  293. //$parsed = parse_url($request_uri);
  294. if ($redirect_global && $request_uri != $original_uri) {
  295. redirect_redirect(array(/*'redirect' => $request_uri,*/ 'type' => 'global'));
  296. }
  297. }
  298. /**
  299. * Implements hook_cron().
  300. */
  301. function redirect_cron() {
  302. // Purge inactive self-managed redirects from the database.
  303. redirect_purge_inactive_redirects();
  304. }
  305. /**
  306. * Implements hook_exit().
  307. */
  308. function redirect_exit($destination = NULL) {
  309. // If the current page is being cached, track it.
  310. if (drupal_get_http_header('Location') && $rid = drupal_get_http_header('X-Redirect-ID')) {
  311. // Ensure the database is loaded. This is only the next bootstrap step
  312. // after DRUPAL_BOOTSTRAP_DATABASE
  313. drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE);
  314. db_update('redirect')
  315. ->fields(array('access' => REQUEST_TIME))
  316. ->expression('count', 'count + 1')
  317. ->condition('rid', $rid)
  318. ->execute();
  319. }
  320. }
  321. /**
  322. * Implements hook_entity_delete().
  323. */
  324. function redirect_entity_delete($entity, $entity_type) {
  325. if (redirect_entity_type_supports_redirects($entity_type)) {
  326. redirect_delete_by_entity_path($entity_type, $entity);
  327. }
  328. }
  329. /**
  330. * Implements hook_path_update().
  331. */
  332. function redirect_path_update(array $path) {
  333. if (!variable_get('redirect_auto_redirect', TRUE)) {
  334. return;
  335. }
  336. elseif (isset($path['redirect']) && !$path['redirect']) {
  337. return;
  338. }
  339. if (!empty($path['original']['pid']) && $path['original']['pid'] == $path['pid'] && $path['original']['alias'] != $path['alias']) {
  340. // Disable all redirects having the same source as this alias.
  341. redirect_disable_by_path($path['alias'], $path['language']);
  342. $redirect = new stdClass();
  343. redirect_object_prepare($redirect);
  344. $redirect->source = $path['original']['alias'];
  345. $redirect->redirect = $path['source'];
  346. $redirect->language = $path['original']['language'];
  347. // Check if the redirect exists before saving.
  348. $hash = redirect_hash($redirect);
  349. $existing = redirect_load_by_hash($hash);
  350. if (!$existing) {
  351. redirect_save($redirect);
  352. }
  353. // If the existing redirect is disabled, re-enable it.
  354. elseif (isset($existing->status) && $existing->status == 0) {
  355. $existing->status = 1;
  356. redirect_save($existing);
  357. }
  358. }
  359. }
  360. /**
  361. * Implements hook_path_insert().
  362. */
  363. function redirect_path_insert(array $path) {
  364. if (!empty($path['alias'])) {
  365. // Disable all redirects having the same source as this alias.
  366. redirect_disable_by_path($path['alias'], $path['language']);
  367. }
  368. }
  369. /**
  370. * Implements hook_path_delete().
  371. */
  372. function redirect_path_delete($path) {
  373. if (!variable_get('redirect_auto_redirect', TRUE)) {
  374. return;
  375. }
  376. elseif (isset($path['redirect']) && !$path['redirect']) {
  377. return;
  378. }
  379. elseif (empty($path)) {
  380. // @todo Remove this condition and allow $path to use an array type hint
  381. // when http://drupal.org/node/1025904 is fixed.
  382. return;
  383. }
  384. // Redirect from a deleted alias to the system path.
  385. //if (!redirect_load_by_source($path['alias'], $path['language'])) {
  386. // $redirect = new stdClass();
  387. // redirect_object_prepare($redirect);
  388. // $redirect->source = $path['alias'];
  389. // $redirect->redirect = $path['source'];
  390. // $redirect->language = $path['language'];
  391. // redirect_save($redirect);
  392. //}
  393. }
  394. /**
  395. * Implements hook_views_api().
  396. */
  397. function redirect_views_api() {
  398. return array(
  399. 'api' => 2,
  400. 'path' => drupal_get_path('module', 'redirect') . '/views',
  401. );
  402. }
  403. /**
  404. * Implements hook_page_build().
  405. *
  406. * Adds an action on 404 pages to create a redirect.
  407. */
  408. function redirect_page_build(&$page) {
  409. if (redirect_is_current_page_404() && user_access('administer redirects')) {
  410. if (!isset($page['content']['system_main']['actions'])) {
  411. $page['content']['system_main']['actions'] = array(
  412. '#theme' => 'links',
  413. '#links' => array(),
  414. '#attributes' => array('class' => array('action-links')),
  415. '#weight' => -100,
  416. );
  417. }
  418. // We cannot simply use current_path() because if a 404 path is set, then
  419. // that value overrides whatever is in $_GET['q']. The
  420. // drupal_deliver_html_page() function thankfully puts the original current
  421. // path into $_GET['destination'].
  422. $destination = drupal_get_destination();
  423. $page['content']['system_main']['actions']['#links']['add_redirect'] = array(
  424. 'title' => t('Add URL redirect from this page to another location'),
  425. 'href' => 'admin/config/search/redirect/add',
  426. 'query' => array('source' => $destination['destination']) + drupal_get_destination(),
  427. );
  428. }
  429. }
  430. /**
  431. * Implements hook_form_FORM_ID_alter().
  432. *
  433. * Adds support for creating redirects if a node URL alias is changed.
  434. */
  435. function redirect_form_node_form_alter(&$form, $form_state) {
  436. if (!empty($form['path']['pid']['#value']) && !isset($form['path']['original'])) {
  437. $form['path']['original'] = array('#type' => 'value', '#value' => path_load($form['path']['pid']['#value']));
  438. }
  439. }
  440. /**
  441. * Implements hook_form_FORM_ID_alter().
  442. *
  443. * Adds support for creating redirects if a taxonomy term URL alias is changed.
  444. */
  445. function redirect_form_taxonomy_form_term_alter(&$form, $form_state) {
  446. if (!empty($form['path']['pid']['#value']) && !isset($form['path']['original'])) {
  447. $form['path']['original'] = array('#type' => 'value', '#value' => path_load($form['path']['pid']['#value']));
  448. }
  449. }
  450. /**
  451. * Implements hook_form_FORM_ID_alter().
  452. *
  453. * Adds support for creating redirects if an URL alias is changed.
  454. */
  455. function redirect_form_path_admin_form_alter(&$form, $form_state) {
  456. if (!empty($form['pid']['#value']) && !isset($form['original'])) {
  457. $form['original'] = array('#type' => 'value', '#value' => path_load($form['pid']['#value']));
  458. }
  459. }
  460. /**
  461. * Load an URL redirect from the database.
  462. *
  463. * @param $rid
  464. * The URL redirect ID.
  465. * @param $reset
  466. * Whether to reset the redirect_load_multiple cache.
  467. *
  468. * @return
  469. * An URL redirect object, or FALSE if loading failed.
  470. *
  471. * @ingroup redirect_api
  472. */
  473. function redirect_load($rid, $reset = FALSE) {
  474. $redirects = entity_load('redirect', array($rid), array(), $reset);
  475. return !empty($redirects) ? reset($redirects) : FALSE;
  476. }
  477. /**
  478. * Load an URL redirect from the database by {redirect}.hash.
  479. *
  480. * @param $hash
  481. * The hash of the URL redirect.
  482. * @param $reset
  483. * Whether to reset the redirect_load_multiple cache.
  484. *
  485. * @return
  486. * An URL redirect object, or FALSE if loading failed.
  487. *
  488. * @ingroup redirect_api
  489. */
  490. function redirect_load_by_hash($hash, $reset = FALSE) {
  491. $redirects = entity_load('redirect', FALSE, array('hash' => $hash), $reset);
  492. return !empty($redirects) ? reset($redirects) : FALSE;
  493. }
  494. /**
  495. * Fetches multiple URL redirect IDs from the database by {redirect}.source.
  496. *
  497. * @param $source
  498. * The source of the URL redirect.
  499. * @param $language
  500. * Language of the source URL.
  501. * @param $enabled_only
  502. * Boolean that indicates whether to only load enabled redirects.
  503. *
  504. * @return array
  505. * An indexed array of IDs, or an empty array if there is no result set.
  506. */
  507. function redirect_fetch_rids_by_path($source, $language, $enabled_only = FALSE) {
  508. // Run a case-insensitive query for matching RIDs first.
  509. $rid_query = db_select('redirect');
  510. $rid_query->addField('redirect', 'rid');
  511. // Prevent errors if redirect_update_7101() has not yet been run.
  512. if ($enabled_only && db_field_exists('redirect', 'status')) {
  513. $rid_query->condition('status', 1);
  514. }
  515. if ($source != variable_get('site_frontpage', 'node')) {
  516. $rid_query->condition('source', db_like($source), 'LIKE');
  517. }
  518. else {
  519. $source_condition = db_or();
  520. $source_condition->condition('source', db_like($source), 'LIKE');
  521. $source_condition->condition('source', '');
  522. $rid_query->condition($source_condition);
  523. }
  524. $rid_query->condition('language', array($language, LANGUAGE_NONE));
  525. $rids = $rid_query->execute()->fetchCol();
  526. return $rids;
  527. }
  528. /**
  529. * Load multiple URL redirects from the database by {redirect}.source.
  530. *
  531. * @param $source
  532. * The source of the URL redirect.
  533. * @param $language
  534. * Language of the source URL.
  535. * @param $query
  536. * Array of URL query parameters.
  537. * @param $enabled_only
  538. * Boolean that indicates whether to only load enabled redirects.
  539. *
  540. * @return
  541. * The first matched URL redirect object, or FALSE if there aren't any.
  542. *
  543. * @see redirect_load_multiple()
  544. * @see _redirect_uasort()
  545. * @see redirect_compare_array_recursive()
  546. *
  547. * @ingroup redirect_api
  548. */
  549. function redirect_load_by_source($source, $language = LANGUAGE_NONE, array $query = array(), $enabled_only = TRUE) {
  550. $rids = redirect_fetch_rids_by_path($source, $language, $enabled_only);
  551. if ($rids && $redirects = redirect_load_multiple($rids)) {
  552. // Narrow down the list of candidates.
  553. foreach ($redirects as $rid => $redirect) {
  554. if (!empty($redirect->source_options['query'])) {
  555. if (empty($query) || !redirect_compare_array_recursive($redirect->source_options['query'], $query)) {
  556. unset($redirects[$rid]);
  557. continue;
  558. }
  559. }
  560. // Add a case sensitive matches condition to be used in sorting.
  561. if ($source !== $redirect->source) {
  562. $redirects[$rid]->weight = 1;
  563. }
  564. }
  565. if (!empty($redirects)) {
  566. // Sort the redirects in the proper order.
  567. uasort($redirects, '_redirect_uasort');
  568. // Allow other modules to alter the redirect candidates before selecting the top one.
  569. $context = array('language' => $language, 'query' => $query);
  570. drupal_alter('redirect_load_by_source', $redirects, $source, $context);
  571. return !empty($redirects) ? reset($redirects) : FALSE;
  572. }
  573. }
  574. return FALSE;
  575. }
  576. /**
  577. * Load multiple URL redirects from the database.
  578. *
  579. * @param $rids
  580. * An array of redirect IDs.
  581. * @param $conditions
  582. * An array of conditions on the {redirect} table in the form 'field' =>
  583. * $value.
  584. * @param $reset
  585. * Whether to reset the redirect_load_multiple cache.
  586. *
  587. * @return
  588. * An array of URL redirect objects indexed by redirect IDs.
  589. *
  590. * @ingroup redirect_api
  591. */
  592. function redirect_load_multiple($rids = array(), array $conditions = array(), $reset = FALSE) {
  593. return entity_load('redirect', $rids, $conditions, $reset);
  594. }
  595. /**
  596. * Determine whether the current user may perform the given operation on the
  597. * specified redirect.
  598. *
  599. * @param $op
  600. * The operation to be performed on the redirect. Possible values are:
  601. * - "create"
  602. * - "update"
  603. * - "delete"
  604. * @param $redirect
  605. * The redirect object on which the operation is to be performed, or redirect
  606. * type (e.g. 'feedburner') for the "create" operation.
  607. * @param $account
  608. * Optional, a user object representing the user for whom the operation is to
  609. * be performed. Determines access for a user other than the current user.
  610. *
  611. * @return
  612. * TRUE if the operation may be performed, FALSE otherwise.
  613. */
  614. function redirect_access($op, $redirect, $account = NULL) {
  615. global $user;
  616. $rights = &drupal_static(__FUNCTION__, array());
  617. if (!$redirect || !in_array($op, array('create', 'update', 'delete'), TRUE)) {
  618. // If there was no redirect to check against, or the $op was not one of the
  619. // supported ones, we return access denied.
  620. return FALSE;
  621. }
  622. // If no user object is supplied, the access check is for the current user.
  623. if (empty($account)) {
  624. $account = $user;
  625. }
  626. $cid = isset($redirect->rid) ? $redirect->rid : $redirect;
  627. // Return cached value if access already checked for this redirect, user and op.
  628. if (isset($rights[$account->uid][$cid][$op])) {
  629. return $rights[$account->uid][$cid][$op];
  630. }
  631. // Administrators can access all redirects.
  632. if (user_access('administer redirects', $account)) {
  633. $rights[$account->uid][$cid][$op] = TRUE;
  634. return TRUE;
  635. }
  636. // We grant access to the redirect if both of the following conditions are met:
  637. // - No modules say to deny access.
  638. // - At least one module says to grant access.
  639. $access = module_invoke_all('redirect_access', $op, $redirect, $account);
  640. if (in_array(REDIRECT_ACCESS_DENY, $access, TRUE)) {
  641. $rights[$account->uid][$cid][$op] = FALSE;
  642. return FALSE;
  643. }
  644. elseif (in_array(REDIRECT_ACCESS_ALLOW, $access, TRUE)) {
  645. $rights[$account->uid][$cid][$op] = TRUE;
  646. return TRUE;
  647. }
  648. return FALSE;
  649. }
  650. /**
  651. * Validate a redirect.
  652. */
  653. function redirect_validate($redirect, $form, &$form_state) {
  654. $redirect = (object) $redirect;
  655. // check that there there are no redirect loops
  656. if (url($redirect->source) == url($redirect->redirect)) {
  657. form_set_error('redirect', t('You are attempting to redirect the page to itself. This will result in an infinite loop.'));
  658. }
  659. redirect_hash($redirect);
  660. if ($existing = redirect_load_by_hash($redirect->hash)) {
  661. if ($redirect->rid != $existing->rid) {
  662. form_set_error('source', t('A redirect already exists for the source path %source. Do you want to <a href="@edit-page">edit the existing redirect</a>?', array('%source' => redirect_url($redirect->source, $redirect->source_options), '@edit-page' => url('admin/config/search/redirect/edit/'. $existing->rid))));
  663. }
  664. }
  665. // Allow other modules to validate the redirect.
  666. foreach (module_implements('redirect_validate') as $module) {
  667. $function = $module . '_redirect_validate';
  668. $function($redirect, $form, $form_state);
  669. }
  670. }
  671. function redirect_object_prepare($redirect, $defaults = array()) {
  672. $defaults += array(
  673. 'rid' => NULL,
  674. 'type' => 'redirect',
  675. 'uid' => $GLOBALS['user']->uid,
  676. 'source_options' => array(),
  677. 'redirect_options' => array(),
  678. 'language' => LANGUAGE_NONE,
  679. 'status_code' => 0,
  680. 'count' => 0,
  681. 'access' => 0,
  682. 'hash' => '',
  683. 'status' => 1,
  684. );
  685. foreach ($defaults as $key => $default) {
  686. if (!isset($redirect->{$key})) {
  687. $redirect->{$key} = $default;
  688. }
  689. }
  690. module_invoke_all('redirect_prepare', $redirect);
  691. }
  692. /**
  693. * Save an URL redirect.
  694. *
  695. * @param $redirect
  696. * The URL redirect object to be saved. If $redirect->rid is omitted (or
  697. * $redirect->is_new is TRUE), a new redirect will be added.
  698. *
  699. * @ingroup redirect_api
  700. */
  701. function redirect_save($redirect) {
  702. $transaction = db_transaction();
  703. try {
  704. if (!empty($redirect->rid) && !isset($redirect->original)) {
  705. $redirect->original = entity_load_unchanged('redirect', $redirect->rid);
  706. }
  707. // Determine if we will be inserting a new node.
  708. if (!isset($redirect->is_new)) {
  709. $redirect->is_new = empty($redirect->rid);
  710. }
  711. // The changed timestamp is always updated for bookkeeping purposes.
  712. //$redirect->changed = time();
  713. redirect_hash($redirect);
  714. if ($redirect->is_new || $redirect->hash != $redirect->original->hash) {
  715. // Only new or changed redirects reset the last used value.
  716. $redirect->count = 0;
  717. $redirect->access = 0;
  718. }
  719. // Allow other modules to alter the redirect before saving.
  720. module_invoke_all('redirect_presave', $redirect);
  721. module_invoke_all('entity_presave', $redirect, 'redirect');
  722. // Save the redirect to the database and invoke the post-save hooks.
  723. if ($redirect->is_new) {
  724. drupal_write_record('redirect', $redirect);
  725. module_invoke_all('redirect_insert', $redirect);
  726. module_invoke_all('entity_insert', $redirect, 'redirect');
  727. }
  728. else {
  729. drupal_write_record('redirect', $redirect, array('rid'));
  730. module_invoke_all('redirect_update', $redirect);
  731. module_invoke_all('entity_update', $redirect, 'redirect');
  732. }
  733. // Clear internal properties.
  734. unset($redirect->is_new);
  735. unset($redirect->original);
  736. // Clear the static loading cache.
  737. entity_get_controller('redirect')->resetCache(array($redirect->rid));
  738. // Ignore slave server temporarily to give time for the
  739. // saved node to be propagated to the slave.
  740. db_ignore_slave();
  741. }
  742. catch (Exception $e) {
  743. $transaction->rollback();
  744. watchdog_exception('redirect', $e);
  745. throw $e;
  746. }
  747. }
  748. /**
  749. * Implements hook_redirect_insert().
  750. */
  751. function redirect_redirect_insert($redirect) {
  752. redirect_page_cache_clear($redirect);
  753. }
  754. /**
  755. * Implements hook_redirect_update().
  756. */
  757. function redirect_redirect_update($redirect) {
  758. redirect_page_cache_clear($redirect);
  759. // Clear the page cache for the original redirect as well.
  760. if (!empty($redirect->original) && $redirect->original->source != $redirect->source) {
  761. redirect_page_cache_clear($redirect->original);
  762. }
  763. }
  764. /**
  765. * Implements hook_redirect_delete().
  766. */
  767. function redirect_redirect_delete($redirect) {
  768. redirect_page_cache_clear($redirect);
  769. }
  770. /**
  771. * Delete a single URL redirect.
  772. *
  773. * @param $rid
  774. * The ID of the redirect to delete.
  775. *
  776. * @ingroup redirect_api
  777. */
  778. function redirect_delete($rid) {
  779. return redirect_delete_multiple(array($rid));
  780. }
  781. /**
  782. * Delete any redirects associated with a path or any of its sub-paths.
  783. *
  784. * Given a source like 'node/1' this function will delete any redirects that
  785. * have that specific source or any sources that match 'node/1/%'.
  786. *
  787. * @param $path
  788. * An string with an internal Drupal path.
  789. *
  790. * @ingroup redirect_api
  791. */
  792. function redirect_delete_by_path($path) {
  793. $query = db_select('redirect');
  794. $query->addField('redirect', 'rid');
  795. $query_or = db_or();
  796. $query_or->condition('source', db_like($path), 'LIKE');
  797. $query_or->condition('source', db_like($path . '/') . '%', 'LIKE');
  798. $query_or->condition('redirect', db_like($path), 'LIKE');
  799. $query_or->condition('redirect', db_like($path . '/') . '%', 'LIKE');
  800. $query->condition($query_or);
  801. $rids = $query->execute()->fetchCol();
  802. if ($rids) {
  803. return redirect_delete_multiple($rids);
  804. }
  805. }
  806. /**
  807. * Disable any redirects associated with a path.
  808. *
  809. * Given a source like 'node/1' this function will delete any redirects that
  810. * have that specific source.
  811. *
  812. * @param $path
  813. * An string with an internal Drupal path.
  814. *
  815. * @param @langauge
  816. * The langcode of the path.
  817. *
  818. * @ingroup redirect_api
  819. */
  820. function redirect_disable_by_path($path, $language) {
  821. $rids = redirect_fetch_rids_by_path($path, $language, FALSE);
  822. if ($rids) {
  823. return redirect_change_status_multiple($rids, 0);
  824. }
  825. }
  826. /**
  827. * Delete an entity URL alias and any of its sub-paths.
  828. *
  829. * This function also checks to see if the default entity URI is different from
  830. * the current entity URI and will delete any of the default aliases.
  831. *
  832. * @param $entity_type
  833. * A string with the entity type.
  834. * @param $entity
  835. * An entity object.
  836. *
  837. * @ingroup redirect_api
  838. */
  839. function redirect_delete_by_entity_path($entity_type, $entity) {
  840. $uri = entity_uri($entity_type, $entity);
  841. if (!empty($uri['path'])) {
  842. redirect_delete_by_path($uri['path']);
  843. }
  844. //$info = entity_get_info($entity_type);
  845. //if (isset($info['default path'])) {
  846. // list($id) = entity_extract_ids($entity_type, $entity);
  847. // $default_path = str_replace('[id]', $id, $info['default path']);
  848. // if ($uri['path'] !== $default_path) {
  849. // redirect_delete_by_path($default_path);
  850. // }
  851. //}
  852. }
  853. /**
  854. * Change the status of multiple URL redirects.
  855. *
  856. * @param array $rids
  857. * An array of redirect IDs to disable.
  858. * @param int|string $status
  859. * The status to set the redirect to: either disabled (0) or enabled (1).
  860. *
  861. * @ingroup redirect_api
  862. */
  863. function redirect_change_status_multiple(array $rids, $status) {
  864. if ($status !== 0 && $status !== 1 && $status !== '0' && $status !== '1') {
  865. watchdog('Cannot change redirect status to %status', array('%status' => $status));
  866. drupal_set_message(t('Cannot change redirect status to %status', array('%status' => $status)));
  867. return;
  868. }
  869. if (!empty($rids)) {
  870. $redirects = redirect_load_multiple($rids, array(), TRUE);
  871. foreach ($redirects as $rid => $redirect) {
  872. if (isset($redirect->status) && $redirect->status == $status) {
  873. // We no-op if the redirect is not actually changing status.
  874. // So if a disabled redirect is disabled, neither redirect_save() is
  875. // triggered, nor do we log any message.
  876. continue;
  877. }
  878. $redirect->status = $status;
  879. redirect_save($redirect);
  880. if ($status) {
  881. $redirect_link = l($redirect->redirect, $redirect->redirect) . '=> ' . l($redirect->source, $redirect->source);
  882. watchdog('redirect', 'Enabled redirect: !redirect_source_link', array('!redirect_link' => $redirect_link));
  883. }
  884. else {
  885. $redirect_link = l($redirect->redirect, $redirect->redirect) . '=> ' . l($redirect->source, $redirect->source);
  886. watchdog('redirect', 'Disabled redirect: !redirect_source_link', array('!redirect_link' => $redirect_link));
  887. }
  888. }
  889. }
  890. }
  891. /**
  892. * Delete multiple URL redirects.
  893. *
  894. * @param $rids
  895. * An array of redirect IDs to delete.
  896. *
  897. * @ingroup redirect_api
  898. */
  899. function redirect_delete_multiple(array $rids) {
  900. $transaction = db_transaction();
  901. if (!empty($rids)) {
  902. $redirects = redirect_load_multiple($rids);
  903. try {
  904. // Let modules react to the individual redirects being deleted.
  905. foreach ($redirects as $rid => $redirect) {
  906. module_invoke_all('redirect_delete', $redirect);
  907. module_invoke_all('entity_delete', $redirect, 'redirect');
  908. }
  909. db_delete('redirect')
  910. ->condition('rid', $rids, 'IN')
  911. ->execute();
  912. }
  913. catch (Exception $e) {
  914. $transaction->rollback();
  915. watchdog_exception('redirect', $e);
  916. throw $e;
  917. }
  918. // Clear the redirect_load_multiple cache.
  919. entity_get_controller('redirect')->resetCache();
  920. }
  921. }
  922. /**
  923. * Purge inactive redirects from the database.
  924. *
  925. * @param $types
  926. * An array of redirect types to remove. Default is only the self-managed
  927. * 'redirect'. If not provided all redirect types will be eligible for
  928. * removal.
  929. * @param $interval
  930. * The number of seconds to subtract from the current time and used to
  931. * find the inactive redirects.
  932. *
  933. * @return
  934. * An array of redirect IDs that were deleted or FALSE if none were.
  935. */
  936. function redirect_purge_inactive_redirects(array $types = array('redirect'), $interval = NULL) {
  937. if (!isset($interval)) {
  938. $interval = variable_get('redirect_purge_inactive', 0);
  939. }
  940. if (!$interval) {
  941. return FALSE;
  942. }
  943. if (variable_get('redirect_page_cache', 0) && !variable_get('page_cache_invoke_hooks', TRUE)) {
  944. // If serving redirects from the page cache is enabled and hooks are not
  945. // executed during page caching, then we cannot track when a redirect is
  946. // used. Therefore, we cannot remove unused redirects.
  947. watchdog('redirect', 'Due to existing settings, could not track when a redirect is used, so could not remove unused redirects.');
  948. return FALSE;
  949. }
  950. $query = db_select('redirect');
  951. $query->addField('redirect', 'rid');
  952. if (!empty($types)) {
  953. $query->condition('type', $types);
  954. }
  955. $query->condition('access', REQUEST_TIME - $interval, '<');
  956. $query->addTag('redirect_purge');
  957. $rids = $query->execute()->fetchCol();
  958. if (count($rids)) {
  959. redirect_delete_multiple($rids);
  960. watchdog('redirect', format_plural(count($rids), 'Removed 1 inactive redirect from the database.', 'Removed @count inactive redirects from the database.'));
  961. return $rids;
  962. }
  963. }
  964. /**
  965. * Perform an URL redirect.
  966. *
  967. * @param $redirect
  968. * An optional URL redirect array.
  969. *
  970. * @ingroup redirect_api
  971. */
  972. function redirect_redirect($redirect = NULL) {
  973. if (!isset($redirect)) {
  974. $redirect = new stdClass();
  975. }
  976. redirect_object_prepare($redirect, array('redirect' => current_path(), 'type' => 'manual', 'callback' => 'redirect_goto', 'cache' => TRUE));
  977. if (empty($redirect->status_code)) {
  978. $redirect->status_code = variable_get('redirect_default_status_code', 301);
  979. }
  980. if (variable_get('redirect_passthrough_querystring', 1)) {
  981. // Preserve the current query parameters in the redirect.
  982. $redirect->redirect_options += array('query' => array());
  983. $redirect->redirect_options['query'] += drupal_get_query_parameters();
  984. }
  985. // Prevent the destination query parameter from overriding this redirect.
  986. //if (isset($_GET['destination'])) {
  987. // Simply unset the parameter since it has already been passed into
  988. // $options['query'] in the previous code.
  989. // unset($_GET['destination']);
  990. //}
  991. // Allow other modules to alter the redirect before passing to drupal_goto().
  992. drupal_alter('redirect', $redirect);
  993. // Continue if the redirect has not been disabled by hook_redirect_alter().
  994. if (isset($redirect->redirect) && isset($redirect->callback) && $redirect->redirect !== FALSE && function_exists($redirect->callback)) {
  995. // Prevent circular redirects.
  996. if ($GLOBALS['base_root'] . request_uri() == url($redirect->redirect, array('absolute' => TRUE) + $redirect->redirect_options)) {
  997. return FALSE;
  998. }
  999. // Perform the actual redirect.
  1000. $callback = $redirect->callback;
  1001. $callback($redirect);
  1002. }
  1003. }
  1004. /**
  1005. * Redirect callback; perform an URL redirect.
  1006. */
  1007. function redirect_goto($redirect) {
  1008. $redirect->redirect_options['absolute'] = TRUE;
  1009. $url = url($redirect->redirect, $redirect->redirect_options);
  1010. drupal_add_http_header('Location', $url);
  1011. drupal_add_http_header('Status', redirect_status_code_options($redirect->status_code));
  1012. if (!empty($redirect->rid)) {
  1013. // Add a custom header for the redirect ID so when the redirect is served
  1014. // from the page cache, we can track it.
  1015. drupal_add_http_header('X-Redirect-ID', $redirect->rid);
  1016. }
  1017. if (!variable_get('redirect_page_cache', 0) || !variable_get('cache', 0) || !drupal_page_is_cacheable() || empty($redirect->cache)) {
  1018. drupal_exit($url);
  1019. }
  1020. // @see drupal_exit()
  1021. if (drupal_get_bootstrap_phase() == DRUPAL_BOOTSTRAP_FULL) {
  1022. if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') {
  1023. module_invoke_all('exit', $url);
  1024. }
  1025. drupal_session_commit();
  1026. if (variable_get('cache', 0)) {
  1027. // We must output something to allow the request to be cached.
  1028. echo ' ';
  1029. if ($cache = drupal_page_set_cache()) {
  1030. // When caching this redirect for the first time we still need to ensure
  1031. // that the correct cache headers are sent.
  1032. // @see drupal_page_footer()
  1033. drupal_serve_page_from_cache($cache);
  1034. }
  1035. }
  1036. }
  1037. exit;
  1038. }
  1039. function redirect_hash($redirect) {
  1040. $hash = array(
  1041. 'source' => $redirect->source,
  1042. 'language' => $redirect->language,
  1043. );
  1044. if (!empty($redirect->source_options['query'])) {
  1045. $hash['source_query'] = $redirect->source_options['query'];
  1046. }
  1047. drupal_alter('redirect_hash', $hash, $redirect);
  1048. redirect_sort_recursive($hash, 'ksort');
  1049. $redirect->hash = drupal_hash_base64(serialize($hash));
  1050. return $redirect->hash;
  1051. }
  1052. /**
  1053. * Clear a page from the page cache.
  1054. */
  1055. function redirect_page_cache_clear($redirect = NULL) {
  1056. if (!variable_get('redirect_page_cache', 0)) {
  1057. return;
  1058. }
  1059. if (isset($redirect)) {
  1060. $path = url($redirect->source, array('absolute' => TRUE));
  1061. // Use a wildcard to catch paths with query strings.
  1062. cache_clear_all($path, 'cache_page', TRUE);
  1063. }
  1064. else {
  1065. // Clear the entire page cache.
  1066. cache_clear_all('*', 'cache_page', TRUE);
  1067. }
  1068. }
  1069. /**
  1070. * Given a path determine if it is an entity default path.
  1071. *
  1072. * @param $path
  1073. * The internal path. The id of the entity should be in the string as '[id]'.
  1074. * @return
  1075. * An array with the entity type and the loaded entity object.
  1076. */
  1077. function redirect_load_entity_from_path($path) {
  1078. $entity_paths = &drupal_static(__FUNCTION__);
  1079. if (!isset($entity_paths)) {
  1080. $entity_paths = array();
  1081. foreach (entity_get_info() as $entity_type => $entity_info) {
  1082. if (isset($entity_info['default path'])) {
  1083. $default_path = $entity_info['default path'];
  1084. $default_path = preg_quote($default_path, '/');
  1085. $default_path = str_replace(preg_quote('%' . $entity_type, '/'), '(\d+)', $default_path);
  1086. $entity_paths[$entity_type] = $default_path;
  1087. }
  1088. }
  1089. }
  1090. foreach ($entity_paths as $entity_type => $default_path) {
  1091. if (preg_match("/^{$default_path}$/", $path, $matches)) {
  1092. if ($entity = entity_load($entity_type, array($matches[1]))) {
  1093. return array('entity_type' => $entity_type, 'entity' => reset($entity));
  1094. }
  1095. break;
  1096. }
  1097. }
  1098. }
  1099. /**
  1100. * Check the ability to perform redirects with the current request context.
  1101. *
  1102. * This function checks the following conditions:
  1103. * - If the PHP entry point is the root index.php file.
  1104. * - If PHP is not running as CLI.
  1105. * - If the site is not offline or in install/update mode.
  1106. * - If the curerent page is not an admin page (check can be disabled).
  1107. * - If the current request does not have any POST data since a redirect
  1108. * may interrupt form submission.
  1109. *
  1110. * @return
  1111. * TRUE if redirections can be performed, or FALSE otherwise.
  1112. */
  1113. function redirect_can_redirect() {
  1114. $can_redirect = &drupal_static(__FUNCTION__);
  1115. if (!isset($can_redirect)) {
  1116. $path = current_path();
  1117. $can_redirect = TRUE;
  1118. if (!preg_match('/index\.php$/', $_SERVER['SCRIPT_NAME'])) {
  1119. // Do not redirect if the root script is not /index.php.
  1120. $can_redirect = FALSE;
  1121. }
  1122. elseif (!empty($_POST)) {
  1123. // Do not redirect if this is a post request with data.
  1124. $can_redirect = FALSE;
  1125. }
  1126. elseif (drupal_is_cli()) {
  1127. // If this is a command line request (Drush, etc), skip processing.
  1128. $can_redirect = FALSE;
  1129. }
  1130. elseif ((variable_get('maintenance_mode', 0) || defined('MAINTENANCE_MODE')) && !user_access('access site in maintenance mode')) {
  1131. // Do not redirect in offline or maintenance mode.
  1132. $can_redirect = FALSE;
  1133. }
  1134. elseif (!variable_get('redirect_global_admin_paths', 0) && path_is_admin($path)) {
  1135. // Do not redirect on admin paths.
  1136. $can_redirect = FALSE;
  1137. }
  1138. }
  1139. return $can_redirect;
  1140. }
  1141. /**
  1142. * Compare that all values and associations in one array match another array.
  1143. *
  1144. * We cannot use array_diff_assoc() here because we need to be recursive.
  1145. *
  1146. * @param $match
  1147. * The array that has the values.
  1148. * @param $haystack
  1149. * The array that will be searched for values.
  1150. * @return
  1151. * TRUE if all the elements of $match were found in $haystack, or FALSE
  1152. * otherwise.
  1153. */
  1154. function redirect_compare_array_recursive($match, $haystack) {
  1155. foreach ($match as $key => $value) {
  1156. if (!array_key_exists($key, $haystack)) {
  1157. return FALSE;
  1158. }
  1159. elseif (is_array($value)) {
  1160. if (!is_array($haystack[$key])) {
  1161. return FALSE;
  1162. }
  1163. elseif (!redirect_compare_array_recursive($value, $haystack[$key])) {
  1164. return FALSE;
  1165. }
  1166. }
  1167. elseif ($value != $haystack[$key]) {
  1168. return FALSE;
  1169. }
  1170. }
  1171. return TRUE;
  1172. }
  1173. /**
  1174. * Sort an array recusively.
  1175. *
  1176. * @param $array
  1177. * The array to sort, by reference.
  1178. * @param $callback
  1179. * The sorting callback to use (e.g. 'sort', 'ksort', 'asort').
  1180. *
  1181. * @return
  1182. * TRUE on success or FALSE on failure.
  1183. */
  1184. function redirect_sort_recursive(&$array, $callback = 'sort') {
  1185. $result = $callback($array);
  1186. foreach ($array as $key => $value) {
  1187. if (is_array($value)) {
  1188. $result &= redirect_sort_recursive($array[$key], $callback);
  1189. }
  1190. }
  1191. return $result;
  1192. }
  1193. /**
  1194. * Load a language object by its language code.
  1195. *
  1196. * @todo Remove when http://drupal.org/node/660736 is fixed in Drupal core.
  1197. *
  1198. * @param $language
  1199. * A language code. If not provided the default language will be returned.
  1200. * @return
  1201. * A language object.
  1202. */
  1203. function redirect_language_load($language = LANGUAGE_NONE) {
  1204. $languages = &drupal_static(__FUNCTION__);
  1205. if (!isset($languages)) {
  1206. $languages = language_list();
  1207. $languages[LANGUAGE_NONE] = NULL;
  1208. }
  1209. return isset($languages[$language]) ? $languages[$language] : NULL;
  1210. }
  1211. /**
  1212. * Build the URL of a redirect for display purposes only.
  1213. */
  1214. function redirect_url($path, array $options = array(), $clean_url = NULL) {
  1215. if (!isset($clean_url)) {
  1216. $clean_url = variable_get('clean_url', 0);
  1217. }
  1218. if ($path == '') {
  1219. $path = '<front>';
  1220. }
  1221. if (!isset($options['alter']) || !empty($options['alter'])) {
  1222. drupal_alter('redirect_url', $path, $options);
  1223. }
  1224. // The base_url might be rewritten from the language rewrite in domain mode.
  1225. if (!isset($options['base_url'])) {
  1226. if (isset($options['https']) && variable_get('https', FALSE)) {
  1227. if ($options['https'] === TRUE) {
  1228. $options['base_url'] = $GLOBALS['base_secure_url'];
  1229. $options['absolute'] = TRUE;
  1230. }
  1231. elseif ($options['https'] === FALSE) {
  1232. $options['base_url'] = $GLOBALS['base_insecure_url'];
  1233. $options['absolute'] = TRUE;
  1234. }
  1235. }
  1236. else {
  1237. $options['base_url'] = $GLOBALS['base_url'];
  1238. }
  1239. }
  1240. if (empty($options['absolute']) || url_is_external($path)) {
  1241. $url = $path;
  1242. }
  1243. else {
  1244. $url = $options['base_url'] . base_path() . $path;
  1245. }
  1246. if (isset($options['query'])) {
  1247. $url .= $clean_url ? '?' : '&';
  1248. $url .= drupal_http_build_query($options['query']);
  1249. }
  1250. if (isset($options['fragment'])) {
  1251. $url .= '#' . $options['fragment'];
  1252. }
  1253. return $url;
  1254. }
  1255. function redirect_variables() {
  1256. return array(
  1257. 'redirect_default_status_code' => 301,
  1258. 'redirect_auto_redirect' => TRUE,
  1259. 'redirect_warning' => FALSE,
  1260. 'redirect_passthrough_querystring' => 1,
  1261. 'redirect_page_cache' => 0,
  1262. 'redirect_purge_inactive' => 0,
  1263. 'redirect_global_home' => 1,
  1264. 'redirect_global_clean' => 1,
  1265. 'redirect_global_canonical' => 1,
  1266. 'redirect_global_admin_paths' => 0,
  1267. );
  1268. }
  1269. //function redirect_get_redirect_info() {
  1270. // $info = &drupal_static(__FUNCTION__);
  1271. //
  1272. // if (!isset($info)) {
  1273. // if ($cache = cache_get('redirect:info')) {
  1274. // $info = $cache->data;
  1275. // }
  1276. // else {
  1277. // $info = module_invoke_all('redirect_info');
  1278. // drupal_alter('redirect_info', $info);
  1279. // cache_set('redirect:info', $info);
  1280. // }
  1281. // }
  1282. //
  1283. // return $info;
  1284. //}
  1285. function redirect_parse_url($url) {
  1286. $original_url = $url;
  1287. $url = trim($url, " \t\n\r\0\x0B\/");
  1288. $parsed = parse_url($url);
  1289. if (isset($parsed['fragment'])) {
  1290. $url = substr($url, 0, -strlen($parsed['fragment']));
  1291. $url = trim($url, '#');
  1292. }
  1293. if (isset($parsed['query'])) {
  1294. $url = substr($url, 0, -strlen($parsed['query']));
  1295. $url = trim($url, '?&');
  1296. $parsed['query'] = drupal_get_query_array($parsed['query']);
  1297. }
  1298. // Convert absolute to relative.
  1299. if (isset($parsed['scheme']) && isset($parsed['host'])) {
  1300. $base_secure_url = rtrim($GLOBALS['base_secure_url'] . base_path(), '/');
  1301. $base_insecure_url = rtrim($GLOBALS['base_insecure_url'] . base_path(), '/');
  1302. if (strpos($url, $base_secure_url) === 0) {
  1303. $url = str_replace($base_secure_url, '', $url);
  1304. $parsed['https'] = TRUE;
  1305. }
  1306. elseif (strpos($url, $base_insecure_url) === 0) {
  1307. $url = str_replace($base_insecure_url, '', $url);
  1308. }
  1309. }
  1310. $url = trim($url, '/');
  1311. // Convert to frontpage paths.
  1312. if ($url == '<front>') {
  1313. $url = '';
  1314. }
  1315. //$parsed['url'] = http_build_query($url, HTTP_URL_STRIP_QUERY | HTTP_URL_STRIP_FRAGMENT);
  1316. $parsed['url'] = $url;
  1317. // Allow modules to alter the parsed URL.
  1318. drupal_alter('redirect_parse_url', $parsed, $original_url);
  1319. return $parsed;
  1320. }
  1321. function redirect_status_code_options($code = NULL) {
  1322. $codes = array(
  1323. 300 => t('300 Multiple Choices'),
  1324. 301 => t('301 Moved Permanently'),
  1325. 302 => t('302 Found'),
  1326. 303 => t('303 See Other'),
  1327. 304 => t('304 Not Modified'),
  1328. 305 => t('305 Use Proxy'),
  1329. 307 => t('307 Temporary Redirect'),
  1330. );
  1331. return isset($codes[$code]) ? $codes[$code] : $codes;
  1332. }
  1333. /**
  1334. * Returns if the current page request is a page not found (404 status error).
  1335. *
  1336. * Why the fuck do we have to do this? Why is there not an easier way???
  1337. *
  1338. * @return
  1339. * TRUE if the current page is a 404, or FALSE otherwise.
  1340. */
  1341. function redirect_is_current_page_404() {
  1342. return drupal_get_http_header('Status') == '404 Not Found';
  1343. }
  1344. /**
  1345. * uasort callback; Compare redirects based on language neutrality and rids.
  1346. */
  1347. function _redirect_uasort($a, $b) {
  1348. $a_weight = isset($a->weight) ? $a->weight : 0;
  1349. $b_weight = isset($b->weight) ? $b->weight : 0;
  1350. if ($a_weight != $b_weight) {
  1351. // First sort by weight (case sensitivity).
  1352. return $a_weight > $b_weight;
  1353. }
  1354. elseif ($a->language != $b->language) {
  1355. // Then sort by language specific over language neutral.
  1356. return $a->language == LANGUAGE_NONE;
  1357. }
  1358. elseif (!empty($a->source_options['query']) != !empty($b->source_options['query'])) {
  1359. // Then sort by redirects that do not have query strings over ones that do.
  1360. return empty($a->source_options['query']);
  1361. }
  1362. else {
  1363. // Lastly sort by the highest redirect ID.
  1364. return $a->rid < $b->rid;
  1365. }
  1366. }
  1367. /**
  1368. * Implements hook_form_FORM_ID_alter() on behalf of locale.module.
  1369. */
  1370. function locale_form_redirect_edit_form_alter(&$form, &$form_state) {
  1371. $form['language'] = array(
  1372. '#type' => 'select',
  1373. '#title' => t('Language'),
  1374. '#options' => array(LANGUAGE_NONE => t('All languages')) + locale_language_list('name'),
  1375. '#default_value' => $form['language']['#value'],
  1376. '#description' => t('A redirect set for a specific language will always be used when requesting this page in that language, and takes precedence over redirects set for <em>All languages</em>.'),
  1377. );
  1378. }
  1379. /**
  1380. * Implements hook_field_attach_form().
  1381. *
  1382. * @todo Investigate using hook_entity_load() to load all entity redirects.
  1383. * @todo Figure out how to support entity URIs that contain query strings.
  1384. */
  1385. function redirect_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) {
  1386. list($id) = entity_extract_ids($entity_type, $entity);
  1387. if (!empty($form['redirect']) || empty($id)) {
  1388. return;
  1389. }
  1390. // Check if this entity type supports redirects.
  1391. if (!redirect_entity_type_supports_redirects($entity_type)) {
  1392. return;
  1393. }
  1394. $uri = entity_uri($entity_type, $entity);
  1395. if (empty($uri['path'])) {
  1396. // If the entity has no source path, then we cannot lookup the existing
  1397. // redirects.
  1398. return;
  1399. }
  1400. $info = entity_get_info($entity_type);
  1401. $form['redirect'] = array(
  1402. '#type' => 'fieldset',
  1403. '#title' => t('URL redirects'),
  1404. '#collapsible' => TRUE,
  1405. '#collapsed' => TRUE,
  1406. '#access' => user_access('administer redirects'),
  1407. '#weight' => 30,
  1408. '#attributes' => array('class' => array('redirect-list')),
  1409. );
  1410. // Only support vertical tabs if there is a vertical tab element.
  1411. foreach (element_children($form) as $key) {
  1412. if (isset($form[$key]['#type']) && $form[$key]['#type'] == 'vertical_tabs') {
  1413. $form['redirect']['#group'] = $key;
  1414. $form['redirect']['#attached']['js']['vertical-tabs'] = drupal_get_path('module', 'redirect') . '/redirect.js';
  1415. }
  1416. }
  1417. $redirect = array(
  1418. 'redirect' => $uri['path'],
  1419. 'redirect_options' => array_diff_key($uri['options'], array('entity_type' => '', 'entity' => '')),
  1420. 'language' => $langcode,
  1421. );
  1422. $form['redirect']['actions'] = array(
  1423. '#theme' => 'links',
  1424. '#links' => array(),
  1425. '#attributes' => array('class' => array('action-links')),
  1426. );
  1427. if (redirect_access('create', 'redirect')) {
  1428. $form['redirect']['actions']['#links']['add'] = array(
  1429. 'title' => t('Add URL redirect to this @entitytype', array('@entitytype' => drupal_strtolower($info['label']))),
  1430. 'href' => 'admin/config/search/redirect/add',
  1431. 'query' => array_filter($redirect) + drupal_get_destination(),
  1432. );
  1433. }
  1434. // We don't have to put our include in $form_state['build_info']['files']
  1435. // since the build array will already be cached.
  1436. module_load_include('inc', 'redirect', 'redirect.admin');
  1437. $redirects = redirect_load_multiple(FALSE, array('redirect' => $uri['path']));
  1438. $header = array('source', 'status', 'status_code', 'language', 'count', 'access', 'operations');
  1439. $form['redirect'] += redirect_list_table($redirects, $header);
  1440. }
  1441. /**
  1442. * Implements hook_field_extra_fields().
  1443. */
  1444. function redirect_field_extra_fields() {
  1445. $entity_info = entity_get_info();
  1446. foreach (array_keys($entity_info) as $entity_type) {
  1447. if (!redirect_entity_type_supports_redirects($entity_type)) {
  1448. // Redirect is explicitly disabled for this entity type.
  1449. continue;
  1450. }
  1451. foreach (array_keys($entity_info[$entity_type]['bundles']) as $bundle) {
  1452. if (!isset($entity_info[$entity_type]['bundles'][$bundle]['uri callback']) && !isset($entity_info[$entity_type]['uri callback'])) {
  1453. // The bundle or base entity must have an URI callback defined otherwise
  1454. // we cannot use the entity_uri() function to lookup the entity's source
  1455. // path.
  1456. continue;
  1457. }
  1458. $info[$entity_type][$bundle]['form']['redirect'] = array(
  1459. 'label' => t('URL redirects'),
  1460. 'description' => t('Redirect module form elements'),
  1461. 'weight' => 30,
  1462. );
  1463. }
  1464. }
  1465. return $info;
  1466. }
  1467. /**
  1468. * Fetch an array of redirect bulk operations.
  1469. *
  1470. * @see hook_redirect_operations()
  1471. * @see hook_redirect_operations_alter()
  1472. */
  1473. function redirect_get_redirect_operations() {
  1474. $operations = &drupal_static(__FUNCTION__);
  1475. if (!isset($operations)) {
  1476. $operations = module_invoke_all('redirect_operations');
  1477. drupal_alter('redirect_operations', $operations);
  1478. }
  1479. return $operations;
  1480. }
  1481. /**
  1482. * Implements hook_redirect_operations().
  1483. */
  1484. function redirect_redirect_operations() {
  1485. $operations['delete'] = array(
  1486. 'action' => t('Delete'),
  1487. 'action_past' => t('Deleted'),
  1488. 'callback' => 'redirect_delete_multiple',
  1489. 'confirm' => TRUE,
  1490. );
  1491. $operations['disable'] = array(
  1492. 'action' => t('Disable'),
  1493. 'action_past' => t('Disabled'),
  1494. 'callback' => 'redirect_change_status_multiple',
  1495. 'callback arguments' => array(0),
  1496. 'confirm' => TRUE,
  1497. );
  1498. $operations['enable'] = array(
  1499. 'action' => t('Enable'),
  1500. 'action_past' => t('Enabled'),
  1501. 'callback' => 'redirect_change_status_multiple',
  1502. 'callback arguments' => array(1),
  1503. 'confirm' => TRUE,
  1504. );
  1505. return $operations;
  1506. }