Projet

Général

Profil

Paste
Télécharger (38,3 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / pathauto / pathauto.module @ 59ae487e

1
<?php
2

    
3
/**
4
 * @defgroup pathauto Pathauto: Automatically generates aliases for content
5
 *
6
 * The Pathauto module automatically generates path aliases for various kinds of
7
 * content (nodes, categories, users) without requiring the user to manually
8
 * specify the path alias. This allows you to get aliases like
9
 * /category/my-node-title.html instead of /node/123. The aliases are based upon
10
 * a "pattern" system which the administrator can control.
11
 */
12

    
13
/**
14
 * @file
15
 * Main file for the Pathauto module, which automatically generates aliases for content.
16
 *
17
 * @ingroup pathauto
18
 */
19

    
20
/**
21
 * The default ignore word list.
22
 */
23
define('PATHAUTO_IGNORE_WORDS', 'a, an, as, at, before, but, by, for, from, is, in, into, like, of, off, on, onto, per, since, than, the, this, that, to, up, via, with');
24

    
25
/**
26
 * Implements hook_hook_info().
27
 */
28
function pathauto_hook_info() {
29
  $hooks = array(
30
    'pathauto',
31
    'path_alias_types',
32
    'pathauto_pattern_alter',
33
    'pathauto_alias_alter',
34
    'pathauto_is_alias_reserved',
35
  );
36
  return array_fill_keys($hooks, array('group' => 'pathauto'));
37
}
38

    
39
/**
40
 * Implements hook_help().
41
 */
42
function pathauto_help($path, $arg) {
43
  switch ($path) {
44
    case 'admin/help#pathauto':
45
      module_load_include('inc', 'pathauto');
46
      $output = '<h3>' . t('About') . '</h3>';
47
      $output .= '<p>' . t('Provides a mechanism for modules to automatically generate aliases for the content they manage.') . '</p>';
48
      $output .= '<h3>' . t('Settings') . '</h3>';
49
      $output .= '<dl>';
50
      $output .= '<dt>' . t('Maximum alias and component length') . '</dt>';
51
      $output .= '<dd>' . t('The <strong>maximum alias length</strong> and <strong>maximum component length</strong> values default to 100 and have a limit of @max from Pathauto. This length is limited by the length of the "alias" column of the url_alias database table. The default database schema for this column is @max. If you set a length that is equal to that of the one set in the "alias" column it will cause problems in situations where the system needs to append additional words to the aliased URL. You should enter a value that is the length of the "alias" column minus the length of any strings that might get added to the end of the URL. The length of strings that might get added to the end of your URLs depends on which modules you have enabled and on your Pathauto settings. The recommended and default value is 100.', array('@max' => _pathauto_get_schema_alias_maxlength())) . '</dd>';
52
      $output .= '</dl>';
53
      return $output;
54
    case 'admin/config/search/path/update_bulk':
55
      $output = '<p>' . t('Bulk generation will only generate URL aliases for items that currently have no aliases. This is typically used when installing Pathauto on a site that has existing un-aliased content that needs to be aliased in bulk.') . '</p>';
56
      return $output;
57
  }
58
}
59

    
60
/**
61
 * Implements hook_permission().
62
 */
63
function pathauto_permission() {
64
  return array(
65
    'administer pathauto' => array(
66
      'title' => t('Administer pathauto'),
67
      'description' => t('Allows a user to configure patterns for automated aliases and bulk delete URL-aliases.'),
68
    ),
69
    'notify of path changes' => array(
70
      'title' => t('Notify of Path Changes'),
71
      'description' => t('Determines whether or not users are notified.'),
72
    ),
73
  );
74
}
75

    
76
/**
77
 * Implements hook_menu().
78
 */
79
function pathauto_menu() {
80
  $items['admin/config/search/path/patterns'] = array(
81
    'title' => 'Patterns',
82
    'page callback' => 'drupal_get_form',
83
    'page arguments' => array('pathauto_patterns_form'),
84
    'access arguments' => array('administer pathauto'),
85
    'type' => MENU_LOCAL_TASK,
86
    'weight' => 10,
87
    'file' => 'pathauto.admin.inc',
88
  );
89
  $items['admin/config/search/path/settings'] = array(
90
    'title' => 'Settings',
91
    'page callback' => 'drupal_get_form',
92
    'page arguments' => array('pathauto_settings_form'),
93
    'access arguments' => array('administer pathauto'),
94
    'type' => MENU_LOCAL_TASK,
95
    'weight' => 20,
96
    'file' => 'pathauto.admin.inc',
97
  );
98
  $items['admin/config/search/path/update_bulk'] = array(
99
    'title' => 'Bulk generate',
100
    'page callback' => 'drupal_get_form',
101
    'page arguments' => array('pathauto_bulk_update_form'),
102
    'access arguments' => array('administer url aliases'),
103
    'type' => MENU_LOCAL_TASK,
104
    'weight' => 30,
105
    'file' => 'pathauto.admin.inc',
106
  );
107
  $items['admin/config/search/path/delete_bulk'] = array(
108
    'title' => 'Delete aliases',
109
    'page callback' => 'drupal_get_form',
110
    'page arguments' => array('pathauto_admin_delete'),
111
    'access arguments' => array('administer url aliases'),
112
    'type' => MENU_LOCAL_TASK,
113
    'weight' => 40,
114
    'file' => 'pathauto.admin.inc',
115
  );
116

    
117
  return $items;
118
}
119

    
120
/**
121
 * Load an URL alias pattern by entity, bundle, and language.
122
 *
123
 * @param $entity
124
 *   An entity (e.g. node, taxonomy, user, etc.)
125
 * @param $bundle
126
 *   A bundle (e.g. content type, vocabulary ID, etc.)
127
 * @param $language
128
 *   A language code, defaults to the LANGUAGE_NONE constant.
129
 */
130
function pathauto_pattern_load_by_entity($entity, $bundle = '', $language = LANGUAGE_NONE) {
131
  $patterns = &drupal_static(__FUNCTION__, array());
132

    
133
  $pattern_id = "$entity:$bundle:$language";
134
  if (!isset($patterns[$pattern_id])) {
135
    $variables = array();
136
    if ($language != LANGUAGE_NONE) {
137
      $variables[] = "pathauto_{$entity}_{$bundle}_{$language}_pattern";
138
    }
139
    if ($bundle) {
140
      $variables[] = "pathauto_{$entity}_{$bundle}_pattern";
141
    }
142
    $variables[] = "pathauto_{$entity}_pattern";
143

    
144
    foreach ($variables as $variable) {
145
      if ($pattern = trim(variable_get($variable, ''))) {
146
        break;
147
      }
148
    }
149

    
150
    $patterns[$pattern_id] = $pattern;
151
  }
152

    
153
  return $patterns[$pattern_id];
154
}
155

    
156
/**
157
 * Delete multiple URL aliases.
158
 *
159
 * Intent of this is to abstract a potential path_delete_multiple() function
160
 * for Drupal 7 or 8.
161
 *
162
 * @param $pids
163
 *   An array of path IDs to delete.
164
 */
165
function pathauto_path_delete_multiple($pids) {
166
  foreach ($pids as $pid) {
167
    path_delete(array('pid' => $pid));
168
  }
169
}
170

    
171
/**
172
 * Delete an URL alias and any of its sub-paths.
173
 *
174
 * Given a source like 'node/1' this function will delete any alias that have
175
 * that specific source or any sources that match 'node/1/%'.
176
 *
177
 * @param $source
178
 *   An string with a source URL path.
179
 */
180
function pathauto_path_delete_all($source) {
181
  $sql = "SELECT pid FROM {url_alias} WHERE source = :source OR source LIKE :source_wildcard";
182
  $pids = db_query($sql, array(':source' => $source, ':source_wildcard' => $source . '/%'))->fetchCol();
183
  if ($pids) {
184
    pathauto_path_delete_multiple($pids);
185
  }
186
}
187

    
188
/**
189
 * Delete an entity URL alias and any of its sub-paths.
190
 *
191
 * This function also checks to see if the default entity URI is different from
192
 * the current entity URI and will delete any of the default aliases.
193
 *
194
 * @param $entity_type
195
 *   A string with the entity type.
196
 * @param $entity
197
 *   An entity object.
198
 * @param $default_uri
199
 *   The optional default uri path for the entity.
200
 */
201
function pathauto_entity_path_delete_all($entity_type, $entity, $default_uri = NULL) {
202
  $uri = entity_uri($entity_type, $entity);
203
  pathauto_path_delete_all($uri['path']);
204
  if (isset($default_uri) && $uri['path'] != $default_uri) {
205
    pathauto_path_delete_all($default_uri);
206
  }
207
}
208

    
209
/**
210
 * Implements hook_field_attach_rename_bundle().
211
 *
212
 * Respond to machine name changes for pattern variables.
213
 */
214
function pathauto_field_attach_rename_bundle($entity_type, $bundle_old, $bundle_new) {
215
  $variables = db_select('variable', 'v')
216
    ->fields('v', array('name'))
217
    ->condition('name', db_like("pathauto_{$entity_type}_{$bundle_old}_") . '%', 'LIKE')
218
    ->execute()
219
    ->fetchCol();
220
  foreach ($variables as $variable) {
221
    $value = variable_get($variable, '');
222
    variable_del($variable);
223
    $variable = strtr($variable, array("{$entity_type}_{$bundle_old}" => "{$entity_type}_{$bundle_new}"));
224
    variable_set($variable, $value);
225
  }
226
}
227

    
228
/**
229
 * Implements hook_field_attach_delete_bundle().
230
 *
231
 * Respond to sub-types being deleted, their patterns can be removed.
232
 */
233
function pathauto_field_attach_delete_bundle($entity_type, $bundle) {
234
  $variables = db_select('variable', 'v')
235
    ->fields('v', array('name'))
236
    ->condition('name', db_like("pathauto_{$entity_type}_{$bundle}_") . '%', 'LIKE')
237
    ->execute()
238
    ->fetchCol();
239
  foreach ($variables as $variable) {
240
    variable_del($variable);
241
  }
242
}
243

    
244
/**
245
 * Implements hook_field_attach_form().
246
 *
247
 * Add the automatic alias form elements to an existing path form fieldset.
248
 */
249
function pathauto_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) {
250
  list($id, , $bundle) = entity_extract_ids($entity_type, $entity);
251

    
252
  if (!isset($form['path'])) {
253
    // This entity must be supported by core's path.module first.
254
    // @todo Investigate removing this and supporting all fieldable entities.
255
    return;
256
  }
257
  else {
258
    // Taxonomy terms do not have an actual fieldset for path settings.
259
    // Merge in the defaults.
260
    $form['path'] += array(
261
      '#type' => 'fieldset',
262
      '#title' => t('URL path settings'),
263
      '#collapsible' => TRUE,
264
      '#collapsed' => empty($form['path']['alias']),
265
      '#group' => 'additional_settings',
266
      '#attributes' => array(
267
        'class' => array('path-form'),
268
      ),
269
      '#access' => user_access('create url aliases') || user_access('administer url aliases'),
270
      '#weight' => 30,
271
      '#tree' => TRUE,
272
      '#element_validate' => array('path_form_element_validate'),
273
    );
274
  }
275

    
276
  $pattern = pathauto_pattern_load_by_entity($entity_type, $bundle, $langcode);
277
  if (empty($pattern)) {
278
    return;
279
  }
280

    
281
  if (!isset($entity->path['pathauto'])) {
282
    if (!empty($id)) {
283
      module_load_include('inc', 'pathauto');
284
      $uri = entity_uri($entity_type, $entity);
285
      $pathauto_alias = pathauto_create_alias($entity_type, 'return', $uri['path'], array($entity_type => $entity), $bundle, $langcode);
286
      if ($pathauto_alias === FALSE) {
287
        // If Pathauto is not going to be able to generate an alias, then we
288
        // should not bother to show the checkbox since it wouldn't do anything.
289
        // Note that if a pattern does apply, but all the tokens currently
290
        // evaluate to empty strings, then $pathauto_alias would equal null and
291
        // not false.
292
        return;
293
      }
294
      else {
295
        $path = drupal_get_path_alias($uri['path'], $langcode);
296
        $entity->path['pathauto'] = ($path != $uri['path'] && $path == $pathauto_alias);
297
      }
298
    }
299
    else {
300
      $entity->path['pathauto'] = TRUE;
301
    }
302
  }
303

    
304
  // Add JavaScript that will disable the path textfield when the automatic
305
  // alias checkbox is checked.
306
  $form['path']['alias']['#states']['!enabled']['input[name="path[pathauto]"]'] = array('checked' => TRUE);
307

    
308
  // Override path.module's vertical tabs summary.
309
  $form['path']['#attached']['js'] = array(
310
    'vertical-tabs' => drupal_get_path('module', 'pathauto') . '/pathauto.js'
311
  );
312

    
313
  $form['path']['pathauto'] = array(
314
    '#type' => 'checkbox',
315
    '#title' => t('Generate automatic URL alias'),
316
    '#default_value' => $entity->path['pathauto'],
317
    '#description' => t('Uncheck this to create a custom alias below.'),
318
    '#weight' => -1,
319
  );
320

    
321
  // Add a shortcut link to configure URL alias patterns.
322
  if (drupal_valid_path('admin/config/search/path/patterns')) {
323
    $form['path']['pathauto']['#description'] .= ' ' . l(t('Configure URL alias patterns.'), 'admin/config/search/path/patterns');
324
  }
325

    
326
  if ($entity->path['pathauto'] && !empty($entity->old_alias) && empty($entity->path['alias'])) {
327
    $form['path']['alias']['#default_value'] = $entity->old_alias;
328
    $entity->path['alias'] = $entity->old_alias;
329
  }
330

    
331
  // For Pathauto to remember the old alias and prevent the Path module from
332
  // deleting it when Pathauto wants to preserve it.
333
  if (!empty($entity->path['alias'])) {
334
    $form['path']['old_alias'] = array(
335
      '#type' => 'value',
336
      '#value' => $entity->path['alias'],
337
    );
338
  }
339
}
340

    
341
/**
342
 * Implements hook_entity_load().
343
 */
