Projet

Général

Profil

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

root / drupal7 / sites / all / modules / taxonomy_menu / taxonomy_menu.module @ a2baadd1

1
<?php
2

    
3
/**
4
 * @file
5
 * It generates menu links for all selected taxonomy terms
6
 */
7

    
8
//include the database layer
9
require_once(DRUPAL_ROOT . '/' . drupal_get_path('module', 'taxonomy_menu') . '/taxonomy_menu.database.inc');
10

    
11
//include the batch functions
12
require_once(DRUPAL_ROOT . '/' . drupal_get_path('module', 'taxonomy_menu') . '/taxonomy_menu.batch.inc');
13

    
14
/**
15
 * Implements hook_form_alter().
16
 *
17
 * Modify the form at admin/content/taxonomy/edit/vocabulary/xx. We add
18
 * our taxonomy_menu options in here on a per-vocab basis.
19
 */
20
function taxonomy_menu_form_alter(&$form, &$form_state, $form_id) {
21
  if ($form_id == 'taxonomy_form_vocabulary') {
22
    // do not alter on deletion
23
    if (isset($form_state['confirm_delete']) && isset($form_state['values']['vid'])) {
24
      return;
25
    }
26
    // Choose a menu to add link items to.
27
    $menus = menu_get_menus();
28
    array_unshift($menus, t('- Disabled -'));
29

    
30
    // Options for path if tokens are not enabled.
31
    $paths = _taxonomy_menu_get_paths();
32

    
33
    $form['taxonomy_menu'] = array(
34
      '#type' => 'fieldset',
35
      '#collapsible' => TRUE,
36
      '#title' => t('Taxonomy menu'),
37
      '#weight' => 10,
38
      '#tree' => TRUE,
39
    );
40
    // This turns the vocab terms into menu items.
41
    $item['mlid'] = 0;
42

    
43
    $menu_items = menu_parent_options(menu_get_menus(), $item);
44

    
45
    array_unshift($menu_items, '= DISABLED =');
46

    
47
    // The vid isn't set when a new vocabulary is being created.
48
    if (isset($form['vid']['#value'])) {
49
      $default = variable_get(_taxonomy_menu_build_variable('vocab_menu', $form['vid']['#value']), NULL) . ':' .
50
                 variable_get(_taxonomy_menu_build_variable('vocab_parent', $form['vid']['#value']), NULL);
51
      if (!isset($menu_items[$default])) {
52
        $default = 0;
53
      }
54
    }
55
    else {
56
      $default = 0;
57
    }
58

    
59
    $form['taxonomy_menu']['vocab_parent'] = array(
60
      '#type' => 'select',
61
      '#title' => t('Menu location'),
62
      '#default_value' => $default,
63
      '#options' => $menu_items,
64
      '#description' => t('The menu and parent under which to insert taxonomy menu items.'),
65
      '#attributes' => array('class' => array('menu-title-select')),
66
    );
67

    
68
    $form['taxonomy_menu']['path'] = array(
69
      '#type' => 'select',
70
      '#title' => t('Menu path type'),
71
      '#default_value' => isset($form['vid']['#value']) ? variable_get(_taxonomy_menu_build_variable('path', $form['vid']['#value']), 0) : 0,
72
      '#options' => $paths,
73
      '#description' => t('The path will be taxonomy/term/tid if <em>Default</em> has been selected.<br />The menu path will be passed through drupal_get_path_alias() function so all aliases will be applied.'),
74
    );
75

    
76
    //get taxonomy menu form options
77
    if (isset($form['vid']) && $form['vid']['#value']) {
78
      $vid = $form['vid']['#value'];
79
    }
80
    else {
81
      $vid = 0;
82
    }
83
    $form['taxonomy_menu']['options'] = _taxonomy_menu_create_options($vid);
84

    
85
    //rebuild the menu
86
    $form['taxonomy_menu']['options']['rebuild'] = array(
87
      '#type' => 'checkbox',
88
      '#title' => t('Select to rebuild the menu on submit.'),
89
      '#default_value' => 0,
90
      '#weight' => 20,
91
      '#description' => t('Rebuild the menu on submit. <strong>Warning</strong>: This will delete then re-create all of the menu items. Only use this option if you are experiencing issues like missing menu items or other inconsistencies.'),
92
    );
93
    // move the buttons to the bottom of the form
94
    $form['submit']['#weight'] = 49;
95
    $form['delete']['#weight'] = 50;
96

    
97
    // add an extra submit handler to save these settings
98
    $form['#submit'][] = 'taxonomy_menu_vocab_submit';
99

    
100
  }
101
  elseif ($form_id == "taxonomy_overview_terms") {
102
    // add an extra submit handler to sync the rearranged terms with menu
103
    // @ TODO: using hook_taxonomy_vocabulary_update is nicer then callback,
104
    // but gives less info and does not always fire.
105
    $form['#submit'][] = 'taxonomy_menu_overview_submit';
106
  }
107
}
108

    
109
/**
110
 * Submit handler for the extra settings added to the taxonomy vocab form.
111
 *
112
 * Check to see if the user has selected a different menu, and only rebuild
113
 * if this is the case.
114
 */
