redirect.admin.inc 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922
  1. <?php
  2. /**
  3. * @file
  4. * Administrative page callbacks for the redirect module.
  5. */
  6. function redirect_list_form($form, &$form_state) {
  7. $form['#operations'] = redirect_get_redirect_operations();
  8. if (isset($form_state['values']['operation']) && empty($form_state['values']['confirm'])) {
  9. return redirect_list_form_operations_confirm_form($form, $form_state, $form_state['values']['operation'], array_filter($form_state['values']['rids']));
  10. }
  11. $destination = drupal_get_destination();
  12. $default_status_code = variable_get('redirect_default_status_code', 301);
  13. // Set up the header.
  14. $header = array(
  15. 'source' => array('data' => t('From'), 'field' => 'source', 'sort' => 'asc'),
  16. 'redirect' => array('data' => t('To'), 'field' => 'redirect'),
  17. 'status' => array('data' => t('Status'), 'field' => 'status'),
  18. 'status_code' => array('data' => t('Type'), 'field' => 'status_code'),
  19. 'language' => array('data' => t('Language'), 'field' => 'language'),
  20. 'count' => array('data' => t('Count'), 'field' => 'count'),
  21. 'access' => array('data' => t('Last accessed'), 'field' => 'access'),
  22. 'operations' => array('data' => t('Operations')),
  23. );
  24. // Do not include the language column if locale is disabled.
  25. if (!module_exists('locale')) {
  26. unset($header['language']);
  27. }
  28. // Get filter keys and add the filter form.
  29. $keys = func_get_args();
  30. $keys = array_splice($keys, 2); // Offset the $form and $form_state parameters.
  31. $keys = implode('/', $keys);
  32. $form['redirect_list_filter_form'] = redirect_list_filter_form($keys);
  33. // Build the 'Update options' form.
  34. $form['operations'] = array(
  35. '#type' => 'fieldset',
  36. '#title' => t('Update options'),
  37. '#prefix' => '<div class="container-inline">',
  38. '#suffix' => '</div>',
  39. '#attributes' => array(
  40. 'class' => array('redirect-list-operations'),
  41. ),
  42. );
  43. $operations = array();
  44. foreach ($form['#operations'] as $key => $operation) {
  45. $operations[$key] = $operation['action'];
  46. }
  47. $form['operations']['operation'] = array(
  48. '#type' => 'select',
  49. '#options' => $operations,
  50. '#default_value' => 'delete',
  51. );
  52. $form['operations']['submit'] = array(
  53. '#type' => 'submit',
  54. '#value' => t('Update'),
  55. '#validate' => array('redirect_list_form_operations_validate'),
  56. '#submit' => array('redirect_list_form_operations_submit'),
  57. );
  58. // Building the SQL query and load the redirects.
  59. $query = db_select('redirect', 'r')->extend('TableSort')->extend('PagerDefault');
  60. $query->addField('r', 'rid');
  61. $query->condition('r.type', 'redirect');
  62. $query->orderByHeader($header);
  63. $query->limit(50);
  64. $query->addTag('redirect_list');
  65. $query->addTag('redirect_access');
  66. redirect_build_filter_query($query, array('source', 'redirect'), $keys);
  67. $rids = $query->execute()->fetchCol();
  68. $redirects = redirect_load_multiple($rids);
  69. $rows = array();
  70. foreach ($redirects as $rid => $redirect) {
  71. $row = array();
  72. $redirect->source_options = array_merge($redirect->source_options, array('alias' => TRUE, 'language' => redirect_language_load($redirect->language)));
  73. $source_url = redirect_url($redirect->source, $redirect->source_options);
  74. $redirect_url = redirect_url($redirect->redirect, array_merge($redirect->redirect_options, array('alias' => TRUE)));
  75. drupal_alter('redirect_url', $redirect->source, $redirect->source_options);
  76. drupal_alter('redirect_url', $redirect->redirect, $redirect->redirect_options);
  77. $row['source'] = l($source_url, $redirect->source, $redirect->source_options);
  78. $row['redirect'] = l($redirect_url, $redirect->redirect, $redirect->redirect_options);
  79. $row['status'] = $redirect->status ? t('Enabled') : t('Disabled');
  80. $row['status_code'] = $redirect->status_code ? $redirect->status_code : t('Default (@default)', array('@default' => $default_status_code));
  81. $row['language'] = module_invoke('locale', 'language_name', $redirect->language);
  82. $row['count'] = $redirect->count;
  83. if ($redirect->access) {
  84. $row['access'] = array(
  85. 'data' => t('!interval ago', array('!interval' => format_interval(REQUEST_TIME - $redirect->access))),
  86. 'title' => t('Last accessed on @date', array('@date' => format_date($redirect->access))),
  87. );
  88. }
  89. else {
  90. $row['access'] = t('Never');
  91. }
  92. // Mark redirects that override existing paths with a warning in the table.
  93. if (drupal_valid_path($redirect->source)) {
  94. $row['#attributes']['class'][] = 'warning';
  95. $row['#attributes']['title'] = t('This redirect overrides an existing internal path.');
  96. }
  97. $operations = array();
  98. if (redirect_access('update', $redirect)) {
  99. $operations['edit'] = array(
  100. 'title' => t('Edit'),
  101. 'href' => 'admin/config/search/redirect/edit/' . $rid,
  102. 'query' => $destination,
  103. );
  104. }
  105. if (redirect_access('delete', $redirect)) {
  106. $operations['delete'] = array(
  107. 'title' => t('Delete'),
  108. 'href' => 'admin/config/search/redirect/delete/' . $rid,
  109. 'query' => $destination,
  110. );
  111. }
  112. $row['operations'] = array(
  113. 'data' => array(
  114. '#theme' => 'links',
  115. '#links' => $operations,
  116. '#attributes' => array('class' => array('links', 'inline', 'nowrap')),
  117. ),
  118. );
  119. $rows[$rid] = $row;
  120. }
  121. $form['rids'] = array(
  122. '#type' => 'tableselect',
  123. '#header' => $header,
  124. '#options' => $rows,
  125. '#empty' => t('No URL redirects available.'),
  126. '#attributes' => array(
  127. 'class' => array('redirect-list-tableselect'),
  128. ),
  129. '#attached' => array(
  130. 'js' => array(
  131. drupal_get_path('module', 'redirect') . '/redirect.admin.js',
  132. ),
  133. ),
  134. );
  135. if (redirect_access('create', 'redirect')) {
  136. $form['rids']['#empty'] .= ' ' . l(t('Add URL redirect.'), 'admin/config/search/redirect/add', array('query' => $destination));
  137. }
  138. $form['pager'] = array('#theme' => 'pager');
  139. return $form;
  140. }
  141. /**
  142. * Return a form to filter URL redirects.
  143. *
  144. * @see redirect_list_filter_form_submit()
  145. *
  146. * @ingroup forms
  147. */
  148. function redirect_list_filter_form($keys = '') {
  149. $form['#attributes'] = array('class' => array('search-form'));
  150. $form['basic'] = array(
  151. '#type' => 'fieldset',
  152. '#title' => t('Filter redirects'),
  153. '#attributes' => array('class' => array('container-inline')),
  154. );
  155. $form['basic']['filter'] = array(
  156. '#type' => 'textfield',
  157. '#title' => '',
  158. '#default_value' => $keys,
  159. '#maxlength' => 128,
  160. '#size' => 25,
  161. );
  162. $form['basic']['submit'] = array(
  163. '#type' => 'submit',
  164. '#value' => t('Filter'),
  165. '#submit' => array('redirect_list_filter_form_submit'),
  166. );
  167. if ($keys) {
  168. $form['basic']['reset'] = array(
  169. '#type' => 'submit',
  170. '#value' => t('Reset'),
  171. '#submit' => array('redirect_list_filter_form_reset'),
  172. );
  173. }
  174. return $form;
  175. }
  176. /**
  177. * Process filter form submission when the Filter button is pressed.
  178. */
  179. function redirect_list_filter_form_submit($form, &$form_state) {
  180. $form_state['redirect'] = 'admin/config/search/redirect/list/' . trim($form_state['values']['filter']);
  181. }
  182. /**
  183. * Process filter form submission when the Reset button is pressed.
  184. */
  185. function redirect_list_filter_form_reset($form, &$form_state) {
  186. $form_state['redirect'] = 'admin/config/search/redirect';
  187. }
  188. /**
  189. * Extends a query object for URL redirect filters.
  190. *
  191. * @param $query
  192. * Query object that should be filtered.
  193. * @param $keys
  194. * The filter string to use.
  195. */
  196. function redirect_build_filter_query(QueryAlterableInterface $query, array $fields, $keys = '') {
  197. if ($keys && $fields) {
  198. // Replace wildcards with PDO wildcards.
  199. $conditions = db_or();
  200. $wildcard = '%' . trim(preg_replace('!\*+!', '%', db_like($keys)), '%') . '%';
  201. foreach ($fields as $field) {
  202. $conditions->condition($field, $wildcard, 'LIKE');
  203. }
  204. $query->condition($conditions);
  205. }
  206. }
  207. /**
  208. * Validate redirect_list_form form submissions.
  209. *
  210. * Check if any redirects have been selected to perform the chosen
  211. * 'Update option' on.
  212. */
  213. function redirect_list_form_operations_validate($form, &$form_state) {
  214. // Error if there are no redirects selected.
  215. if (!is_array($form_state['values']['rids']) || !count(array_filter($form_state['values']['rids']))) {
  216. form_set_error('', t('No redirects selected.'));
  217. }
  218. }
  219. /**
  220. * Process redirect_list_form form submissions.
  221. *
  222. * Execute the chosen 'Update option' on the selected redirects.
  223. */
  224. function redirect_list_form_operations_submit($form, &$form_state) {
  225. $operations = $form['#operations'];
  226. $operation = $operations[$form_state['values']['operation']];
  227. // Filter out unchecked redirects
  228. $rids = array_filter($form_state['values']['rids']);
  229. if (!empty($operation['confirm']) && empty($form_state['values']['confirm'])) {
  230. // We need to rebuild the form to go to a second step. For example, to
  231. // show the confirmation form for the deletion of redirects.
  232. $form_state['rebuild'] = TRUE;
  233. }
  234. else {
  235. $function = $operation['callback'];
  236. // Add in callback arguments if present.
  237. if (isset($operation['callback arguments'])) {
  238. $args = array_merge(array($rids), $operation['callback arguments']);
  239. }
  240. else {
  241. $args = array($rids);
  242. }
  243. call_user_func_array($function, $args);
  244. // We display the number of redirects the user selected, regardless of
  245. // how many redirects actually changed status. Eg. if 1 enabled and 1
  246. // disabled redirects are checked for being enabled, we'll still display
  247. // "Enabled 1 redirect."
  248. $count = count($form_state['values']['rids']);
  249. watchdog('redirect', '@action @count redirects.', array('@action' => $operation['action_past'], '@count' => $count));
  250. drupal_set_message(format_plural(count($rids), '@action @count redirect.', '@action @count redirects.', array('@action' => $operation['action_past'], '@count' => $count)));
  251. }
  252. }
  253. function redirect_list_form_operations_confirm_form($form, &$form_state, $operation, $rids) {
  254. $operations = $form['#operations'];
  255. $operation = $operations[$form_state['values']['operation']];
  256. $form['rids_list'] = array(
  257. '#theme' => 'item_list',
  258. '#items' => array(),
  259. );
  260. $form['rids'] = array(
  261. '#type' => 'value',
  262. '#value' => $rids,
  263. );
  264. $redirects = redirect_load_multiple($rids);
  265. foreach ($redirects as $rid => $redirect) {
  266. $form['rids_list']['#items'][$rid] = check_plain(redirect_url($redirect->source, $redirect->source_options));
  267. }
  268. $form['operation'] = array('#type' => 'hidden', '#value' => $form_state['values']['operation']);
  269. $form['#submit'][] = 'redirect_list_form_operations_submit';
  270. $confirm_question = format_plural(count($rids), 'Are you sure you want to @action this redirect?', 'Are you sure you want to @action these redirects?', array('@action' => drupal_strtolower($operation['action'])));
  271. return confirm_form(
  272. $form,
  273. $confirm_question,
  274. 'admin/config/search/redirect', // @todo This does not redirect back to filtered page.
  275. t('This action cannot be undone.'),
  276. $operation['action'],
  277. t('Cancel')
  278. );
  279. }
  280. /**
  281. * Form builder to add or edit an URL redirect.
  282. *
  283. * @see redirect_element_validate_source()
  284. * @see redirect_element_validate_redirect()
  285. * @see redirect_edit_form_validate()
  286. * @see redirect_edit_form_submit()
  287. *
  288. * @ingroup forms
  289. */
  290. function redirect_edit_form($form, &$form_state, $redirect = NULL) {
  291. if (!isset($redirect)) {
  292. $redirect = new stdClass();
  293. }
  294. // Merge default values.
  295. redirect_object_prepare($redirect, array(
  296. 'source' => isset($_GET['source']) ? urldecode($_GET['source']) : '',
  297. 'source_options' => isset($_GET['source_options']) ? drupal_get_query_array($_GET['source_options']) : array(),
  298. 'redirect' => isset($_GET['redirect']) ? urldecode($_GET['redirect']) : '',
  299. 'redirect_options' => isset($_GET['redirect_options']) ? drupal_get_query_array($_GET['redirect_options']) : array(),
  300. 'language' => isset($_GET['language']) ? urldecode($_GET['language']) : LANGUAGE_NONE,
  301. ));
  302. $form['rid'] = array(
  303. '#type' => 'value',
  304. '#value' => $redirect->rid,
  305. );
  306. $form['type'] = array(
  307. '#type' => 'value',
  308. '#value' => $redirect->type,
  309. );
  310. $form['hash'] = array(
  311. '#type' => 'value',
  312. '#value' => $redirect->hash,
  313. );
  314. $form['uid'] = array(
  315. '#type' => 'value',
  316. '#value' => $redirect->uid,
  317. );
  318. $form['source'] = array(
  319. '#type' => 'textfield',
  320. '#title' => t('From'),
  321. '#description' => t("Enter an internal Drupal path or path alias to redirect (e.g. %example1 or %example2). Fragment anchors (e.g. %anchor) are <strong>not</strong> allowed.", array('%example1' => 'node/123', '%example2' => 'taxonomy/term/123', '%anchor' => '#anchor')),
  322. '#maxlength' => 560,
  323. '#default_value' => $redirect->rid || $redirect->source ? redirect_url($redirect->source, $redirect->source_options + array('alter' => FALSE)) : '',
  324. '#required' => TRUE,
  325. '#field_prefix' => $GLOBALS['base_url'] . '/' . (variable_get('clean_url', 0) ? '' : '?q='),
  326. '#element_validate' => array('redirect_element_validate_source'),
  327. );
  328. $form['source_options'] = array(
  329. '#type' => 'value',
  330. '#value' => $redirect->source_options,
  331. '#tree' => TRUE,
  332. );
  333. $form['redirect'] = array(
  334. '#type' => 'textfield',
  335. '#title' => t('To'),
  336. '#maxlength' => 560,
  337. '#default_value' => $redirect->rid || $redirect->redirect ? redirect_url($redirect->redirect, $redirect->redirect_options, TRUE) : '',
  338. '#required' => TRUE,
  339. '#description' => t('Enter an internal Drupal path, path alias, or complete external URL (like http://example.com/) to redirect to. Use %front to redirect to the front page.', array('%front' => '<front>')),
  340. '#element_validate' => array('redirect_element_validate_redirect'),
  341. );
  342. $form['redirect_options'] = array(
  343. '#type' => 'value',
  344. '#value' => $redirect->redirect_options,
  345. '#tree' => TRUE,
  346. );
  347. // This will be a hidden value unless locale module is enabled.
  348. $form['language'] = array(
  349. '#type' => 'value',
  350. '#value' => $redirect->language,
  351. );
  352. $form['status'] = array(
  353. '#type' => 'checkbox',
  354. '#title' => t('Enabled'),
  355. '#description' => t('If this box is checked, this redirect will be enabled.'),
  356. '#default_value' => $redirect->status,
  357. '#required' => FALSE,
  358. );
  359. $form['advanced'] = array(
  360. '#type' => 'fieldset',
  361. '#title' => t('Advanced options'),
  362. '#collapsible' => TRUE,
  363. '#collapsed' => TRUE,
  364. );
  365. $form['advanced']['status_code'] = array(
  366. '#type' => 'select',
  367. '#title' => t('Redirect status'),
  368. '#description' => t('You can find more information about HTTP redirect status codes at <a href="@status-codes">@status-codes</a>.', array('@status-codes' => 'http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_Redirection')),
  369. '#default_value' => $redirect->status_code,
  370. '#options' => array(0 => t('Default (@default)', array('@default' => variable_get('redirect_default_status_code', 301)))) + redirect_status_code_options(),
  371. );
  372. $form['override'] = array(
  373. '#type' => 'checkbox',
  374. '#title' => t('I understand the following warnings and would like to proceed with saving this URL redirect'),
  375. '#default_value' => FALSE,
  376. '#access' => FALSE,
  377. '#required' => FALSE,
  378. '#weight' => -100,
  379. '#prefix' => '<div class="messages warning">',
  380. '#suffix' => '</div>',
  381. );
  382. if (!empty($form_state['storage']['override_messages'])) {
  383. $form['override']['#access'] = TRUE;
  384. //$form['override']['#required'] = TRUE;
  385. $form['override']['#description'] = theme('item_list', array('items' => $form_state['storage']['override_messages']));
  386. // Reset the messages.
  387. $form_state['storage']['override_messages'] = array();
  388. }
  389. $form['actions'] = array('#type' => 'actions');
  390. $form['actions']['submit'] = array(
  391. '#type' => 'submit',
  392. '#value' => t('Save'),
  393. );
  394. $form['actions']['cancel'] = array(
  395. '#type' => 'link',
  396. '#title' => t('Cancel'),
  397. '#href' => isset($_GET['destination']) ? $_GET['destination'] : 'admin/config/search/redirect',
  398. );
  399. return $form;
  400. }
  401. /**
  402. * Element validate handler; validate the source of an URL redirect.
  403. *
  404. * @see redirect_edit_form()
  405. */
  406. function redirect_element_validate_source($element, &$form_state) {
  407. $value = &$element['#value'];
  408. // Check that the source contains no URL fragment.
  409. if (strpos($value, '#') !== FALSE) {
  410. form_error($element, t('The source path cannot contain an URL fragment anchor.'));
  411. }
  412. _redirect_extract_url_options($element, $form_state);
  413. // Disallow redirections from the frontpage.
  414. if ($value === '<front>') {
  415. form_error($element, t('The source path cannot be the front page.'));
  416. }
  417. // Cannot create redirects for valid paths.
  418. if (empty($form_state['values']['override'])) {
  419. $menu_item = menu_get_item($value);
  420. if ($menu_item && $menu_item['page_callback'] != 'redirect_redirect' && $value == $menu_item['path']) {
  421. $form_state['storage']['override_messages']['valid-path'] = t('The source path %path is likely a valid path. It is preferred to <a href="@url-alias">create URL aliases</a> for existing paths rather than redirects.', array('%path' => $value, '@url-alias' => url('admin/config/search/path/add')));
  422. $form_state['rebuild'] = TRUE;
  423. }
  424. }
  425. return $element;
  426. }
  427. /**
  428. * Element validate handler; validate the redirect of an URL redirect.
  429. *
  430. * @see redirect_edit_form()
  431. */
  432. function redirect_element_validate_redirect($element, &$form_state) {
  433. $value = &$element['#value'];
  434. _redirect_extract_url_options($element, $form_state);
  435. $value = &$form_state['values']['redirect'];
  436. // Normalize the path.
  437. $value = drupal_get_normal_path($value, $form_state['values']['language']);
  438. if (!valid_url($value) && !valid_url($value, TRUE) && $value != '<front>' && $value != '' && !file_exists($value)) {
  439. form_error($element, t('The redirect path %value is not valid.', array('%value' => $value)));
  440. }
  441. return $element;
  442. }
  443. /**
  444. * Extract the query and fragment parts out of an URL field.
  445. */
  446. function _redirect_extract_url_options(&$element, &$form_state) {
  447. $value = &$element['#value'];
  448. $type = $element['#name'];
  449. $options = &$form_state['values']["{$type}_options"];
  450. $parsed = redirect_parse_url($value);
  451. if (isset($parsed['fragment'])) {
  452. $options['fragment'] = $parsed['fragment'];
  453. }
  454. else {
  455. unset($options['fragment']);
  456. }
  457. if (isset($parsed['query'])) {
  458. $options['query'] = $parsed['query'];
  459. }
  460. else {
  461. unset($options['query']);
  462. }
  463. if (isset($parsed['scheme']) && $parsed['scheme'] == 'https') {
  464. $options['https'] = TRUE;
  465. }
  466. else {
  467. unset($options['https']);
  468. }
  469. if (!url_is_external($parsed['url'])) {
  470. $parsed['url'] = drupal_get_normal_path($parsed['url'], $form_state['values']['language']);
  471. }
  472. form_set_value($element, $parsed['url'], $form_state);
  473. return $parsed;
  474. }
  475. /**
  476. * Form validate handler; validate an URL redirect
  477. *
  478. * @see redirect_edit_form()
  479. */
  480. function redirect_edit_form_validate($form, &$form_state) {
  481. $redirect = (object) $form_state['values'];
  482. if (empty($form_state['values']['override'])) {
  483. // Find out if any (disabled or enabled) redirect with this source already
  484. // exists.
  485. if ($existing = redirect_load_by_source($redirect->source, $redirect->language, array(), FALSE)) {
  486. if ($redirect->rid != $existing->rid && $redirect->language == $existing->language) {
  487. // The "from" path should not conflict with another (disabled or
  488. // enabled) redirect.
  489. $form_state['storage']['override_messages']['redirect-conflict'] = 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->source, '@edit-page' => url('admin/config/search/redirect/edit/'. $existing->rid)));
  490. $form_state['rebuild'] = TRUE;
  491. }
  492. }
  493. if ($form['override']['#access']) {
  494. drupal_set_message('Did you read the warnings and click the checkbox?', 'error');
  495. $form_state['rebuild'] = TRUE;
  496. //form_set_error('override', 'CLICK DA BUTTON!');
  497. }
  498. }
  499. redirect_validate($redirect, $form, $form_state);
  500. }
  501. /**
  502. * Form submit handler; insert or update an URL redirect.
  503. *
  504. * @see redirect_edit_form()
  505. */
  506. function redirect_edit_form_submit($form, &$form_state) {
  507. form_state_values_clean($form_state);
  508. $redirect = (object) $form_state['values'];
  509. redirect_save($redirect);
  510. drupal_set_message(t('The redirect has been saved.'));
  511. $form_state['redirect'] = 'admin/config/search/redirect';
  512. }
  513. /**
  514. * Form builder to delete an URL redirect.
  515. *
  516. * @see redirect_delete_form()
  517. * @see confirm_form()
  518. *
  519. * @ingroup forms
  520. */
  521. function redirect_delete_form($form, &$form_state, $redirect) {
  522. $form['rid'] = array(
  523. '#type' => 'value',
  524. '#value' => $redirect->rid,
  525. );
  526. return confirm_form(
  527. $form,
  528. t('Are you sure you want to delete the URL redirect from %source to %redirect?', array('%source' => $redirect->source, '%redirect' => $redirect->redirect)),
  529. 'admin/config/search/redirect'
  530. );
  531. }
  532. /**
  533. * Form submit handler; delete an URL redirect after confirmation.
  534. *
  535. * @see redirect_delete_form()
  536. */
  537. function redirect_delete_form_submit($form, &$form_state) {
  538. redirect_delete($form_state['values']['rid']);
  539. drupal_set_message(t('The redirect has been deleted.'));
  540. $form_state['redirect'] = 'admin/config/search/redirect';
  541. }
  542. /**
  543. * Form builder for redirection settings.
  544. *
  545. * @see system_settings_form()
  546. * @see redirect_settings_form_submit()
  547. *
  548. * @ingroup forms
  549. */
  550. function redirect_settings_form($form, &$form_state) {
  551. $form['redirect_auto_redirect'] = array(
  552. '#type' => 'checkbox',
  553. '#title' => t('Automatically create redirects when URL aliases are changed.'),
  554. '#default_value' => variable_get('redirect_auto_redirect', TRUE),
  555. '#disabled' => !module_exists('path'),
  556. );
  557. $form['redirect_passthrough_querystring'] = array(
  558. '#type' => 'checkbox',
  559. '#title' => t('Retain query string through redirect.'),
  560. '#default_value' => variable_get('redirect_passthrough_querystring', 1),
  561. '#description' => t('For example, given a redirect from %source to %redirect, if a user visits %sourcequery they would be redirected to %redirectquery. The query strings in the redirection will always take precedence over the current query string.', array('%source' => 'source-path', '%redirect' => 'node?a=apples', '%sourcequery' => 'source-path?a=alligators&b=bananas', '%redirectquery' => 'node?a=apples&b=bananas')),
  562. );
  563. $form['redirect_warning'] = array(
  564. '#type' => 'checkbox',
  565. '#title' => t('Display a warning message to users when they are redirected.'),
  566. '#default_value' => variable_get('redirect_warning', FALSE),
  567. '#access' => FALSE,
  568. );
  569. $form['redirect_default_status_code'] = array(
  570. '#type' => 'select',
  571. '#title' => t('Default redirect status'),
  572. '#description' => t('You can find more information about HTTP redirect status codes at <a href="@status-codes">@status-codes</a>.', array('@status-codes' => 'http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_Redirection')),
  573. '#options' => redirect_status_code_options(),
  574. '#default_value' => variable_get('redirect_default_status_code', 301),
  575. );
  576. $form['redirect_page_cache'] = array(
  577. '#type' => 'checkbox',
  578. '#title' => t('Allow redirects to be saved into the page cache.'),
  579. '#default_value' => variable_get('redirect_page_cache', 0),
  580. '#description' => t('This feature requires <a href="@performance">Cache pages for anonymous users</a> to be enabled and the %variable variable to be true (currently set to @value).', array('@performance' => url('admin/config/development/performance'), '%variable' => "\$conf['page_cache_invoke_hooks']", '@value' => var_export(variable_get('page_cache_invoke_hooks', TRUE), TRUE))),
  581. '#disabled' => !variable_get('cache', 0) || !variable_get('page_cache_invoke_hooks', TRUE),
  582. );
  583. $form['redirect_purge_inactive'] = array(
  584. '#type' => 'select',
  585. '#title' => t('Delete redirects that have not been accessed for'),
  586. '#default_value' => variable_get('redirect_purge_inactive', 0),
  587. '#options' => array(0 => t('Never (do not discard)')) + drupal_map_assoc(array(604800, 1209600, 1814400, 2592000, 5184000, 7776000, 10368000, 15552000, 31536000), 'format_interval'),
  588. '#description' => t('Only redirects managed by the redirect module itself will be deleted. Redirects managed by other modules will be left alone.'),
  589. '#disabled' => variable_get('redirect_page_cache', 0) && !variable_get('page_cache_invoke_hooks', TRUE),
  590. );
  591. $form['globals'] = array(
  592. '#type' => 'fieldset',
  593. '#title' => t('Always enabled redirections'),
  594. '#description' => t('(formerly Global Redirect features)'),
  595. '#access' => FALSE,
  596. );
  597. $form['globals']['redirect_global_home'] = array(
  598. '#type' => 'checkbox',
  599. '#title' => t('Redirect from paths like index.php and /node to the root directory.'),
  600. '#default_value' => variable_get('redirect_global_home', 1),
  601. '#access' => FALSE,
  602. );
  603. $form['globals']['redirect_global_clean'] = array(
  604. '#type' => 'checkbox',
  605. '#title' => t('Redirect from non-clean URLs to clean URLs.'),
  606. '#default_value' => variable_get('redirect_global_clean', 1),
  607. '#disabled' => !variable_get('clean_url', 0),
  608. '#access' => FALSE,
  609. );
  610. $form['globals']['redirect_global_canonical'] = array(
  611. '#type' => 'checkbox',
  612. '#title' => t('Redirect from non-canonical URLs to the canonical URLs.'),
  613. '#default_value' => variable_get('redirect_global_canonical', 1),
  614. );
  615. $form['globals']['redirect_global_deslash'] = array(
  616. '#type' => 'checkbox',
  617. '#title' => t('Remove trailing slashes from paths.'),
  618. '#default_value' => variable_get('redirect_global_deslash', 0),
  619. '#access' => FALSE,
  620. );
  621. $form['globals']['redirect_global_admin_paths'] = array(
  622. '#type' => 'checkbox',
  623. '#title' => t('Allow redirections on admin paths.'),
  624. '#default_value' => variable_get('redirect_global_admin_paths', 0),
  625. );
  626. $form['#submit'][] = 'redirect_settings_form_submit';
  627. return system_settings_form($form);
  628. }
  629. /**
  630. * Form submit handler; clears the page cache.
  631. *
  632. * @see redirect_settings_form()
  633. */
  634. function redirect_settings_form_submit($form, &$form_state) {
  635. redirect_page_cache_clear();
  636. }
  637. function redirect_404_list($form = NULL) {
  638. $destination = drupal_get_destination();
  639. // Get filter keys and add the filter form.
  640. $keys = func_get_args();
  641. //$keys = array_splice($keys, 2); // Offset the $form and $form_state parameters.
  642. $keys = implode('/', $keys);
  643. $build['redirect_list_404_filter_form'] = drupal_get_form('redirect_list_404_filter_form', $keys);
  644. $header = array(
  645. array('data' => t('Page'), 'field' => 'message'),
  646. array('data' => t('Count'), 'field' => 'count', 'sort' => 'desc'),
  647. array('data' => t('Last accessed'), 'field' => 'timestamp'),
  648. array('data' => t('Operations')),
  649. );
  650. $count_query = db_select('watchdog', 'w');
  651. $count_query->addExpression('COUNT(DISTINCT(w.message))');
  652. $count_query->leftJoin('redirect', 'r', 'w.message = r.source');
  653. $count_query->condition('w.type', 'page not found');
  654. $count_query->isNull('r.rid');
  655. redirect_build_filter_query($count_query, array('w.message'), $keys);
  656. $query = db_select('watchdog', 'w')->extend('PagerDefault')->extend('TableSort');
  657. $query->fields('w', array('message'));
  658. $query->addExpression('COUNT(wid)', 'count');
  659. $query->addExpression('MAX(timestamp)', 'timestamp');
  660. $query->leftJoin('redirect', 'r', 'w.message = r.source');
  661. $query->isNull('r.rid');
  662. $query->condition('w.type', 'page not found');
  663. $query->groupBy('w.message');
  664. $query->orderByHeader($header);
  665. $query->limit(25);
  666. redirect_build_filter_query($query, array('w.message'), $keys);
  667. $query->setCountQuery($count_query);
  668. $results = $query->execute();
  669. $rows = array();
  670. foreach ($results as $result) {
  671. $row = array();
  672. $row['source'] = l($result->message, $result->message, array('query' => $destination));
  673. $row['count'] = $result->count;
  674. $row['timestamp'] = format_date($result->timestamp, 'short');
  675. $operations = array();
  676. if (redirect_access('create', 'redirect')) {
  677. $operations['add'] = array(
  678. 'title' => t('Add redirect'),
  679. 'href' => 'admin/config/search/redirect/add/',
  680. 'query' => array('source' => $result->message) + $destination,
  681. );
  682. }
  683. $row['operations'] = array(
  684. 'data' => array(
  685. '#theme' => 'links',
  686. '#links' => $operations,
  687. '#attributes' => array('class' => array('links', 'inline', 'nowrap')),
  688. ),
  689. );
  690. $rows[] = $row;
  691. }
  692. $build['redirect_404_table'] = array(
  693. '#theme' => 'table',
  694. '#header' => $header,
  695. '#rows' => $rows,
  696. '#empty' => t('No 404 pages without redirects found.'),
  697. );
  698. $build['redirect_404_pager'] = array('#theme' => 'pager');
  699. return $build;
  700. }
  701. /**
  702. * Return a form to filter URL redirects.
  703. *
  704. * @see redirect_list_filter_form_submit()
  705. *
  706. * @ingroup forms
  707. */
  708. function redirect_list_404_filter_form($form, &$form_state, $keys = '') {
  709. $form['#attributes'] = array('class' => array('search-form'));
  710. $form['basic'] = array(
  711. '#type' => 'fieldset',
  712. '#title' => t('Filter 404s'),
  713. '#attributes' => array('class' => array('container-inline')),
  714. );
  715. $form['basic']['filter'] = array(
  716. '#type' => 'textfield',
  717. '#title' => '',
  718. '#default_value' => $keys,
  719. '#maxlength' => 128,
  720. '#size' => 25,
  721. );
  722. $form['basic']['submit'] = array(
  723. '#type' => 'submit',
  724. '#value' => t('Filter'),
  725. '#submit' => array('redirect_list_404_filter_form_submit'),
  726. );
  727. if ($keys) {
  728. $form['basic']['reset'] = array(
  729. '#type' => 'submit',
  730. '#value' => t('Reset'),
  731. '#submit' => array('redirect_list_404_filter_form_reset'),
  732. );
  733. }
  734. return $form;
  735. }
  736. /**
  737. * Process filter form submission when the Filter button is pressed.
  738. */
  739. function redirect_list_404_filter_form_submit($form, &$form_state) {
  740. $form_state['redirect'] = 'admin/config/search/redirect/404/' . trim($form_state['values']['filter']);
  741. }
  742. /**
  743. * Process filter form submission when the Reset button is pressed.
  744. */
  745. function redirect_list_404_filter_form_reset($form, &$form_state) {
  746. $form_state['redirect'] = 'admin/config/search/redirect/404';
  747. }
  748. function redirect_list_table($redirects, $header) {
  749. $destination = drupal_get_destination();
  750. $default_status_code = variable_get('redirect_default_status_code', 301);
  751. // Set up the header.
  752. $header = array_combine($header, $header);
  753. $header = array_intersect_key(array(
  754. 'source' => array('data' => t('From'), 'field' => 'source', 'sort' => 'asc'),
  755. 'redirect' => array('data' => t('To'), 'field' => 'redirect'),
  756. 'status' => array('data' => t('Status'), 'field' => 'status'),
  757. 'status_code' => array('data' => t('Type'), 'field' => 'status_code'),
  758. 'language' => array('data' => t('Language'), 'field' => 'language'),
  759. 'count' => array('data' => t('Count'), 'field' => 'count'),
  760. 'access' => array('data' => t('Last accessed'), 'field' => 'access'),
  761. 'operations' => array('data' => t('Operations')),
  762. ), $header);
  763. // Do not include the language column if locale is disabled.
  764. if (!module_exists('locale')) {
  765. unset($header['language']);
  766. }
  767. $rows = array();
  768. foreach ($redirects as $rid => $redirect) {
  769. $row = array();
  770. $redirect->source_options = array_merge($redirect->source_options, array('alias' => TRUE, 'language' => redirect_language_load($redirect->language)));
  771. $source_url = redirect_url($redirect->source, $redirect->source_options);
  772. $redirect_url = redirect_url($redirect->redirect, array_merge($redirect->redirect_options, array('alias' => TRUE)));
  773. $row['data']['source'] = l($source_url, $redirect->source, $redirect->source_options);
  774. $row['data']['redirect'] = l($redirect_url, $redirect->redirect, $redirect->redirect_options);
  775. $row['data']['status'] = $redirect->status ? t('Enabled') : t('Disabled');
  776. $row['data']['status_code'] = $redirect->status_code ? $redirect->status_code : t('Default (@default)', array('@default' => $default_status_code));
  777. $row['data']['language'] = module_invoke('locale', 'language_name', $redirect->language);
  778. $row['data']['count'] = $redirect->count;
  779. if ($redirect->access) {
  780. $row['data']['access'] = array(
  781. 'data' => t('!interval ago', array('!interval' => format_interval(REQUEST_TIME - $redirect->access))),
  782. 'title' => t('Last accessed on @date', array('@date' => format_date($redirect->access))),
  783. );
  784. }
  785. else {
  786. $row['data']['access'] = t('Never');
  787. }
  788. // Mark redirects that override existing paths with a warning in the table.
  789. if (drupal_valid_path($redirect->source)) {
  790. $row['class'][] = 'warning';
  791. $row['title'] = t('This redirect overrides an existing internal path.');
  792. }
  793. if ($redirect->status) {
  794. $row['class'][] = 'redirect-enabled';
  795. }
  796. else {
  797. $row['class'][] = 'redirect-disabled';
  798. }
  799. $operations = array();
  800. if (redirect_access('update', $redirect)) {
  801. $operations['edit'] = array(
  802. 'title' => t('Edit'),
  803. 'href' => 'admin/config/search/redirect/edit/' . $rid,
  804. 'query' => $destination,
  805. );
  806. }
  807. if (redirect_access('delete', $redirect)) {
  808. $operations['delete'] = array(
  809. 'title' => t('Delete'),
  810. 'href' => 'admin/config/search/redirect/delete/' . $rid,
  811. 'query' => $destination,
  812. );
  813. }
  814. $row['data']['operations'] = array(
  815. 'data' => array(
  816. '#theme' => 'links',
  817. '#links' => $operations,
  818. '#attributes' => array('class' => array('links', 'inline', 'nowrap')),
  819. ),
  820. );
  821. $row['data'] = array_intersect_key($row['data'], $header);
  822. $rows[$rid] = $row;
  823. }
  824. $build['list'] = array(
  825. '#theme' => 'table',
  826. '#header' => $header,
  827. '#rows' => $rows,
  828. '#empty' => t('No URL redirects available.'),
  829. '#attributes' => array('class' => array('redirect-list')),
  830. );
  831. return $build;
  832. }