344
function pathauto_entity_load($entities, $entity_type) {
345
  // Statically cache which entity types have data in the pathauto_state
346
  // table to avoid unnecessary queries for entities that would not have any
347
  // data anyway.
348
  static $loadable_types;
349
  if (!isset($loadable_types)) {
350
    $loadable_types = &drupal_static(__FUNCTION__);
351
    if (!isset($loadable_types)) {
352
      // Prevent errors if pathauto_update_7006() has not yet been run.
353
      if (!db_table_exists('pathauto_state')) {
354
        $loadable_types = array();
355
      }
356
      else {
357
        $loadable_types = db_query("SELECT DISTINCT entity_type FROM {pathauto_state}")->fetchCol();
358
      }
359
    }
360
  }
361

    
362
  // Check if this entity type has loadable records.
363
  if (!in_array($entity_type, $loadable_types)) {
364
    return;
365
  }
366

    
367
  $states = pathauto_entity_state_load_multiple($entity_type, array_keys($entities));
368
  foreach ($states as $id => $state) {
369
    if (!isset($entities[$id]->path)) {
370
      $entities[$id]->path = array();
371
    }
372

    
373
    if (is_array($entities[$id]->path) && !isset($entities[$id]->path['pathauto'])) {
374
      $entities[$id]->path['pathauto'] = $state;
375
    }
376
  }
377
}
378

    
379
/**
380
 * Implements hook_entity_presave().
381
 */