115
function taxonomy_menu_vocab_submit($form, &$form_state) {
116
  $vid = $form_state['values']['vid'];
117
  $changed = FALSE;
118

    
119
  if (is_numeric($form_state['values']['taxonomy_menu']['vocab_parent'])) {
120
    // Menu location has been set to disabled, don't want to throw notices
121
    $form_state['values']['taxonomy_menu']['vocab_parent'] = '0:0';
122
  }
123

    
124
  // Split the menu location into menu name and menu item id.
125
  list($vocab_parent['vocab_menu'], $vocab_parent['vocab_parent']) = explode(':', $form_state['values']['taxonomy_menu']['vocab_parent']);
126

    
127
  // Init flag variables to avoid notices if changes haven't happened
128
  $changed_menu = FALSE;
129
  $change_vocab_item = FALSE;
130
  $changed_path = FALSE;
131

    
132
  // Set the menu name and check for changes
133
  $variable_name = _taxonomy_menu_build_variable('vocab_menu', $vid);
134
  if (_taxonomy_menu_check_variable($variable_name, $vocab_parent['vocab_menu'])) {
135
    $changed_menu = TRUE;
136
  }
137
  variable_set($variable_name, $vocab_parent['vocab_menu']);
138

    
139
  // Set the menu parent item and check for changes
140
  $variable_name = _taxonomy_menu_build_variable('vocab_parent', $vid);
141
  if (_taxonomy_menu_check_variable($variable_name, $vocab_parent['vocab_parent'])) {
142
    $changed_menu = TRUE;
143
  }
144
  variable_set($variable_name, $vocab_parent['vocab_parent']);
145

    
146
  // Set the path and check for changes
147
  $variable_name = _taxonomy_menu_build_variable('path', $vid);
148
  if (_taxonomy_menu_check_variable($variable_name, $form_state['values']['taxonomy_menu']['path'])) {
149
    $changed_path = TRUE;
150
  }
151
  variable_set($variable_name, $form_state['values']['taxonomy_menu']['path']);
152

    
153
  foreach ($form_state['values']['taxonomy_menu']['options'] as $key => $value) {
154
    // Create the variable name
155
    $variable_name = _taxonomy_menu_build_variable($key, $vid);
156

    
157
    // Check to see if the vocab enable options has changed
158
    if ($key == 'voc_item') {
159
      if (_taxonomy_menu_check_variable($variable_name, $value)) {
160
        $change_vocab_item = TRUE;
161
      }
162
    }
163

    
164
    // If $changed is alreayd set to true, then don't bother checking any others.
165
    if (!$changed) {
166
      // Check to see if the variable has changed.
167
      if (_taxonomy_menu_check_variable($variable_name, $value)) {
168
        $changed = TRUE;
169
      }
170
    }
171
    // Save variable.
172
    variable_set($variable_name, $value);
173
  }
174

    
175
  // If the menu hasn't changed and the menu is disabled then do not do anything else.
176
  if ($form_state['values']['taxonomy_menu']['options']['rebuild'] ||
177
      $changed_menu ||
178
      (!$changed_menu && variable_get(_taxonomy_menu_build_variable('vocab_menu', $vid), FALSE) == 0)) {
179
    // Rebuild if rebuild is selected, menu has changed or vocabulary option changed.
180
    if ($form_state['values']['taxonomy_menu']['options']['rebuild'] || $changed_menu || $change_vocab_item || $changed_path) {
181
      $message = _taxonomy_menu_rebuild($vid);
182
    }
183
    // If setting has changed and a menu item is enabled, then update all of the menu items.
184
    elseif ($changed && variable_get(_taxonomy_menu_build_variable('vocab_menu', $vid), FALSE)) {
185
      $message = _taxonomy_menu_update_link_items($vid);
186
    }
187

    
188
    // Do a full menu rebuild in case we have removed the menu or moved it between menus.
189
    variable_set('menu_rebuild_needed', TRUE);
190
    // Only send a message if one has been created.
191
    if (isset($message) && $message) {
192
      // $message is sanitized coming out of its source function,
193
      // no need to reclean it here
194
      drupal_set_message($message, 'status');
195
    }
196
  }
197
}
198

    
199
/**
200
 * Submit handler, reacting on form ID: taxonomy_overview_terms
201
 */
202
function taxonomy_menu_overview_submit(&$form, &$form_state) {
203
  // Only sync if taxonomy_menu is enabled for this vocab and the 'sync'
204
  // option has been checked.
205

    
206
  // This form has the following flow of buttons:
207
  // 1. [Save] --> rebuild taxonomy_menu
208
  // 2. [Reset to alphabetical] --> no rebuild yet
209
  // 3. [Reset to alphabetical][Reset to alphabetical] --> rebuild
210
  // 4. [Reset to alphabetical][Cancel] --> no rebuild
211
  // The code below avoids rebuilding after situation 2.
212

    
213
  if ($form_state['rebuild'] == FALSE && isset($form['#vocabulary']->vid) ) {
214
    // Try to catch the 'Save' button.
215
    $vid = $form['#vocabulary']->vid;
216
  }
217
  elseif ($form_state['rebuild'] == TRUE && isset($form['#vocabulary']->vid) ) {
218
    // Try to catch the 'Reset to alphabetical' button
219
    $vid = NULL;
220
  }
221
  elseif ($form_state['rebuild'] == FALSE && isset($form['vid']['#value']) ) {
222
    // Try to catch the second (confirming) 'Reset to alphabetical' button.
223
    $vid = $form['vid']['#value'];
224
  }
225
  else {
226
    // The button [Reset to alphabetical] [Cancel] does not call this page.
227
    $vid = NULL;
228
  }
229

    
230
  if (isset($vid)) {
231
    $menu_name = variable_get(_taxonomy_menu_build_variable('vocab_menu', $vid), 0);
232
    $sync = variable_get(_taxonomy_menu_build_variable('sync', $vid), 0);
233
    if ($menu_name && $sync) {
234
      // Update all menu items (do not rebuild the menu).
235
      $message = _taxonomy_menu_update_link_items($vid);
236

    
237
      // Report status.
238
      if (isset($message)) {
239
        // message is sanitized coming out of _taxonomy_menu_update_link_items
240
        // no need to reclean it here
241
        drupal_set_message($message, 'status');
242
      }
243

    
244
      // Rebuild the menu.
245
      menu_cache_clear($menu_name);
246
    }
247
  }
248
}
249

    
250
/**
251
 * rebuilds a menu
252
 *
253
 * @param $vid
254
 * @return $message
255
 *  message that is displayed
256
 */
