Added the Porter Stemmer module to improve searches. This doesn't deal with some...
[yaffs-website] / web / modules / contrib / redirect / redirect.module
1 <?php
2
3 /**
4  * @file
5  * The redirect module.
6  */
7
8 /**
9  * @defgroup redirect_api Redirection API
10  * @{
11  * Functions related to URL redirects.
12  *
13  * @} End of "defgroup redirect_api".
14  */
15 use Drupal\Component\Utility\UrlHelper;
16 use Drupal\Core\Cache\Cache;
17 use Drupal\Core\Entity\EntityInterface;
18 use Drupal\Core\Field\FieldItemList;
19 use Drupal\Core\Form\FormStateInterface;
20 use Drupal\Core\Language\Language;
21 use Drupal\Core\Routing\RouteMatchInterface;
22 use Drupal\Core\Url;
23 use Drupal\Core\Site\Settings;
24 use Drupal\redirect\Entity\Redirect;
25 use Symfony\Component\Routing\Exception\RouteNotFoundException;
26
27 /**
28  * Implements hook_hook_info().
29  */
30 function redirect_hook_info() {
31   $hooks = array(
32     'redirect_load',
33     'redirect_load_by_source_alter',
34     'redirect_access',
35     'redirect_prepare',
36     'redirect_validate',
37     'redirect_presave',
38     'redirect_insert',
39     'redirect_update',
40     'redirect_delete',
41     'redirect_alter',
42   );
43
44   return array_fill_keys($hooks, array('group' => 'redirect'));
45 }
46
47 /**
48  * Implements hook_help().
49  */
50 function redirect_help($route_name, RouteMatchInterface $route_match) {
51   $output = '';
52   switch ($route_name) {
53     case 'help.page.redirect':
54       $output = '<h3>' . t('About') . '</h3>';
55       $output .= '<p>' . t('The Redirect module allows users to redirect from old URLs to new URLs.   For more information, see the <a href=":online">online documentation for Redirect</a>.', [':online' => 'https://www.drupal.org/documentation/modules/path-redirect']) . '</p>';
56       $output .= '<dl>';
57       $output .= '<h3>' . t('Uses') . '</h3>';
58       $output .= '<dd>' . t('Redirect is accessed from three tabs that help you manage <a href=":list">URL Redirects</a>.', [':list' => Url::fromRoute('redirect.list')->toString()]) . '</dd>';
59       $output .= '<dt>' . t('Manage URL Redirects') . '</dt>';
60       $output .= '<dd>' . t('The <a href=":redirect">"URL Redirects"</a> page is used to setup and manage URL Redirects.  New redirects are created here using the <a href=":add_form">Add redirect</a> button which presents a form to simplify the creation of redirects . The URL redirects page provides a list of all redirects on the site and allows you to edit them.', [':redirect' => Url::fromRoute('redirect.list')->toString(), ':add_form' => Url::fromRoute('redirect.add')->toString()]) . '</dd>';
61       if (\Drupal::moduleHandler()->moduleExists('redirect_404')) {
62         $output .= '<dt>' . t('Fix 404 pages') . '</dt>';
63         $output .= '<dd>' . t('<a href=":fix_404">"Fix 404 pages"</a> lists all paths that have resulted in 404 errors and do not yet have any redirects assigned to them. This 404 (or Not Found) error message is an HTTP standard response code indicating that the client was able to communicate with a given server, but the server could not find what was requested.', [':fix_404' => Url::fromRoute('redirect_404.fix_404')->toString()]) . '</dd>';
64       }
65       elseif (!\Drupal::moduleHandler()->moduleExists('redirect_404') && \Drupal::currentUser()->hasPermission('administer modules')) {
66         $output .= '<dt>' . t('Fix 404 pages') . '</dt>';
67         $output .= '<dd>' . t('404 (or Not Found) error message is an HTTP standard response code indicating that the client was able to communicate with a given server, but the server could not find what was requested. Please install the <a href=":extend">Redirect 404</a> submodule to be able to log all paths that have resulted in 404 errors.', [':extend' => Url::fromRoute('system.modules_list')->toString()]) . '</dd>';
68       }
69       $output .= '<dt>' . t('Configure Global Redirects') . '</dt>';
70       $output .= '<dd>' . t('The <a href=":settings">"Settings"</a> page presents you with a number of means to adjust redirect settings.', [':settings' => Url::fromRoute('redirect.settings')->toString()]) . '</dd>';
71       $output .= '</dl>';
72       return $output;
73       break;
74   }
75 }
76
77 /**
78  * Implements hook_entity_delete().
79  *
80  * Will delete redirects based on the entity URL.
81  */
82 function redirect_entity_delete(EntityInterface $entity) {
83   try {
84     if ($entity->getEntityType()->hasLinkTemplate('canonical') && $entity->toUrl('canonical')->isRouted()) {
85       redirect_delete_by_path('internal:/' . $entity->toUrl('canonical')->getInternalPath());
86       redirect_delete_by_path('entity:' . $entity->getEntityTypeId() . '/' . $entity->id());
87     }
88   }
89   catch (RouteNotFoundException $e) {
90     // This can happen if a module incorrectly defines a link template, ignore
91     // such errors.
92   }
93 }
94
95 /**
96  * Implements hook_path_update().
97  *
98  * Will create redirect from the old path alias to the new one.
99  */
100 function redirect_path_update(array $path) {
101   if (!\Drupal::config('redirect.settings')->get('auto_redirect')) {
102     return;
103   }
104   $original_path = $path['original'];
105
106   // Delete all redirects having the same source as this alias.
107   redirect_delete_by_path($path['alias'], $path['langcode'], FALSE);
108   if ($original_path['alias'] != $path['alias']) {
109     if (!redirect_repository()->findMatchingRedirect($original_path['alias'], array(), $original_path['langcode'])) {
110       $redirect = Redirect::create();
111       $redirect->setSource($original_path['alias']);
112       $redirect->setRedirect($path['source']);
113       $redirect->setLanguage($original_path['langcode']);
114       $redirect->setStatusCode(\Drupal::config('redirect.settings')->get('default_status_code'));
115       $redirect->save();
116     }
117   }
118 }
119
120 /**
121  * Implements hook_path_insert().
122  */
123 function redirect_path_insert(array $path) {
124   // Delete all redirects having the same source as this alias.
125   redirect_delete_by_path($path['alias'], $path['langcode'], FALSE);
126 }
127
128 /**
129  * Implements hook_path_delete().
130  */
131 function redirect_path_delete($path) {
132   if (!\Drupal::config('redirect.settings')->get('auto_redirect')) {
133     return;
134   }
135   elseif (isset($path['redirect']) && !$path['redirect']) {
136     return;
137   }
138   elseif (empty($path)) {
139     // @todo Remove this condition and allow $path to use an array type hint
140     // when http://drupal.org/node/1025904 is fixed.
141     return;
142   }
143
144   // Redirect from a deleted alias to the system path.
145   //if (!redirect_load_by_source($path['alias'], $path['language'])) {
146   //  $redirect = new stdClass();
147   //  redirect_create($redirect);
148   //  $redirect->source = $path['alias'];
149   //  $redirect->redirect = $path['source'];
150   //  $redirect->language = $path['language'];
151   //  redirect_save($redirect);
152   //}
153 }
154
155 /**
156  * Implements hook_page_build().
157  *
158  * Adds an action on 404 pages to create a redirect.
159  *
160  * @todo hook_page_build() can no longer be used for this. Find a different way.
161  */
162 function redirect_page_build(&$page) {
163   if (redirect_is_current_page_404() && \Drupal::currentUser()->hasPermission('administer redirects')) {
164     if (!isset($page['content']['system_main']['actions'])) {
165       $page['content']['system_main']['actions'] = array(
166         '#theme' => 'links',
167         '#links' => array(),
168         '#attributes' => array('class' => array('action-links')),
169         '#weight' => -100,
170       );
171     }
172     // We cannot simply use current_path() because if a 404 path is set, then
173     // that value overrides whatever is in $_GET['q']. The
174     // drupal_deliver_html_page() function thankfully puts the original current
175     // path into $_GET['destination'].
176     $destination = drupal_get_destination();
177     $page['content']['system_main']['actions']['#links']['add_redirect'] = array(
178       'title' => t('Add URL redirect from this page to another location'),
179       'href' => 'admin/config/search/redirect/add',
180       'query' => array('source' => $destination['destination']) + drupal_get_destination(),
181     );
182   }
183 }
184
185 /**
186  * Gets the redirect repository service.
187  *
188  * @return \Drupal\redirect\RedirectRepository
189  *   The repository service.
190  */
191 function redirect_repository() {
192   return \Drupal::service('redirect.repository');
193 }
194
195 /**
196  * Delete any redirects associated with a path or any of its sub-paths.
197  *
198  * Given a source like 'node/1' this function will delete any redirects that
199  * have that specific source or any sources that match 'node/1/%'.
200  *
201  * @param string $path
202  *   An string with an internal Drupal path.
203  * @param string $langcode
204  *   (optional) If specified, limits deletion to redirects for the given
205  *   language. Defaults to all languages.
206  * @param bool $match_subpaths_and_redirect
207  *   (optional) Whether redirects with a destination to the given path and
208  *   sub-paths should also be deleted.
209  *
210  * @ingroup redirect_api
211  */
212 function redirect_delete_by_path($path, $langcode = NULL, $match_subpaths_and_redirect = TRUE) {
213   $path = ltrim($path, '/');
214   $database = \Drupal::database();
215   $query = $database->select('redirect');
216   $query->addField('redirect', 'rid');
217   $query_or = db_or();
218   $query_or->condition('redirect_source__path', $database->escapeLike($path), 'LIKE');
219   if ($match_subpaths_and_redirect) {
220     $query_or->condition('redirect_source__path', $database->escapeLike($path . '/') . '%', 'LIKE');
221     $query_or->condition('redirect_redirect__uri', $database->escapeLike($path), 'LIKE');
222     $query_or->condition('redirect_redirect__uri', $database->escapeLike($path . '/') . '%', 'LIKE');
223   }
224
225   if ($langcode) {
226     $query->condition('language', $langcode);
227   }
228
229   $query->condition($query_or);
230   $rids = $query->execute()->fetchCol();
231
232   if ($rids) {
233     foreach (redirect_repository()->loadMultiple($rids) as $redirect) {
234       $redirect->delete();
235     }
236   }
237 }
238
239 /**
240  * Sort an array recusively.
241  *
242  * @param $array
243  *   The array to sort, by reference.
244  * @param $callback
245  *   The sorting callback to use (e.g. 'sort', 'ksort', 'asort').
246  *
247  * @return
248  *   TRUE on success or FALSE on failure.
249  */
250 function redirect_sort_recursive(&$array, $callback = 'sort') {
251   $result = $callback($array);
252   foreach ($array as $key => $value) {
253     if (is_array($value)) {
254       $result &= redirect_sort_recursive($array[$key], $callback);
255     }
256   }
257   return $result;
258 }
259
260 /**
261  * Build the URL of a redirect for display purposes only.
262  */
263 function redirect_url($path, array $options = array(), $clean_url = NULL) {
264   // @todo - deal with removal of clean_url config. See
265   //    https://drupal.org/node/1659580
266   if (!isset($clean_url)) {
267     //$clean_url = variable_get('clean_url', 0);
268   }
269
270   if ($path == '') {
271     $path = '<front>';
272   }
273
274   if (!isset($options['alter']) || !empty($options['alter'])) {
275     \Drupal::moduleHandler()->alter('redirect_url', $path, $options);
276   }
277
278   // The base_url might be rewritten from the language rewrite in domain mode.
279   if (!isset($options['base_url'])) {
280     // @todo - is this correct? See https://drupal.org/node/1798832.
281     if (isset($options['https']) && Settings::get('mixed_mode_sessions', FALSE)) {
282       if ($options['https'] === TRUE) {
283         $options['base_url'] = $GLOBALS['base_secure_url'];
284         $options['absolute'] = TRUE;
285       }
286       elseif ($options['https'] === FALSE) {
287         $options['base_url'] = $GLOBALS['base_insecure_url'];
288         $options['absolute'] = TRUE;
289       }
290     }
291     else {
292       $options['base_url'] = $GLOBALS['base_url'];
293     }
294   }
295
296   if (empty($options['absolute']) || url_is_external($path)) {
297     $url = $path;
298   }
299   else {
300     $url = $options['base_url'] . base_path() . $path;
301   }
302
303   if (isset($options['query'])) {
304     $url .= $clean_url ? '?' : '&';
305     $url .= UrlHelper::buildQuery($options['query']);
306   }
307   if (isset($options['fragment'])) {
308     $url .= '#' . $options['fragment'];
309   }
310
311   return $url;
312 }
313
314 function redirect_status_code_options($code = NULL) {
315   $codes = array(
316     300 => t('300 Multiple Choices'),
317     301 => t('301 Moved Permanently'),
318     302 => t('302 Found'),
319     303 => t('303 See Other'),
320     304 => t('304 Not Modified'),
321     305 => t('305 Use Proxy'),
322     307 => t('307 Temporary Redirect'),
323   );
324   return isset($codes[$code]) ? $codes[$code] : $codes;
325 }
326
327 /**
328  * Returns if the current page request is a page not found (404 status error).
329  *
330  * Why the fuck do we have to do this? Why is there not an easier way???
331  *
332  * @return
333  *   TRUE if the current page is a 404, or FALSE otherwise.
334  */
335 function redirect_is_current_page_404() {
336   return drupal_get_http_header('Status') == '404 Not Found';
337 }
338
339 /**
340  * uasort callback; Compare redirects based on language neutrality and rids.
341  */
342 function _redirect_uasort($a, $b) {
343   $a_weight = isset($a->weight) ? $a->weight : 0;
344   $b_weight = isset($b->weight) ? $b->weight : 0;
345   if ($a_weight != $b_weight) {
346     // First sort by weight (case sensitivity).
347     return $a_weight > $b_weight;
348   }
349   elseif ($a->language != $b->language) {
350     // Then sort by language specific over language neutral.
351     return $a->language == Language::LANGCODE_NOT_SPECIFIED;
352   }
353   elseif (!empty($a->source_options['query']) != !empty($b->source_options['query'])) {
354     // Then sort by redirects that do not have query strings over ones that do.
355     return empty($a->source_options['query']);
356   }
357   else {
358     // Lastly sort by the highest redirect ID.
359     return $a->rid < $b->rid;
360   }
361 }
362
363 /**
364  * Implements hook_form_FORM_ID_alter() on behalf of locale.module.
365  */
366 function locale_form_redirect_edit_form_alter(array &$form, FormStateInterface $form_state) {
367   $form['language'] = array(
368     '#type' => 'select',
369     '#title' => t('Language'),
370     '#options' => array(Language::LANGCODE_NOT_SPECIFIED => t('All languages')) + \Drupal::languageManager()->getLanguages(),
371     '#default_value' => $form['language']['#value'],
372     '#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>.'),
373   );
374 }
375
376 /**
377  * Fetch an array of redirect bulk operations.
378  *
379  * @see hook_redirect_operations()
380  * @see hook_redirect_operations_alter()
381  */
382 function redirect_get_redirect_operations() {
383   $operations = &drupal_static(__FUNCTION__);
384
385   if (!isset($operations)) {
386     $operations = \Drupal::moduleHandler()->invokeAll('redirect_operations');
387     \Drupal::moduleHandler()->alter('redirect_operations', $operations);
388   }
389
390   return $operations;
391 }
392
393 /**
394  * Implements hook_redirect_operations().
395  */
396 function redirect_redirect_operations() {
397   $operations['delete'] = array(
398     'action' => t('Delete'),
399     'action_past' => t('Deleted'),
400     'callback' => 'redirect_delete_multiple',
401     'confirm' => TRUE,
402   );
403   return $operations;
404 }
405
406 /**
407  * Ajax callback for the redirect link widget.
408  */
409 function redirect_source_link_get_status_messages(array $form, FormStateInterface $form_state) {
410   return $form['redirect_source']['widget'][0]['status_box'];
411 }
412
413 /**
414  * Implements hook_entity_extra_field_info().
415  */
416 function redirect_entity_extra_field_info() {
417   $extra = [];
418
419   if (\Drupal::service('module_handler')->moduleExists('node')) {
420     $node_types = \Drupal::entityTypeManager()
421       ->getStorage('node_type')
422       ->loadMultiple();
423
424     foreach ($node_types as $node_type) {
425       $extra['node'][$node_type->id()]['form']['url_redirects'] = [
426         'label' => t('URL redirects'),
427         'description' => t('Redirect module form elements'),
428         'weight' => 50,
429       ];
430     }
431   }
432
433   return $extra;
434 }
435
436 /**
437  * Implements hook_form_node_form_alter().
438  */
439 function redirect_form_node_form_alter(&$form, FormStateInterface $form_state, $form_id) {
440   /** @var \Drupal\node\NodeInterface $node */
441   $node = $form_state->getFormObject()->getEntity();
442   if (!$node->isNew() && \Drupal::currentUser()->hasPermission('administer redirects')) {
443
444     $nid = $node->id();
445
446     // Find redirects to this node.
447     $redirects = \Drupal::service('redirect.repository')
448       ->findByDestinationUri(["internal:/node/$nid", "entity:node/$nid"]);
449
450     // Assemble the rows for the table.
451     $rows = [];
452     /** @var \Drupal\Core\Entity\EntityListBuilder $list_builder */
453     $list_builder = \Drupal::service('entity.manager')->getListBuilder('redirect');
454     /** @var \Drupal\redirect\Entity\Redirect[] $redirects */
455     foreach ($redirects as $redirect) {
456       $row = [];
457       $path = $redirect->getSourcePathWithQuery();
458       $row['path'] = [
459         'class' => ['redirect-table__path'],
460         'data' => ['#plain_text' => $path],
461         'title' => $path,
462       ];
463       $row['operations'] = [
464         'data' => [
465           '#type' => 'operations',
466           '#links' => $list_builder->getOperations($redirect),
467         ],
468       ];
469       $rows[] = $row;
470     }
471
472     // Add the list to the vertical tabs section of the form.
473     $header = [
474       ['class' => ['redirect-table__path'], 'data' => t('From')],
475       ['class' => ['redirect-table__operations'], 'data' => t('Operations')],
476     ];
477     $form['url_redirects'] = [
478       '#type' => 'details',
479       '#title' => t('URL redirects'),
480       '#group' => 'advanced',
481       '#open' => FALSE,
482       'table' => [
483         '#type' => 'table',
484         '#header' => $header,
485         '#rows' => $rows,
486         '#empty' => t('No URL redirects available.'),
487         '#attributes' => ['class' => ['redirect-table']],
488       ],
489       '#attached' => [
490         'library' => [
491           'redirect/drupal.redirect.admin',
492         ],
493       ],
494     ];
495
496     if (!empty($rows)) {
497       $form['url_redirects']['warning'] = [
498         '#markup' => t('Note: links open in the current window.'),
499         '#prefix' => '<p>',
500         '#suffix' => '</p>',
501       ];
502     }
503   }
504 }