382
function pathauto_entity_presave($entity, $entity_type) {
383
  if (isset($entity->path['pathauto']) && is_array($entity->path)) {
384
    // We must set an empty alias string for the path to prevent saving an
385
    // alias.
386
    $entity->path += array('alias' => '');
387
  }
388

    
389
  // About to be saved (before insert/update)
390
  if (!empty($entity->path['pathauto']) && isset($entity->path['old_alias'])
391
      && $entity->path['alias'] == '' && $entity->path['old_alias'] != '') {
392
    /**
393
     * There was an old alias, but when pathauto_perform_alias was checked
394
     * the javascript disabled the textbox which led to an empty value being
395
     * submitted. Restoring the old path-value here prevents the Path module
396
     * from deleting any old alias before Pathauto gets control.
397
     */
398
    $entity->path['alias'] = $entity->path['old_alias'];
399
  }
400

    
401
  // Help prevent errors with progromatically creating entities by defining
402
  // path['alias'] as an empty string.
403
  // @see http://drupal.org/node/1328180
404
  // @see http://drupal.org/node/1576552
405
  if (isset($entity->path['pathauto']) && !isset($entity->path['alias'])) {
406
    $entity->path['alias'] = '';
407
  }
408
}
409

    
410
/**
411
 * Implements hook_entity_insert().
412
 */
413
function pathauto_entity_insert($entity, $entity_type) {
414
  if (isset($entity->path['pathauto'])) {
415
    pathauto_entity_state_save($entity_type, $entity, $entity->path['pathauto']);
416
  }
417
}
418

    
419
/**
420
 * Implements hook_entity_update().
421
 */
422
function pathauto_entity_update($entity, $entity_type) {
423
  if (isset($entity->path['pathauto'])) {
424
    pathauto_entity_state_save($entity_type, $entity, $entity->path['pathauto']);
425
  }
426
}
427

    
428
/**
429
 * Implements hook_entity_delete().
430
 */