257
function _taxonomy_menu_rebuild($vid) {
258
  // Remove all of the menu items for this vocabulary
259
  _taxonomy_menu_delete_all($vid);
260

    
261
  // Only insert the links if a menu is set
262
  if (variable_get(_taxonomy_menu_build_variable('vocab_menu', $vid), FALSE)) {
263
    _taxonomy_menu_insert_link_items($vid);
264
    menu_rebuild();
265
    return t('The Taxonomy Menu has been rebuilt.');
266
  }
267

    
268
  menu_rebuild();
269
  return t('The Taxonomy Menu has been removed.');
270
}
271

    
272
/**
273
 * Checks to see if the variable has changed.
274
 *
275
 * @param $variable
276
 *  name of variable
277
 * @return Boolean
278
 *  TRUE if it has changed
279
 */
280
function _taxonomy_menu_check_variable($variable, $new_value) {
281
  if ($new_value != variable_get($variable, FALSE)) {
282
    return TRUE;
283
  }
284
  return FALSE;
285
}
286

    
287
/**
288
 * Update the menu items
289
 *
290
 * @param $vid
291
 *  vocab id
292
 */
293
function _taxonomy_menu_update_link_items($vid) {
294
  $menu_name = variable_get(_taxonomy_menu_build_variable('vocab_menu', $vid), FALSE);
295

    
296
  // Get a list of the current tid - menu_link combinations
297
  $menu_links = _taxonomy_menu_get_menu_items($vid);
298

    
299
  // Cycle through the menu links
300
  foreach ($menu_links as $tid => $mlid) {
301
    // $args must be reset each time through.
302
    $args = array(
303
      'menu_name' => $menu_name,
304
      'mlid' => $mlid,
305
    );
306

    
307
    if ($tid == 0) {
308
      $args['vid'] = $vid;
309
    }
310
    else {
311
      $args['term'] = taxonomy_term_load($tid);
312
    }
313

    
314
    //update the menu link
315
    taxonomy_menu_handler('update', $args);
316
  }
317

    
318
  return t('The Taxonomy Menu %menu_name has been updated.', array('%menu_name' => $menu_name));
319
}
320

    
321
/**
322
 * Creates new link items for the vocabulary
323
 *
324
 * @param $vid
325
 */
326
function _taxonomy_menu_insert_link_items($vid) {
327
  $menu_name = variable_get(_taxonomy_menu_build_variable('vocab_menu', $vid), FALSE);
328
  // Check to see if we should had a vocab item
329
  if (variable_get(_taxonomy_menu_build_variable('voc_item', $vid), FALSE)) {
330
      $args = array(
331
        'vid' => $vid,
332
        'menu_name' => $menu_name,
333
      );
334
      taxonomy_menu_handler('insert', $args);
335
    }
336
  // Let batch api take care of inserting the menu items
337
  _taxonomy_menu_insert_link_items_batch($vid);
338
}
339

    
340
/**
341
 * Implements hook_taxonomy_vocabulary_delete().
342
 */
343
function taxonomy_menu_taxonomy_vocabulary_delete($vocabulary) {
344
  //delete the menu items
345
  _taxonomy_menu_delete_all($vocabulary->vid);
346
  $menu_name = variable_get(_taxonomy_menu_build_variable('vocab_menu', $vocabulary->vid), 0);
347
  menu_cache_clear($menu_name);
348
}
349

    
350
/**
351
 * Implements hook_taxonomy_term_insert($term).
352
 */
353

    
354
function taxonomy_menu_taxonomy_term_insert($term) {
355
  _taxonomy_menu_taxonomy_termapi_helper($term, 'insert');
356
}
357

    
358
/**
359
 * Implements hook_taxonomy_term_update().
360
 */
361

    
362
function taxonomy_menu_taxonomy_term_update($term) {
363
  _taxonomy_menu_taxonomy_termapi_helper($term, 'update');
364
}
365

    
366
/**
367
 * Implements hook_taxonomy_term_delete().
368
 */
369

    
370
function taxonomy_menu_taxonomy_term_delete($term) {
371
  _taxonomy_menu_taxonomy_termapi_helper($term, 'delete');
372
}
373

    
374
/**
375
 * Implements hook_node_insert().
376
 */
377
function taxonomy_menu_node_insert($node) {
378
  $terms_old = &drupal_static('taxonomy_menu_terms_old');
379
  // We use this direct table pull to avoid the cache and because
380
  // free tags are not formated in a matter where extrating the
381
  // tid's is easy.
382
  $terms_new = _taxonomy_menu_get_node_terms($node);
383

    
384
  // Merge current terms and previous terms to update both menu items.
385
  $terms = array_unique(array_merge((array)$terms_new, (array)$terms_old));
386
  _taxonomy_menu_nodeapi_helper('insert', $terms, $node);
387
}
388

    
389
/**
390
 * Implements hook_node_update().
391
 */
392
function taxonomy_menu_node_update($node) {
393
  $terms_old = &drupal_static('taxonomy_menu_terms_old');
394
  //we use this direct table pull to avoid the cache and because
395
  //free tags are not formated in a matter where extrating the
396
  //tid's is easy
397
  $terms_new = _taxonomy_menu_get_node_terms($node);
398

    
399
  //merge current terms and previous terms to update both menu items.
400
  $terms = array_unique(array_merge((array)$terms_new, (array)$terms_old));
401
  _taxonomy_menu_nodeapi_helper('update', $terms, $node);
402
}
403

    
404
/**
405
 * Implements hook_node_presave().
406
 */
407
function taxonomy_menu_node_presave($node) {
408
  $terms_old = &drupal_static('taxonomy_menu_terms_old');
409
  //get the terms from the database before the changes are made.
410
  //these will be used to update the menu item's name if needed
411
  //we go directly to the db to bypass any caches
412
  if (isset($node->nid)) {
413
    $node_old = node_load($node->nid);
414
    $terms_old = _taxonomy_menu_get_node_terms($node_old);
415
  }
416
  else {
417
    $terms_old = array();
418
  }
419
}
420

    
421
/**
422
 * Implements hook_node_delete().
423
 */
424
function taxonomy_menu_node_delete($node) {
425
  // since the delete operation is run after the data is deleted
426
  // pull the terms from the node object
427
  $terms =  _taxonomy_menu_get_node_terms($node);
428
  _taxonomy_menu_nodeapi_helper('delete', $terms, $node);
429
}
430

    
431
/**
432
 * Abstraction of hook_taxonomy_term_<operation>()
433
 */
434
function _taxonomy_menu_taxonomy_termapi_helper($term, $operation) {
435
  // Only sync if taxonomy_menu is enabled for this vocab and the 'sync'
436
  // option has been checked.
437
  $menu_name = variable_get(_taxonomy_menu_build_variable('vocab_menu', $term->vid), 0);
438
  $sync = variable_get(_taxonomy_menu_build_variable('sync', $term->vid), 0);
439

    
440
  if ($menu_name && $sync) {
441
    $item = array(
442
      'tid' => $term->tid,
443
      'vid' => $term->vid,
444
      'term' => $term,
445
      'menu_name' => $menu_name,
446
      'mlid' => _taxonomy_menu_get_mlid($term->tid, $term->vid),
447
    );
448

    
449
    switch ($operation) {
450
      case 'insert':
451
        $text = 'Added term %term to taxonomy menu %menu_name.';
452
        break;
453
      case 'update':
454
        $text = 'Updated term %term in taxonomy menu %menu_name.';
455
        break;
456
      case 'delete':
457
        $text = 'Deleted term %term from taxonomy menu %menu_name.';
458
        break;
459
    }
460
    $message = t($text, array('%term' => $term->name, '%menu_name' => $menu_name));
461

    
462
      // run function
463
    taxonomy_menu_handler($operation, $item);
464

    
465
    // report status
466
    drupal_set_message($message, 'status');
467

    
468
    // rebuild the menu
469
    menu_cache_clear($menu_name);
470
  }
471
}
472

    
473
/**
474
 * Builds argument arrays calls taxonomy_menu_handler.
475
 *
476
 * @param $op
477
 *  A string of the operation to be performed [update|insert|delete]
478
 * @param $terms
479
 *  An array of tids.
480
 * @param $node
481
 */
482
function _taxonomy_menu_nodeapi_helper($op, $terms = array(), $node) {
483
  foreach ($terms as $key => $tid) {
484

    
485
    // taxonomy_term_load($tid) return FALSE if the term was not found
486
    // if taxonomy $term is false, then go to the next $term
487
    if (!$term = taxonomy_term_load($tid)) {
488
      continue;
489
    }
490

    
491
    // update the menu for each term if necessary
492
    $menu_name = variable_get(_taxonomy_menu_build_variable('vocab_menu', $term->vid), FALSE);
493
    $vocb_sync = variable_get(_taxonomy_menu_build_variable('sync', $term->vid), TRUE);
494
    $menu_num = variable_get(_taxonomy_menu_build_variable('display_num', $term->vid), TRUE);
495
    $hide_empty = variable_get(_taxonomy_menu_build_variable('hide_empty_terms', $term->vid), FALSE);
496
    if ($menu_name && $vocb_sync && ($menu_num || $hide_empty)) {
497
      //build argument array to save menu_item
498
      $args = array(
499
        'tid' => $term->tid,
500
        'vid' => $term->vid,
501
        'term' => $term,
502
        'menu_name' => $menu_name,
503
        'mlid' => _taxonomy_menu_get_mlid($term->tid, $term->vid),
504
      );
505

    
506
      if ($op == 'delete') {
507
        /* Turn the op to 'update' here since we really do want to update the item
508
        * and not delete/recreate it, since the latter will break hierarchy and
509
        * customizations.
510
        */
511
        $op = 'update';
512
      }
513

    
514
      taxonomy_menu_handler($op, $args, $node);
515
      if (variable_get(_taxonomy_menu_build_variable('hide_empty_terms', $term->vid), FALSE)) {
516
        _taxonomy_menu_update_all_parents($term, $menu_name);
517
      }
518
    }
519
  }
520
}
521

    
522
/**
523
 * Update all parent items
524
 *
525
 * @param $op
526
 *  options are 'insert', 'update', 'delete' or path
527
 *
528
 * @param $node
529
 *  The node object.
530
 */
531
function _taxonomy_menu_update_all_parents($term, $menu_name) {
532
  $parents = taxonomy_get_parents($term->tid);
533
  if ($parents) {
534
    foreach ($parents as $parent) {
535
      $parent->parents = array_keys(taxonomy_get_parents($parent->tid));
536
      $item = array(
537
        'term' => $parent,
538
        'menu_name' => $menu_name,
539
        'mlid' => _taxonomy_menu_get_mlid($parent->tid, $parent->vid),
540
        'remove' => FALSE,
541
      );
542
      taxonomy_menu_handler('update', $item);
543
      _taxonomy_menu_update_all_parents($parent, $menu_name);
544
    }
545
  }
546
}
547

    
548
/**
549
 * HANDLING
550
 *
551
 * @param $op
552
 *  options are 'insert', 'update', 'delete' or path
553
 *
554
 * @param $node
555
 *  The node object.
556
 *
557
 * @param $args
558
 *  if $op == 'insert' then
559
 *    array with the following key/value pairs:
560
 *     'term' => term object,
561
 *     'menu_name' => menu that the item is set to apply to
562
 *  if $op == 'delete' then
563
 *    array(
564
 *      'tid' => TermID
565
 *      'mlid => Menu ID
566
 *    )
567
 *  if $op == 'update' then
568
 *     'term' => term object,
569
 *     'menu_name' => menu that the item is set to apply to
570
 *     'mlid' => Menu ID
571
 */