431
function pathauto_entity_delete($entity, $entity_type) {
432
  if (isset($entity->path['pathauto'])) {
433
    pathauto_entity_state_delete($entity_type, $entity);
434
  }
435
}
436

    
437
/**
438
 * Load a pathauto state for an entity.
439
 *
440
 * @param string $entity_type
441
 *   An entity type.
442
 * @param int $entity_id
443
 *   An entity ID.
444
 *
445
 * @return bool
446
 *   A value that evaluates to TRUE if Pathauto should control this entity's
447
 *   path. A value that evaluates to FALSE if Pathauto should not manage the
448
 *   entity's path.
449
 */
450
function pathauto_entity_state_load($entity_type, $entity_id) {
451
  $pathauto_state = pathauto_entity_state_load_multiple($entity_type, array($entity_id));
452
  return !empty($pathauto_state) ? reset($pathauto_state) : FALSE;
453
}
454

    
455
/**
456
 * Load a pathauto state for multiple entities.
457
 *
458
 * @param string $entity_type
459
 *   The entity type.
460
 * @param int[] $entity_ids
461
 *   The array of entity IDs.
462
 *
463
 * @return bool[]
464
 *   An array of Pathauto states keyed by entity ID.
465
 */
466
function pathauto_entity_state_load_multiple($entity_type, $entity_ids) {
467
  return db_query("SELECT entity_id, pathauto FROM {pathauto_state} WHERE entity_type = :entity_type AND entity_id IN (:entity_ids)", array(':entity_type' => $entity_type, ':entity_ids' => $entity_ids))->fetchAllKeyed();
468
}
469

    
470
/**
471
 * Save the pathauto state for an entity.
472
 *
473
 * @param string $entity_type
474
 *   The entity type.
475
 * @param object $entity
476
 *   The entity object.
477
 * @param bool $pathauto_state
478
 *   A value that evaluates to TRUE means that Pathauto should keep controlling
479
 *   this entity's path in the future. A value that evaluates to FALSE means
480
 *   that Pathauto should not manage the entity's path.
481
 */
482
function pathauto_entity_state_save($entity_type, $entity, $pathauto_state) {
483
  list($entity_id) = entity_extract_ids($entity_type, $entity);
484
  db_merge('pathauto_state')
485
    ->key(array(
486
      'entity_type' => $entity_type,
487
      'entity_id' => $entity_id,
488
    ))
489
    ->fields(array(
490
      'pathauto' => $pathauto_state ? 1 : 0,
491
    ))
492
    ->execute();
493
  drupal_static_reset('pathauto_entity_load');
494
}
495

    
496
/**
497
 * Delete the pathauto state for an entity.
498
 *
499
 * @param string $entity_type
500
 *   The entity type.
501
 * @param object $entity
502
 *   The entity object.
503
 */
504
function pathauto_entity_state_delete($entity_type, $entity) {
505
  list($entity_id) = entity_extract_ids($entity_type, $entity);
506
  db_delete('pathauto_state')
507
    ->condition('entity_type', $entity_type)
508
    ->condition('entity_id', $entity_id)
509
    ->execute();
510
  drupal_static_reset('pathauto_entity_load');
511
}
512

    
513
/**
514
 * Implements hook_action_info().
515
 */
516
function pathauto_action_info() {
517
  $info['pathauto_node_update_action'] = array(
518
    'type' => 'node',
519
    'label' => t('Update node alias'),
520
    'configurable' => FALSE,
521
    'triggers' => array(),
522
  );
523
  $info['pathauto_taxonomy_term_update_action'] = array(
524
    'type' => 'taxonomy_term',
525
    'label' => t('Update taxonomy term alias'),
526
    'configurable' => FALSE,
527
    'triggers' => array(),
528
  );
529
  $info['pathauto_user_update_action'] = array(
530
    'type' => 'user',
531
    'label' => t('Update user alias'),
532
    'configurable' => FALSE,
533
    'triggers' => array(),
534
  );
535

    
536
  return $info;
537
}
538

    
539
/**
540
 * Returns the language code of the given entity.
541
 *
542
 * Backward compatibility layer to ensure that installations running an older
543
 * version of core where entity_language() is not available do not break.
544
 *
545
 * @param string $entity_type
546
 *   An entity type.
547
 * @param object $entity
548
 *   An entity object.
549
 * @param bool $check_language_property
550
 *   A boolean if TRUE, will attempt to fetch the language code from
551
 *   $entity->language if the entity_language() function failed or does not
552
 *   exist. Default is TRUE.
553
 */
554
function pathauto_entity_language($entity_type, $entity, $check_language_property = TRUE) {
555
  $langcode = NULL;
556

    
557
  if (function_exists('entity_language')) {
558
    $langcode = entity_language($entity_type, $entity);
559
  }
560
  elseif ($check_language_property && !empty($entity->language)) {
561
    $langcode = $entity->language;
562
  }
563

    
564
  return !empty($langcode) ? $langcode : LANGUAGE_NONE;
565
}
566

    
567
function pathauto_is_alias_reserved($alias, $source, $langcode = LANGUAGE_NONE) {
568
  foreach (module_implements('pathauto_is_alias_reserved') as $module) {
569
    $result = module_invoke($module, 'pathauto_is_alias_reserved', $alias, $source, $langcode);
570
    if (!empty($result)) {
571
      // As soon as the first module says that an alias is in fact reserved,
572
      // then there is no point in checking the rest of the modules.
573
      return TRUE;
574
    }
575
  }
576

    
577
  return FALSE;
578
}
579

    
580
/**
581
 * Implements hook_pathauto_is_alias_reserved() on behalf of path.module.
582
 */
583
function path_pathauto_is_alias_reserved($alias, $source, $langcode) {
584
  // For language neutral content, we need to make sure the alias doesn't
585
  // collide with any existing aliases. For localized content, just make sure
586
  // it doesn't collide with same language or language neutral aliases.
587
  $query = db_select('url_alias', 'ua')
588
         ->fields('ua', array('pid'))
589
         ->condition('source', $source, '<>')
590
         ->condition('alias', $alias);
591

    
592
  if ($langcode != LANGUAGE_NONE) {
593
    $query->condition('language', array($langcode, LANGUAGE_NONE), 'IN');
594
  }
595

    
596
  return $query->execute()->rowCount() > 0;
597
}
598

    
599
/**
600
 * Implements hook_pathauto_is_alias_reserved().
601
 */