572
function taxonomy_menu_handler($op, $args = array(), $node = NULL, $item = array()) {
573
  // Get the initial $item
574
  if (empty($item)) {
575
    $item = _taxonomy_menu_create_item($args, $node);
576
  }
577

    
578
  // Let other modules make edits
579
  $hook = 'taxonomy_menu_' . $op;
580
  foreach (module_implements($hook) as $module) {
581
    $function = $module . '_' . $hook;
582
    $function($item);
583
  }
584

    
585
  // Update the menu and return the mlid if the remove element is not true
586
  if ($op != 'delete') {
587
    return _taxonomy_menu_save($item);
588
  }
589
}
590

    
591
/**
592
 * Adds/Updates a taxonomy menu item.
593
 *
594
 * We use a custom data array $item as a parameter, instead of using a
595
 * standard taxonomy $term object. This is because this function is also
596
 * called from hook_taxonomy(), which doesn't have a $term object. Rather
597
 * have one consistent method of passing the data.
598
 *
599
 * @param $item
600
 *   array with the following key/value pairs:
601
 *     'tid' => the term id (if 0 then adding the vocab as an item)
602
 *     'name' => the term's name
603
 *     'description' => term description, used as to build the title attribute
604
 *     'weight' => term weight
605
 *       (This will be overriden by the order created from taxonomy_get_tree which respects the correct wight)
606
 *     'vid' => the vocabulary's id
607
 *     'ptid' => the term's parent's term id
608
 *     'menu_name' => the menu that the link item will be inserted into
609
 *     'mlid' => if this is filled in then the mlid will be updated
610
 */
611
function _taxonomy_menu_save($item) {
612
  $insert = TRUE;
613

    
614
  $flatten_menu = variable_get(_taxonomy_menu_build_variable('flat',
615
$item['vid']));
616
  // Child items should appear around the parent/root, so set their weight
617
  // equal to the root term's
618
  if ($flatten_menu) {
619
    $item['weight'] = $item['root_term_weight'];
620
  }
621

    
622
  // create the path.
623
  $path = taxonomy_menu_create_path($item['vid'], $item['tid']);
624
  // get the parent mlid: this is either:
625
  // - the parent tid's mlid
626
  // - the vocab menu item's mlid
627
  // - the menu parent setting for this vocab
628
  $plid = _taxonomy_menu_get_mlid($item['ptid'], $item['vid']);
629
  if (!$plid || $flatten_menu) {
630
    $plid = variable_get(_taxonomy_menu_build_variable('vocab_parent', $item['vid']), NULL);
631
  }
632

    
633
  // Make sure the path has less then 256 characters
634
  if (drupal_strlen($path) > 256) {
635
    preg_match('/(.{256}.*?)\b/', $path, $matches);
636
    $path = rtrim($matches[1]);
637
  }
638

    
639
  $link = array(
640
    'link_title' => $item['name'],
641
    'menu_name' => $item['menu_name'],
642
    'plid' => $plid,
643
    'options' => array('attributes' => array('title' => trim($item['description'])
644
      ? $item['description'] : $item['name'])),
645
    'weight' => $item['weight'],
646
    'module' => 'taxonomy_menu',
647
    'expanded' => variable_get(_taxonomy_menu_build_variable('expanded', $item['vid']), TRUE),
648
    'link_path' => $path,
649
  );
650

    
651
  // Add setup the query paramater in the URL correctly
652
  if (strpos($path, '?') !== FALSE) {
653
    $split = explode('?', $path);
654
    if (strpos($split[1], '?') !== FALSE) {
655
      // the query split didn't work, too many question marks
656
      // error?
657
    }
658
    else {
659
      parse_str($split[1], $link['options']['query']);
660
      $link['link_path'] = $split[0];
661
    }
662
  }
663

    
664
  // If passed a mlid then add it
665
  if (isset($item['mlid']) && $item['mlid']) {
666
    $link['mlid'] = $item['mlid'];
667
    $insert = FALSE;
668
  }
669

    
670
  // FIXME: i18nmenu need to be cleaned up to allow translation from other menu module
671
  if (module_exists('i18n_menu')) {
672
    $link['options']['alter'] = TRUE;
673
    $link['language'] = $item['language'];
674
    $link['customized'] = 1;
675
  }
676

    
677
  // set the has_children property
678
  // if tid=0 then adding a vocab item and had children
679
  // if the term has any children then set it to true
680
  if ($item['tid'] == 0) {
681
    $link['has_children'] = 1;
682
  }
683
  else {
684
    $children = taxonomy_get_children($item['tid']);
685
    if (!empty($children)) {
686
      $link['has_children'] = 1;
687
    }
688
  }
689

    
690
  // If remove is true then set hidden to 1
691
  $link['hidden'] = (isset($item['remove']) && $item['remove']) ? 1 : 0;
692

    
693
  // Save the menu item
694
  if ($mlid = menu_link_save($link)) {
695
    // if inserting a new menu item then insert a record into the table
696
    if ($insert) {
697
      _taxonomy_menu_insert_menu_item($mlid, $item['tid'], $item['vid']);
698
    }
699
    return $mlid;
700
  }
701
  else {
702
    drupal_set_message(t('Could not save the menu link for the taxonomy menu.'), 'error');
703
    return FALSE;
704
  }
705
}
706

    
707
/**
708
 * Create the path to use in the menu item
709
 *
710
 * @return array
711
 *  path selections
712
 */
713
function _taxonomy_menu_get_paths() {
714
  return module_invoke_all('taxonomy_menu_path');
715
}
716

    
717
/**
718
 * Creates the path for the vid/tid combination.
719
 *
720
 * @param $vid
721
 * @param $tid
722
 * @return string
723
 *  path
724
 */
725
function taxonomy_menu_create_path($vid, $tid) {
726
  // get the path function for this vocabulary
727
  $function = variable_get(_taxonomy_menu_build_variable('path', $vid), 'taxonomy_menu_path_default');
728
  // run the function
729
  return $function($vid, $tid);
730
}
731

    
732
/**
733
 * Implements hook_taxonomy_menu_path().
734
 *
735
 * Invoked from _taxonomy_menu_get_paths.
736
 *
737
 * @return array
738
 *  function name => Display Title
739
 *  a list of the path options.
740
 */
741
function taxonomy_menu_taxonomy_menu_path() {
742
  $output = array(
743
    'taxonomy_menu_path_default' => t('Default'),
744
  );
745

    
746
  return $output;
747
}
748

    
749
/**
750
 * Callback for hook_taxonomy_menu_path
751
 */