602
function pathauto_pathauto_is_alias_reserved($alias, $source, $langcode) {
603
  module_load_include('inc', 'pathauto');
604
  return _pathauto_path_is_callback($alias);
605
}
606

    
607
if (!function_exists('path_field_extra_fields')) {
608
/**
609
 * Implements hook_field_extra_fields() on behalf of path.module.
610
 *
611
 * Add support for the 'URL path settings' to be re-ordered by the user on the
612
 * 'Manage Fields' tab of content types and vocabularies.
613
 */
614
function path_field_extra_fields() {
615
  $info = array();
616

    
617
  foreach (node_type_get_types() as $node_type) {
618
    if (!isset($info['node'][$node_type->type]['form']['path'])) {
619
      $info['node'][$node_type->type]['form']['path'] = array(
620
        'label' => t('URL path settings'),
621
        'description' => t('Path module form elements'),
622
        'weight' => 30,
623
      );
624
    }
625
  }
626

    
627
  if (module_exists('taxonomy')) {
628
    $vocabularies = taxonomy_get_vocabularies();
629
    foreach ($vocabularies as $vocabulary) {
630
      if (!isset($info['taxonomy_term'][$vocabulary->machine_name]['form']['path'])) {
631
        $info['taxonomy_term'][$vocabulary->machine_name]['form']['path'] = array(
632
          'label' => t('URL path settings'),
633
          'description' => t('Path module form elements'),
634
          'weight' => 30,
635
        );
636
      }
637
    }
638
  }
639

    
640
  return $info;
641
}
642
}
643

    
644
/**
645
 * @name pathauto_node Pathauto integration for the core node module.
646
 * @{
647
 */
648

    
649
/**
650
 * Implements hook_path_alias_types() on behalf of node module.
651
 */
652
function node_path_alias_types() {
653
  return array('node/' => t('Content'));
654
}
655

    
656
/**
657
 * Implements hook_pathauto() on behalf of node module.
658
 */
659
function node_pathauto($op) {
660
  if ($op == 'settings') {
661
    $settings = array();
662
    $settings['module'] = 'node';
663
    $settings['token_type'] = 'node';
664
    $settings['groupheader'] = t('Content paths');
665
    $settings['patterndescr'] = t('Default path pattern (applies to all content types with blank patterns below)');
666
    $settings['patterndefault'] = 'content/[node:title]';
667
    $settings['batch_update_callback'] = 'node_pathauto_bulk_update_batch_process';
668
    $settings['batch_file'] = drupal_get_path('module', 'pathauto') . '/pathauto.pathauto.inc';
669

    
670
    $languages = array();
671
    if (module_exists('locale')) {
672
      $languages = array(LANGUAGE_NONE => t('language neutral')) + locale_language_list('name');
673
    }
674

    
675
    foreach (node_type_get_names() as $node_type => $node_name) {
676
      if (count($languages) && variable_get('language_content_type_' . $node_type, 0)) {
677
        $settings['patternitems'][$node_type] = t('Default path pattern for @node_type (applies to all @node_type content types with blank patterns below)', array('@node_type' => $node_name));
678
        foreach ($languages as $lang_code => $lang_name) {
679
          $settings['patternitems'][$node_type . '_' . $lang_code] = t('Pattern for all @language @node_type paths', array('@node_type' => $node_name, '@language' => $lang_name));
680
        }
681
      }
682
      else {
683
        $settings['patternitems'][$node_type] = t('Pattern for all @node_type paths', array('@node_type' => $node_name));
684
      }
685
    }
686
    return (object) $settings;
687
  }
688
}
689

    
690
/**
691
 * Implements hook_node_insert().
692
 */
693
function pathauto_node_insert($node) {
694
  // @todo Remove the next line when http://drupal.org/node/1025870 is fixed.
695
  unset($node->uri);
696
  pathauto_node_update_alias($node, 'insert');
697
}
698

    
699
/**
700
 * Implements hook_node_update().
701
 */
702
function pathauto_node_update($node) {
703
  pathauto_node_update_alias($node, 'update');
704
}
705

    
706
/**
707
 * Implements hook_node_delete().
708
 */
709
function pathauto_node_delete($node) {
710
  pathauto_entity_path_delete_all('node', $node, "node/{$node->nid}");
711
}
712

    
713
/**
714
 * Implements hook_form_BASE_FORM_ID_alter().
715
 *
716
 * Add the Pathauto settings to the node form.
717
 */
718
function pathauto_form_node_form_alter(&$form, &$form_state) {
719
  $node = $form_state['node'];
720
  $langcode = pathauto_entity_language('node', $node);
721
  pathauto_field_attach_form('node', $node, $form, $form_state, $langcode);
722
}
723

    
724
/**
725
 * Implements hook_node_operations().
726
 */
727
function pathauto_node_operations() {
728
  $operations['pathauto_update_alias'] = array(
729
    'label' => t('Update URL alias'),
730
    'callback' => 'pathauto_node_update_alias_multiple',
731
    'callback arguments' => array('bulkupdate', array('message' => TRUE)),
732
  );
733
  return $operations;
734
}
735

    
736
/**
737
 * Update the URL aliases for an individual node.
738
 *
739
 * @param $node
740
 *   A node object.
741
 * @param $op
742
 *   Operation being performed on the node ('insert', 'update' or 'bulkupdate').
743
 * @param $options
744
 *   An optional array of additional options.
745
 */
746
function pathauto_node_update_alias(stdClass $node, $op, array $options = array()) {
747
  // Skip processing if the user has disabled pathauto for the node.
748
  if (isset($node->path['pathauto']) && empty($node->path['pathauto']) && empty($options['force'])) {
749
    return FALSE;
750
  }
751

    
752
  $options += array('language' => pathauto_entity_language('node', $node));
753

    
754
  // Skip processing if the node has no pattern.
755
  if (!pathauto_pattern_load_by_entity('node', $node->type, $options['language'])) {
756
    return FALSE;
757
  }
758

    
759
  module_load_include('inc', 'pathauto');
760
  $uri = entity_uri('node', $node);
761
  return pathauto_create_alias('node', $op, $uri['path'], array('node' => $node), $node->type, $options['language']);
762
}
763

    
764
/**
765
 * Update the URL aliases for multiple nodes.
766
 *
767
 * @param $nids
768
 *   An array of node IDs.
769
 * @param $op
770
 *   Operation being performed on the nodes ('insert', 'update' or
771
 *   'bulkupdate').
772
 * @param $options
773
 *   An optional array of additional options.
774
 */
775
function pathauto_node_update_alias_multiple(array $nids, $op, array $options = array()) {
776
  $options += array('message' => FALSE);
777

    
778
  $nodes = node_load_multiple($nids);
779
  foreach ($nodes as $node) {
780
    pathauto_node_update_alias($node, $op, $options);
781
  }
782

    
783
  if (!empty($options['message'])) {
784
    drupal_set_message(format_plural(count($nids), 'Updated URL alias for 1 node.', 'Updated URL aliases for @count nodes.'));
785
  }
786
}
787

    
788
/**
789
 * Update action wrapper for pathauto_node_update_alias().
790
 */
791
function pathauto_node_update_action($node, $context = array()) {
792
  pathauto_node_update_alias($node, 'bulkupdate', array('message' => TRUE));
793
}
794

    
795
/**
796
 * @} End of "name pathauto_node".
797
 */
798

    
799
/**
800
 * @name pathauto_taxonomy Pathauto integration for the core taxonomy module.
801
 * @{
802
 */
803

    
804
/**
805
 * Implements hook_path_alias_types() on behalf of taxonomy module.
806
 */
807
function taxonomy_path_alias_types() {
808
  return array('taxonomy/term/' => t('Taxonomy terms'));
809
}
810

    
811
/**
812
 * Implements hook_pathauto() on behalf of taxonomy module.
813
 */
814
function taxonomy_pathauto($op) {
815
  if ($op == 'settings') {
816
    $settings = array();
817
    $settings['module'] = 'taxonomy_term';
818
    $settings['token_type'] = 'term';
819
    $settings['groupheader'] = t('Taxonomy term paths');
820
    $settings['patterndescr'] = t('Default path pattern (applies to all vocabularies with blank patterns below)');
821
    $settings['patterndefault'] = '[term:vocabulary]/[term:name]';
822
    $settings['batch_update_callback'] = 'taxonomy_pathauto_bulk_update_batch_process';
823
    $settings['batch_file'] = drupal_get_path('module', 'pathauto') . '/pathauto.pathauto.inc';
824

    
825
    $vocabularies = taxonomy_get_vocabularies();
826
    if (count($vocabularies)) {
827
      $settings['patternitems'] = array();
828
      foreach ($vocabularies as $vid => $vocabulary) {
829
        if ($vid == variable_get('forum_nav_vocabulary', '')) {
830
          // Skip the forum vocabulary.
831
          continue;
832
        }
833
        $settings['patternitems'][$vocabulary->machine_name] = t('Pattern for all %vocab-name paths', array('%vocab-name' => $vocabulary->name));
834
      }
835
    }
836
    return (object) $settings;
837
  }
838
}
839

    
840
/**
841
 * Implements hook_taxonomy_term_insert().
842
 */
843
function pathauto_taxonomy_term_insert($term) {
844
  pathauto_taxonomy_term_update_alias($term, 'insert');
845
}
846

    
847
/**
848
 * Implements hook_taxonomy_term_update().
849
 */
850
function pathauto_taxonomy_term_update($term) {
851
  pathauto_taxonomy_term_update_alias($term, 'update', array('alias children' => TRUE));
852
}
853

    
854
/**
855
 * Implements hook_taxonomy_term_delete().
856
 */
857
function pathauto_taxonomy_term_delete($term) {
858
  pathauto_entity_path_delete_all('taxonomy_term', $term, "taxonomy/term/{$term->tid}");
859
}
860

    
861
/**
862
 * Implements hook_form_FORM_ID_alter().
863
 *
864
 * Add the Pathauto settings to the taxonomy term form.
865
 */
866
function pathauto_form_taxonomy_form_term_alter(&$form, $form_state) {
867
  $term = $form_state['term'];
868
  $langcode = pathauto_entity_language('taxonomy_term', $term);
869
  pathauto_field_attach_form('taxonomy_term', $term, $form, $form_state, $langcode);
870
}
871

    
872
/**
873
 * Update the URL aliases for an individual taxonomy term.
874
 *
875
 * @param $term
876
 *   A taxonomy term object.
877
 * @param $op
878
 *   Operation being performed on the term ('insert', 'update' or 'bulkupdate').
879
 * @param $options
880
 *   An optional array of additional options.
881
 */