752
function taxonomy_menu_path_default($vid, $tid) {
753
  // if tid = 0 then we are creating the vocab menu item format will be taxonomy/term/$tid+$tid+$tid....
754
  if ($tid == 0) {
755
    // get all of the terms for the vocab
756
    $vtids = _taxonomy_menu_get_terms($vid);
757
    $end = implode(' ', $vtids);
758
    $path = "taxonomy/term/$end";
759
  }
760
  else {
761
    $path = 'taxonomy/term/' . $tid;
762
    if (variable_get(_taxonomy_menu_build_variable('display_descendants', $vid), FALSE)) {
763
      // Use 'all' at the end of the path
764
      if (variable_get(_taxonomy_menu_build_variable('end_all', $vid), FALSE)) {
765
        $path .= '/all';
766
      }
767
      else {
768
        // we wait to run this instead of during the if above
769
        // because we only wan to run it once.
770
        $terms = taxonomy_get_tree($vid, $tid);
771
        foreach ($terms as $term) {
772
          $tids[] = $term->tid;
773
        }
774
        if ($tids) {
775
          $end = implode(' ', $tids);
776
          $path .= ' ' . $end;
777
        }
778
      }
779
    }
780
  }
781

    
782
  return $path;
783
}
784

    
785
/**
786
 * hook_taxonomy_menu_delete
787
 *
788
 * @param $args
789
 *  array(
790
 *   'vid' => Vocab ID
791
 *   'tid' => TermID
792
 *   'mlid' => Menu ID
793
 *  )
794
 *
795
 */
796
function taxonomy_menu_taxonomy_menu_delete(&$item) {
797
  menu_link_delete($item['mlid']);
798
  _taxonomy_menu_delete_item($item['vid'], $item['tid']);
799
  unset($item['mlid']);
800

    
801
}
802

    
803
/**
804
 * Create the initial $item array
805
 *
806
 * @param $args
807
 *  array with the following key/value pairs:
808
 *   'term' => term object, if updating a term
809
 *   'menu_name' => menu that the item is set to apply to
810
 *   'vid' => vocab id.  if editing vocab item
811
 *   'mlid' => menu id
812
 *
813
 * @param $node
814
 *  The node object.
815
 */
816
function _taxonomy_menu_create_item($args = array(), $node) {
817

    
818
  // If tid = 0, then we are creating a vocab item
819
  if (isset($args['tid']) && isset($args['vid']) && $args['tid'] == 0 && variable_get(_taxonomy_menu_build_variable('voc_item', $args['vid']), 0)) {
820
    $vocab = taxonomy_vocabulary_load($args['vid']);
821
    $item = array(
822
      'tid' => 0,
823
      'name' => $vocab->name,
824
      'description' => variable_get(_taxonomy_menu_build_variable('voc_item_description', $args['vid']), 0) ? $vocab->description : '',
825
      'weight' => $vocab->weight,
826
      'vid' => $args['vid'],
827
      'ptid' => 0,
828
      'root_term_weight' => $vocab->weight,
829
      'menu_name' => $args['menu_name'],
830
      'language' => $vocab->language,
831
    );
832
  }
833
  else {
834
    // If tid <> 0 then we are creating a term item.
835
    $term = $args['term'];
836

    
837
    // Sometimes $term->parents is not set so we find it.
838
    if (empty($term->parents)) {
839
      $term->parents = _taxonomy_menu_get_parents($term->tid);
840
      if (empty($term->parents)) {
841
        // even without parents, create one with $ptid = 0
842
        $term->parents = array(0 => '0');
843
      }
844
    }
845

    
846
    // Find the weight of the root taxonomy term; we'll need it in case we want
847
    // a flat taxonomy menu.
848
    if (is_object($term)) {
849
      $term_parents = taxonomy_get_parents_all($term->tid);
850
      $root_term_weight = $term_parents[count($term_parents) - 1]->weight;
851
    }
852
    else {
853
      $root_term_weight = 0;
854
    }
855

    
856
    foreach ($term->parents as $parent) {
857
      $ptid = $parent;
858
      // turn the term into the correct $item array form
859
      $item = array(
860
        'tid' => $term->tid,
861
        'name' => $term->name,
862
        'description' => variable_get(_taxonomy_menu_build_variable('term_item_description', $term->vid), 0) ? $term->description : '',
863
        'weight' => $term->weight,
864
        'vid' => $term->vid,
865
        'ptid' => $ptid,
866
        'root_term_weight' => $root_term_weight,
867
        'menu_name' => $args['menu_name'],
868
        'language' => isset($term->language) ? $term->language : ($node ? $node->language : $GLOBALS['language']->language),
869
      );
870
      if (isset($args['mlid'])) {
871
        $item['mlid'] = $args['mlid'];
872
      }
873
      // Mutiple parents are not supported yet, hence this break.
874
      // without the break, the item is inserted multiple under one parent, instead once under each parent.
875
      break;
876
    }
877
  }
878

    
879
  return $item;
880
}
881

    
882
/**
883
 * Helper function to see if any of the children have any nodes
884
 *
885
 * @param $tid
886
 * @param $vid
887
 * @return boolean
888
 */
889
function _taxonomy_menu_children_has_nodes($tid, $vid, $return = FALSE) {
890
  $children = taxonomy_get_children($tid, $vid);
891
  foreach ($children as $tid => $term) {
892
    if (_taxonomy_menu_term_count($tid) > 0) {
893
      $return = TRUE;
894
    }
895
    else {
896
      $return = _taxonomy_menu_children_has_nodes($tid, $vid, $return);
897
    }
898
  }
899
  return $return;
900
}
901

    
902
/**
903
 * Helper function for insert and update hooks
904
 *
905
 * @param $item
906
 * @return array
907
 */