882
function pathauto_taxonomy_term_update_alias(stdClass $term, $op, array $options = array()) {
883
  // Skip processing if the user has disabled pathauto for the term.
884
  if (isset($term->path['pathauto']) && empty($term->path['pathauto']) && empty($options['force'])) {
885
    return FALSE;
886
  }
887

    
888
  $module = 'taxonomy_term';
889
  if ($term->vid == variable_get('forum_nav_vocabulary', '')) {
890
    if (module_exists('forum')) {
891
      $module = 'forum';
892
    }
893
    else {
894
      return FALSE;
895
    }
896
  }
897

    
898
  // Check that the term has its bundle, which is the vocabulary's machine name.
899
  if (!isset($term->vocabulary_machine_name)) {
900
    $vocabulary = taxonomy_vocabulary_load($term->vid);
901
    $term->vocabulary_machine_name = $vocabulary->machine_name;
902
  }
903

    
904
  $options += array(
905
    'alias children' => FALSE,
906
    'language' => pathauto_entity_language('taxonomy_term', $term),
907
  );
908

    
909
  // Skip processing if the term has no pattern.
910
  if (!pathauto_pattern_load_by_entity($module, $term->vocabulary_machine_name)) {
911
    return FALSE;
912
  }
913

    
914
  module_load_include('inc', 'pathauto');
915
  $uri = entity_uri('taxonomy_term', $term);
916
  $result = pathauto_create_alias($module, $op, $uri['path'], array('term' => $term), $term->vocabulary_machine_name, $options['language']);
917

    
918
  if (!empty($options['alias children'])) {
919
    // For all children generate new aliases.
920
    unset($options['language']);
921
    foreach (taxonomy_get_children($term->tid, $term->vid) as $subterm) {
922
      pathauto_taxonomy_term_update_alias($subterm, $op, $options);
923
    }
924
  }
925

    
926
  return $result;
927
}
928

    
929
/**
930
 * Update the URL aliases for multiple taxonomy terms.
931
 *
932
 * @param $tids
933
 *   An array of term IDs.
934
 * @param $op
935
 *   Operation being performed on the nodes ('insert', 'update' or
936
 *   'bulkupdate').
937
 * @param $options
938
 *   An optional array of additional options.
939
 */
940
function pathauto_taxonomy_term_update_alias_multiple(array $tids, $op, array $options = array()) {
941
  $options += array('message' => FALSE);
942

    
943
  $terms = taxonomy_term_load_multiple($tids);
944
  foreach ($terms as $term) {
945
    pathauto_taxonomy_term_update_alias($term, $op, $options);
946
  }
947

    
948
  if (!empty($options['message'])) {
949
    drupal_set_message(format_plural(count($tids), 'Updated URL alias for 1 term.', 'Updated URL aliases for @count terms.'));
950
  }
951
}
952

    
953
/**
954
 * Update action wrapper for pathauto_taxonomy_term_update_alias().
955
 */
956
function pathauto_taxonomy_term_update_action($term, $context = array()) {
957
  pathauto_taxonomy_term_update_alias($term, 'bulkupdate', array('message' => TRUE));
958
}
959

    
960
/**
961
 * @} End of "name pathauto_taxonomy".
962
 */
963

    
964
/**
965
 * @name pathauto_forum Pathauto integration for the core forum module.
966
 * @{
967
 */
968

    
969
/**
970
 * Implements hook_path_alias_types() on behalf of forum module.
971
 */
972
function forum_path_alias_types() {
973
  return array('forum/' => t('Forums'));
974
}
975

    
976
/**
977
 * Implements hook_pathauto() for forum module.
978
 */
979
function forum_pathauto($op) {
980
  if ($op == 'settings') {
981
    $settings = array();
982
    $settings['module'] = 'forum';
983
    $settings['token_type'] = 'term';
984
    $settings['groupheader'] = t('Forum paths');
985
    $settings['patterndescr'] = t('Pattern for forums and forum containers');
986
    $settings['patterndefault'] = '[term:vocabulary]/[term:name]';
987
    $settings['batch_update_callback'] = 'forum_pathauto_bulk_update_batch_process';
988
    $settings['batch_file'] = drupal_get_path('module', 'pathauto') . '/pathauto.pathauto.inc';
989
    return (object) $settings;
990
  }
991
}
992

    
993
/**
994
 * @} End of "name pathauto_forum".
995
 */
996

    
997
/**
998
 * @name pathauto_user Pathauto integration for the core user and blog modules.
999
 * @{
1000
 */
1001

    
1002
/**
1003
 * Implements hook_path_alias_types() on behalf of user module.
1004
 */
1005
function user_path_alias_types() {
1006
  return array('user/' => t('Users'));
1007
}
1008

    
1009
/**
1010
 * Implements hook_pathauto() on behalf of user module.
1011
 */
1012
function user_pathauto($op) {
1013
  if ($op == 'settings') {
1014
    $settings = array();
1015
    $settings['module'] = 'user';
1016
    $settings['token_type'] = 'user';
1017
    $settings['groupheader'] = t('User paths');
1018
    $settings['patterndescr'] = t('Pattern for user account page paths');
1019
    $settings['patterndefault'] = 'users/[user:name]';
1020
    $settings['batch_update_callback'] = 'user_pathauto_bulk_update_batch_process';
1021
    $settings['batch_file'] = drupal_get_path('module', 'pathauto') . '/pathauto.pathauto.inc';
1022
    return (object) $settings;
1023
  }
1024
}
1025

    
1026
/**
1027
 * Implements hook_user_insert().
1028
 */
1029
function pathauto_user_insert(&$edit, $account, $category) {
1030
  pathauto_user_update_alias($account, 'insert');
1031
}
1032

    
1033
/**
1034
 * Implements hook_user_update().
1035
 */
1036
function pathauto_user_update(&$edit, $account, $category) {
1037
  pathauto_user_update_alias($account, 'update');
1038
}
1039

    
1040
/**
1041
 * Implements hook_user_delete().
1042
 */
1043
function pathauto_user_delete($account) {
1044
  pathauto_entity_path_delete_all('user', $account, "user/{$account->uid}");
1045
  pathauto_path_delete_all("blog/{$account->uid}");
1046
}
1047

    
1048
/**
1049
 * Implements hook_user_operations().
1050
 */
1051
function pathauto_user_operations() {
1052
  $operations['pathauto_update_alias'] = array(
1053
    'label' => t('Update URL alias'),
1054
    'callback' => 'pathauto_user_update_alias_multiple',
1055
    'callback arguments' => array('bulkupdate', array('message' => TRUE)),
1056
  );
1057
  return $operations;
1058
}
1059

    
1060
/**
1061
 * Update the URL aliases for an individual user account.
1062
 *
1063
 * @param $account
1064
 *   A user account object.
1065
 * @param $op
1066
 *   Operation being performed on the account ('insert', 'update' or
1067
 *   'bulkupdate').
1068
 * @param $options
1069
 *   An optional array of additional options.
1070
 */