908
function _taxonomy_menu_item($item) {
909
  // if tid is 0 then do not change any settings
910
  if ($item['tid'] > 0) {
911
    // get the number of node attached to this term
912
    $num = _taxonomy_menu_term_count($item['tid']);
913

    
914
    // if hide menu is selected and the term count is 0 and the term has no children then do not create the menu item
915
    if ($num == 0 &&
916
        variable_get(_taxonomy_menu_build_variable('hide_empty_terms', $item['vid']), FALSE) &&
917
        !_taxonomy_menu_children_has_nodes($item['tid'], $item['vid'])) {
918

    
919
        $item['remove'] = TRUE;
920
        return $item;
921
    }
922

    
923
    // if display number is selected and $num > 0 then change the title
924
    if (variable_get(_taxonomy_menu_build_variable('display_num', $item['vid']), FALSE)) {
925
      // if number > 0 and display decendants, then count all of the children
926
      if (variable_get(_taxonomy_menu_build_variable('display_descendants', $item['vid']), FALSE)) {
927
        $num = taxonomy_menu_term_count_nodes($item['tid'], $item['vid']);
928
      }
929
      $item['name'] .= " ($num)";
930
    }
931
  }
932
  elseif ($item['tid'] == 0) {
933
    // if custom name is provided, use that name
934
    $custom_name = variable_get(_taxonomy_menu_build_variable('voc_name', $item['vid']), '');
935
    if (!empty($custom_name)) {
936
      $item['name'] = $custom_name;
937
    }
938
  }
939

    
940
  return $item;
941
}
942

    
943

    
944
/**
945
 * Calculates the number of nodes linked to the term and all children
946
 * @param $tid
947
 * @param $vid
948
 * @return integer
949
 */
950
function taxonomy_menu_term_count_nodes($tid, $vid, $count = 0) {
951
  $count += _taxonomy_menu_term_count($tid);
952
  $children = taxonomy_get_children($tid, $vid);
953
  foreach ($children as $tid => $term) {
954
    $count = taxonomy_menu_term_count_nodes($term->tid, $term->vid, $count);
955
  }
956
  return $count;
957
}
958

    
959

    
960
/**
961
 * Implements hook_taxonomy_menu_insert().
962
 *
963
 * @param $item
964
 *  array with the following key/value pairs:
965
 *   'tid' => the term id (if 0 then updating the vocab as an item)
966
 *   'name' => new menu name
967
 *   'description' => new menu description, used as to build the title attribute
968
 *   'weight' => new menu weight
969
 *   'vid' => the new vocabulary's id
970
 *   'ptid' => the new parent tid
971
 *   'remove' => if this is set to TRUE then the $item is not added as a menu
972
 *
973
 * @return $item
974
 */
975
function taxonomy_menu_taxonomy_menu_insert(&$item) {
976
  $item = _taxonomy_menu_item($item);
977
}
978

    
979
/**
980
 * Implements hook_taxonomy_menu_update().
981
 *
982
 * @param $item
983
 *  array with the following key/value pairs:
984
 *   'tid' => the term id (if 0 then updating the vocab as an item)
985
 *   'name' => new menu name
986
 *   'description' => new menu description, used as to build the title attribute
987
 *   'weight' => new menu weight
988
 *   'vid' => the new vocabulary's id
989
 *   'ptid' => the new parent tid
990
 *   'mlid' => mlid that needs to be edited
991
 *   'remove' => if this is set to TRUE then the $item is not added as a menu
992
 *
993
 */
994
function taxonomy_menu_taxonomy_menu_update(&$item) {
995
  $item = _taxonomy_menu_item($item);
996
}
997

    
998
/**
999
 * Used to create a form array of taxonomy menu options
1000
 * invokes hook_taxonomy_menu_options().
1001
 *
1002
 * @return $form array
1003
 */
1004
function _taxonomy_menu_create_options($vid) {
1005
  $options = module_invoke_all('taxonomy_menu_options');
1006

    
1007
  // cycle through field
1008
  foreach ($options as $field_name => $field_elements) {
1009
    // cycle through each value of the field
1010
    $variable_name = _taxonomy_menu_build_variable($field_name, $vid);
1011

    
1012
    // if the variable is set then use that, if the default key is set then use that, otherwise use false
1013
    $options[$field_name]['#default_value'] =
1014
      variable_get($variable_name,
1015
      !empty($options[$field_name]['default']) ? $options[$field_name]['default'] : FALSE);
1016

    
1017
    // set the type to checkbox if it is empty
1018
    if (empty($options[$field_name]['#type'])) {
1019
      $options[$field_name]['#type'] = 'checkbox';
1020
    }
1021

    
1022
    // set the option feildset values
1023
    $options['#type'] = 'fieldset';
1024
    $options['#title'] = t('Options');
1025
    $options['#collapsible'] = TRUE;
1026

    
1027
    // remove the default value from the array so we don't pass it to the form
1028
    unset($options[$field_name]['default']);
1029
  }
1030

    
1031
  return $options;
1032
}
1033

    
1034
/**
1035
 * Builds a variable from the supplied name and machine name of the vocabulary.
1036
 *
1037
 * @param $name
1038
 *  String to be added to the returned variable.
1039
 * @param $vid
1040
 *  VID of the vocabulary from which the machine name will be taken.
1041
 *
1042
 * @return bool|string
1043
 */
1044
function _taxonomy_menu_build_variable($name, $vid) {
1045
  $vocabulary = taxonomy_vocabulary_load($vid);
1046
  if ($vocabulary) {
1047
    return 'taxonomy_menu_' . $name . '_' . $vocabulary->machine_name;
1048
  }
1049
  else {
1050
    return FALSE;
1051
  }
1052

    
1053
}
1054

    
1055
/**
1056
 * Implements hook_taxonomy_menu_options().
1057
 *
1058
 * @return array
1059
 *  Uses the value to set the variable taxonomy_menu_<value>_<machine_name>
1060
 *  $options[value]
1061
 *   default - optional.  this is what will be used if the varialbe is not set.  if empty then FALSE is used
1062
 *   #title - required.
1063
 *   any other form element
1064
 */