1071
function pathauto_user_update_alias(stdClass $account, $op, array $options = array()) {
1072
  // Skip processing if the user has disabled pathauto for the account.
1073
  if (isset($account->path['pathauto']) && empty($account->path['pathauto']) && empty($options['force'])) {
1074
    return FALSE;
1075
  }
1076

    
1077
  $options += array(
1078
    'alias blog' => module_exists('blog'),
1079
    // $user->language is not the user entity language, thus we need to skip
1080
    // the property fallback check.
1081
    'language' => pathauto_entity_language('user', $account, FALSE),
1082
  );
1083

    
1084
  // Skip processing if the account has no pattern.
1085
  if (!pathauto_pattern_load_by_entity('user', '', $options['language'])) {
1086
    return FALSE;
1087
  }
1088

    
1089
  module_load_include('inc', 'pathauto');
1090
  $uri = entity_uri('user', $account);
1091
  $return = pathauto_create_alias('user', $op, $uri['path'], array('user' => $account), NULL, $options['language']);
1092

    
1093
  // Because blogs are also associated with users, also generate the blog paths.
1094
  if (!empty($options['alias blog'])) {
1095
    pathauto_blog_update_alias($account, $op, $options);
1096
  }
1097

    
1098
  return $return;
1099
}
1100

    
1101
/**
1102
 * Update the URL aliases for multiple user accounts.
1103
 *
1104
 * @param $uids
1105
 *   An array of user account IDs.
1106
 * @param $op
1107
 *   Operation being performed on the accounts ('insert', 'update' or
1108
 *   'bulkupdate').
1109
 * @param $options
1110
 *   An optional array of additional options.
1111
 */
1112
function pathauto_user_update_alias_multiple(array $uids, $op, array $options = array()) {
1113
  $options += array('message' => FALSE);
1114

    
1115
  $accounts = user_load_multiple($uids);
1116
  foreach ($accounts as $account) {
1117
    pathauto_user_update_alias($account, $op, $options);
1118
  }
1119

    
1120
  if (!empty($options['message'])) {
1121
    drupal_set_message(format_plural(count($uids), 'Updated URL alias for 1 user account.', 'Updated URL aliases for @count user accounts.'));
1122
  }
1123
}
1124

    
1125
/**
1126
 * Update action wrapper for pathauto_user_update_alias().
1127
 */
1128
function pathauto_user_update_action($account, $context = array()) {
1129
  pathauto_user_update_alias($account, 'bulkupdate', array('message' => TRUE));
1130
}
1131

    
1132
/**
1133
 * @} End of "name pathauto_user".
1134
 */
1135

    
1136
/**
1137
 * @name pathauto_blog Pathauto integration for the core blog module.
1138
 * @{
1139
 */
1140

    
1141
/**
1142
 * Implements hook_path_alias_types() on behalf of blog module.
1143
 */
1144
function blog_path_alias_types() {
1145
  return array('blog/' => t('User blogs'));
1146
}
1147

    
1148
/**
1149
 * Implements hook_pathauto() on behalf of blog module.
1150
 */
1151
function blog_pathauto($op) {
1152
  if ($op == 'settings') {
1153
    $settings = array();
1154
    $settings['module'] = 'blog';
1155
    $settings['token_type'] = 'user';
1156
    $settings['groupheader'] = t('Blog paths');
1157
    $settings['patterndescr'] = t('Pattern for blog page paths');
1158
    $settings['patterndefault'] = 'blogs/[user:name]';
1159
    $settings['batch_update_callback'] = 'blog_pathauto_bulk_update_batch_process';
1160
    $settings['batch_file'] = drupal_get_path('module', 'pathauto') . '/pathauto.pathauto.inc';
1161
    return (object) $settings;
1162
  }
1163
}
1164

    
1165
/**
1166
 * Update the blog URL aliases for an individual user account.
1167
 *
1168
 * @param $account
1169
 *   A user account object.
1170
 * @param $op
1171
 *   Operation being performed on the blog ('insert', 'update' or
1172
 *   'bulkupdate').
1173
 * @param $options
1174
 *   An optional array of additional options.
1175
 */
1176
function pathauto_blog_update_alias(stdClass $account, $op, array $options = array()) {
1177
  // Skip processing if the blog has no pattern.
1178
  if (!pathauto_pattern_load_by_entity('blog')) {
1179
    return FALSE;
1180
  }
1181

    
1182
  $options += array(
1183
    'language' => LANGUAGE_NONE,
1184
  );
1185

    
1186
  module_load_include('inc', 'pathauto');
1187
  if (node_access('create', 'blog', $account)) {
1188
    return pathauto_create_alias('blog', $op, "blog/{$account->uid}", array('user' => $account), NULL, $options['language']);
1189
  }
1190
  else {
1191
    pathauto_path_delete_all("blog/{$account->uid}");
1192
  }
1193
}
1194

    
1195
/**
1196
 * @} End of "name pathauto_blog".
1197
 */
1198

    
1199
/**
1200
 * Implements hook_features_pipe_COMPONENT_alter().
1201
 */
1202
function pathauto_features_pipe_node_alter(&$pipe, $data, $export) {
1203
  foreach ($data as $node_type) {
1204
    $pipe['variable'][] = "pathauto_node_{$node_type}_pattern";
1205
    if (module_exists('locale')) {
1206
      $langcodes = array_keys(locale_language_list('name'));
1207
      $langcodes[] = LANGUAGE_NONE;
1208
      foreach ($langcodes as $langcode) {
1209
        $pipe['variable'][] = "pathauto_node_{$node_type}_{$langcode}_pattern";
1210
      }
1211
    }
1212
  }
1213
}
1214

    
1215
/**
1216
 * Implements hook_features_pipe_COMPONENT_alter().
1217
 */
1218
function pathauto_features_pipe_taxonomy_alter(&$pipe, $data, $export) {
1219
  foreach ($data as $vocabulary) {
1220
    $pipe['variable'][] = "pathauto_taxonomy_term_{$vocabulary}_pattern";
1221
  }
1222
}