1065
function taxonomy_menu_taxonomy_menu_options() {
1066

    
1067
  $options['sync'] = array(
1068
    '#title' => t('Synchronise changes to this vocabulary'),
1069
    '#description' => t('Every time a term is added/deleted/modified, the corresponding menu link will be altered too.'),
1070
    'default' => TRUE,
1071
  );
1072

    
1073
  $options['display_num'] = array(
1074
    '#title' => t('Display number of items'),
1075
    '#description' => t('Display the number of items per taxonomy terms. Will not show up for vocabulary menu items.'),
1076
    'default' => FALSE,
1077
  );
1078

    
1079
  $options['hide_empty_terms'] = array(
1080
    '#title' => t('Hide empty terms'),
1081
    '#description' => t('Hide terms with no items attached to them.'),
1082
    'default' => FALSE,
1083
  );
1084

    
1085
  $options['voc_item'] = array(
1086
    '#title' => t('Add item for vocabulary'),
1087
    '#description' => t('Shows the vocabulary name as the top level menu item of the taxonomy menu.'),
1088
    'default' => FALSE,
1089
    '#disabled' => TRUE,
1090
  );
1091

    
1092
  $options['voc_item_description'] = array(
1093
    '#title' => t('Add description for vocabulary'),
1094
    '#description' => t('Add the vocabulary description to the vocabulary menu item.'),
1095
    'default' => FALSE,
1096
  );
1097

    
1098
  $options['term_item_description'] = array(
1099
    '#title' => t('Add description for terms'),
1100
    '#description' => t('Add the term description to the term menu item.'),
1101
    'default' => FALSE,
1102
  );
1103

    
1104
  $options['expanded'] = array(
1105
    '#title' => t('Auto expand menu items'),
1106
    '#description' => t('Automatically show all menu items as expanded.'),
1107
    'default' => TRUE,
1108
  );
1109

    
1110
  $options['flat'] = array(
1111
    '#title' => t('Flatten the taxonomy\'s hierarchy in the menu'),
1112
    '#description' => t('Add all menu items to the same level rather than hierarchically.'),
1113
    'default' => FALSE,
1114
  );
1115

    
1116
  $options['voc_name'] = array(
1117
    '#type' => 'textfield',
1118
    '#title' => t('Custom name for vocabulary item'),
1119
    '#description' => t('Changes the name of the vocabulary item (if enabled above). Leave blank to use the name of the vocabulary.'),
1120
    'default' => '',
1121
    '#disabled' => TRUE,
1122
  );
1123

    
1124
  $options['display_descendants'] = array(
1125
    '#title' => t('Display descendants'),
1126
    '#description' => t('Changes the default path to taxonomy/term/tid+tid+tid for all terms thave have child terms.'),
1127
    'default' => FALSE,
1128
  );
1129

    
1130
  $options['end_all'] = array(
1131
    '#title' => t("Use 'all' at the end of URL"),
1132
    'default' => FALSE,
1133
    '#description' => t('This changes tid+tid+tid to "All" in term when <em>Display descendants</em> has been selected.<br />Only used if <em>Menu path type</em> is "Default path".<br />Works with default taxonomy page.'),
1134
    '#disabled' => TRUE,
1135
  );
1136

    
1137
  return $options;
1138
}
1139

    
1140

    
1141
/**
1142
 * Implements hook_translated_menu_link_alter().
1143
 *
1144
 * Translate menu links on the fly by using term translations.
1145
 *
1146
 */
1147
function taxonomy_menu_translated_menu_link_alter(&$item, $map) {
1148
  if (module_exists('i18n_taxonomy')) {
1149
    // In case of localized terms, use term translation for menu title.
1150
    if ($item['module'] == 'taxonomy_menu') {
1151
      $t = _taxonomy_menu_get_item($item['mlid']);
1152
      // Only translate when term exist (may per example occur with stray menu item)
1153
      if ($t) {
1154
        // Only translate when translation mode is set to localize
1155
        if (i18n_taxonomy_vocabulary_mode($t->vid, I18N_MODE_LOCALIZE)) {
1156
          // this is a term
1157
          if ($t->tid > 0) {
1158
            $term = taxonomy_term_load($t->tid);
1159
            $display_num = '';
1160
            $num = _taxonomy_menu_term_count($t->tid);
1161

    
1162
            // If hide menu is selected and the term count is 0 and the term has no children then do not create the menu item
1163
            if ($num == 0 && variable_get(_taxonomy_menu_build_variable('hide_empty_terms', $t->vid), FALSE) && !_taxonomy_menu_children_has_nodes($t->tid, $t->vid)) {
1164
              $display_num = '';
1165
            }
1166
            // if display number is selected and $num > 0 then change the title
1167
            elseif (variable_get(_taxonomy_menu_build_variable('display_num', $t->vid), FALSE)) {
1168
              // if number > 0 and display decendants, then count all of the children
1169
              if (variable_get(_taxonomy_menu_build_variable('display_descendants', $t->vid), FALSE)) {
1170
                $num = taxonomy_menu_term_count_nodes($t->tid, $t->vid);
1171
              }
1172
              $display_num = " ($num)";
1173
            }
1174

    
1175
            if ($item['title'] != ($term->name . $display_num)) {
1176
              // Should not happen
1177
              watchdog('error', 'Menu and taxonomy name mismatch: @title != @name', array('@title' => $item['title'], '@name' => $term->name . $display_num));
1178
            }
1179

    
1180
            $term = i18n_taxonomy_localize_terms($term);
1181
            $item['title'] = $item['link_title'] = $term->name . $display_num;
1182
            if ($term->description) {
1183
              $item['options']['attributes']['title'] = $term->description;
1184
            }
1185
          }
1186
          // is a vocabulary
1187
          else {
1188
            $vocab = taxonomy_vocabulary_load($t->vid);
1189
            $item['title'] = i18n_string('taxonomy:vocabulary:' . $vocab->vid . ':name', $vocab->name);
1190
          }
1191
        }
1192
      }
1193
      // no term, add a watchdog entry to help
1194
      else {
1195
        watchdog('taxonomy_menu', 'Error with menu entry "%me" in menu "%mt"', array('%me' => $item['title'], '%mt' => $item['menu_name']));
1196
      }
1197
    }
1198
  }
1199
}