multiple_node_menu.module in Multiple Node Menu 7
Same filename and directory in other branches
Add multiple menu management capabilities to node form.
File
multiple_node_menu.moduleView source
<?php
/**
* @file
* Add multiple menu management capabilities to node form.
*/
/**
* Implements hook_module_implements_alter().
*
* Prevent Menu module from adding a menu link form to node forms.
*/
function multiple_node_menu_module_implements_alter(&$implementations, $hook) {
// Only 'hook_node_delete' implementation is useful for us.
if (in_array($hook, array(
'node_insert',
'node_update',
'node_prepare',
'form_node_form_alter',
'node_submit',
))) {
unset($implementations['menu']);
}
}
/**
* Implements hook_node_insert().
*/
function multiple_node_menu_node_insert($node) {
multiple_node_menu_save($node);
}
/**
* Implements hook_node_update().
*/
function multiple_node_menu_node_update($node) {
multiple_node_menu_save($node);
}
/**
* Helper function to save menu items. Used on hook_node_insert() and
* hook_node_update().
*
* @param $node
* Node object to save menu links for.
*/
function multiple_node_menu_save($node) {
if (!empty($node->multiple_node_menu_links_deleted)) {
foreach ($node->multiple_node_menu_links_deleted as $deleted_link) {
menu_link_delete($deleted_link);
}
}
if (empty($node->multiple_node_menu_links)) {
return;
}
foreach ($node->multiple_node_menu_links as $delta => &$link) {
if (!empty($link['link_title'])) {
// Set path to node being saved.
$link['link_path'] = "node/{$node->nid}";
// If 'Enabled' checkbox was ticked. Set hidden to FALSE and vice-versa.
if (isset($link['enabled'])) {
$link['hidden'] = (int) (!$link['enabled']);
}
if (!empty($link['description'])) {
$link['options']['attributes']['title'] = $link['description'];
}
else {
unset($link['options']['attributes']['title']);
}
// Add support for i18n_menu and other language aware modules. Set link
// language to node language.
$link['language'] = empty($node->language) ? LANGUAGE_NONE : $node->language;
// Actually save the link.
if (!menu_link_save($link)) {
// @todo Not sure in what situations we should get here, but it doesn't
// look quite right to display error mensages on a loop like this.
drupal_set_message(t('There was an error saving the menu link.'), 'error');
}
}
}
}
/**
* Implements hook_node_prepare().
*/
function multiple_node_menu_node_prepare($node) {
if (empty($node->multiple_node_menu_links) && !empty($node->nid)) {
$node->multiple_node_menu_links = _multiple_node_menu_load($node);
}
if (empty($node->multiple_node_menu_links)) {
$node->multiple_node_menu_links = array();
}
// Initialize array that holds deleted items.
if (empty($node->multiple_node_menu_links_deleted)) {
$node->multiple_node_menu_links_deleted = array();
}
}
/**
* Implements hook_form_BASE_FORM_ID_alter().
*
* Add multiple menu items management capability to node form.
*/
function multiple_node_menu_form_node_form_alter(&$form, &$form_state, $form_id) {
$node = $form['#node'];
// Don't add the multiple node menu form when the content type has no
// available menus assigned to it.
if (!variable_get('menu_options_' . $node->type, FALSE)) {
return;
}
$form['multiple_node_menu'] = array(
'#type' => 'fieldset',
'#title' => t('Menu settings'),
'#access' => user_access('administer menu'),
'#collapsible' => TRUE,
'#collapsed' => !empty($node->multiple_node_menu_links),
'#group' => 'additional_settings',
'#attached' => array(
'js' => array(
drupal_get_path('module', 'menu') . '/menu.js',
),
),
'#tree' => TRUE,
'#weight' => -2,
'#attributes' => array(
'class' => array(
'menu-link-form',
),
),
);
// Check if the Menu Admin per Menu module is installed on the site. If so,
// use its finer access settings.
if (module_exists('menu_admin_per_menu')) {
$form['multiple_node_menu']['#access'] = _menu_admin_per_menu_access();
}
// Check if there's at least one enabled link for setting the 'Provide a menu
// link' checkbox enabled by default.
$enabled = FALSE;
foreach ($node->multiple_node_menu_links as $link) {
if (empty($link['hidden'])) {
$enabled = TRUE;
break;
}
}
$form['multiple_node_menu']['enabled'] = array(
'#type' => 'checkbox',
'#title' => t('Provide a menu link'),
'#default_value' => $enabled,
);
$form['multiple_node_menu']['link'] = array(
'#type' => 'container',
'#parents' => array(
'multiple_node_menu',
),
'#states' => array(
'invisible' => array(
'input[name="multiple_node_menu[enabled]"]' => array(
'checked' => FALSE,
),
),
),
'#prefix' => '<div id="multiple-node-menu-wrapper">',
'#suffix' => '</div>',
);
if (!empty($node->multiple_node_menu_links)) {
$form['multiple_node_menu']['link']['current_links'] = multiple_node_menu_current_links_form($node);
$form['multiple_node_menu']['link']['current_links']['#prefix'] = '<h3>' . t('Current menu links') . '</h3>';
}
$form['multiple_node_menu']['link']['add_link'] = multiple_node_menu_link_form($node);
$form['multiple_node_menu']['link']['add_link']['#prefix'] = '<h3>' . t('Add new menu link') . '</h3>';
$form['multiple_node_menu']['link']['add_link']['add_link_submit'] = array(
'#type' => 'submit',
'#value' => t('Add new menu link'),
'#submit' => array(
'multiple_node_menu_add_link_submit',
),
// Restrict validation to appropriate fields only.
'#limit_validation_errors' => array(
array(
'multiple_node_menu',
'add_link',
),
),
'#ajax' => array(
'callback' => 'multiple_node_menu_ajax_callback',
'wrapper' => 'multiple-node-menu-wrapper',
),
);
}
/**
* Generate a form for editing a menu links.
*/
function multiple_node_menu_current_links_form($node) {
$form = array(
'#tree' => TRUE,
'#theme' => 'multiple_node_menu_current_links',
);
foreach ($node->multiple_node_menu_links as $delta => $link) {
$form[$delta] = multiple_node_menu_link_form($node, $link, TRUE);
$form[$delta]['enabled'] = array(
'#type' => 'checkbox',
'#title' => t('Enabled'),
'#default_value' => !$link['hidden'],
);
$form[$delta]['remove'] = array(
'#type' => 'submit',
'#value' => t('Delete'),
'#name' => 'remove_' . $delta,
'#submit' => array(
'multiple_node_menu_remove_link_submit',
),
// Bypass validation for 'Delete' button.
'#limit_validation_errors' => array(),
'#ajax' => array(
'callback' => 'multiple_node_menu_ajax_callback',
'wrapper' => 'multiple-node-menu-wrapper',
),
);
}
return $form;
}
/**
* Generate a form for adding/editing a menu link.
*/
function multiple_node_menu_link_form($node, $link = array(), $edit = FALSE) {
// Set default values.
_multiple_node_menu_set_defaults($node, $link);
// menu_parent_options() is goofy and can actually handle either a menu link
// or a node type both as second argument. Pick based on whether there is
// a link already.
$options = menu_parent_options(menu_get_menus(), $node->type);
// If no possible parent menu items were found, there is nothing to display.
if (empty($options)) {
return;
}
$form = array();
// Populate the element with the link data.
foreach (array(
'mlid',
'module',
'hidden',
'has_children',
'customized',
'options',
'expanded',
'hidden',
'parent_depth_limit',
) as $key) {
$form[$key] = array(
'#type' => 'value',
'#value' => $link[$key],
);
}
$form['link_title'] = array(
'#type' => 'textfield',
'#title' => t('Menu link title'),
'#default_value' => $link['link_title'],
);
$form['description'] = array(
'#type' => 'textarea',
'#title' => t('Description'),
'#default_value' => !empty($link['description']) ? $link['description'] : '',
'#rows' => 1,
'#description' => t('Shown when hovering over the menu link.'),
);
$default = !empty($link['mlid']) ? $link['menu_name'] . ':' . $link['plid'] : variable_get('menu_parent_' . $node->type, 'main-menu:0');
// If the current parent menu item is not present in options, use the first
// available option as default value.
// @todo User should not be allowed to access menu link settings in such a
// case.
if (!isset($options[$default])) {
$array = array_keys($options);
$default = reset($array);
}
$form['parent'] = array(
'#type' => 'select',
'#title' => t('Parent item'),
'#default_value' => $default,
'#options' => $options,
);
$form['weight'] = array(
'#type' => 'weight',
'#title' => t('Weight'),
'#delta' => 50,
'#default_value' => $link['weight'],
'#description' => t('Menu links with smaller weights are displayed before links with larger weights.'),
);
return $form;
}
/**
* Implements hook_node_submit().
*
* @see multiple_node_menu_form_node_form_alter()
*/
function multiple_node_menu_node_submit($node, $form, &$form_state) {
if (!variable_get('menu_options_' . $node->type, FALSE)) {
return;
}
// Include 'Add new menu link' form values if it was set.
_multiple_node_menu_add_link($form_state);
$values = $form_state['values']['multiple_node_menu'];
if (!empty($values['current_links'])) {
foreach ($values['current_links'] as $delta => $link) {
if (!empty($link['parent'])) {
// Decompose the selected menu parent option into 'menu_name' and 'plid',
// if the form used the default parent selection widget.
list($link['menu_name'], $link['plid']) = explode(':', $link['parent']);
}
// If 'Provide menu link' checkbox is unchecked. Mark all items disabled.
if (empty($values['enabled'])) {
$link['enabled'] = FALSE;
}
// Update all links to node object.
$node->multiple_node_menu_links[$delta] = $link;
}
}
}
/**
* Ajax callback for multiple menu link forms.
*/
function multiple_node_menu_ajax_callback($form, $form_state) {
// Clear fields for link specific values. The other fields will default to
// previous entered values.
$form['multiple_node_menu']['link']['add_link']['link_title']['#value'] = '';
$form['multiple_node_menu']['link']['add_link']['description']['#value'] = '';
return $form['multiple_node_menu']['link'];
}
/**
* Implements hook_theme().
*/
function multiple_node_menu_theme() {
return array(
'multiple_node_menu_current_links' => array(
'render element' => 'element',
),
);
}
/**
* Theme current menu links table.
*/
function theme_multiple_node_menu_current_links($variables) {
$form = $variables['element'];
$rows = array();
foreach (element_children($form) as $key) {
// Unset field titles and descriptions.
foreach (array(
'link_title',
'description',
'parent',
'weight',
) as $field_name) {
unset($form[$key][$field_name]['#title'], $form[$key][$field_name]['#description']);
}
$rows[] = array(
drupal_render($form[$key]['link_title']) . drupal_render($form[$key]['description']),
drupal_render($form[$key]['parent']),
drupal_render($form[$key]['weight']),
drupal_render($form[$key]['enabled']),
drupal_render($form[$key]['remove']),
);
}
$headers = array(
t('Menu link title and description'),
t('Parent'),
t('Weight'),
t('Enabled'),
t('Operations'),
);
return theme('table', array(
'header' => $headers,
'rows' => $rows,
'attributes' => array(
'id' => 'multiple-node-menu-current-links',
),
));
}
/**
* Submit handler for 'Add menu link' button.
*/
function multiple_node_menu_add_link_submit($form, &$form_state) {
if (_multiple_node_menu_add_link($form_state)) {
// Cause the form to rebuild so the new items show up.
$form_state['rebuild'] = TRUE;
}
}
/**
* Submit handler for "remove" button.
*/
function multiple_node_menu_remove_link_submit($form, &$form_state) {
$values =& $form_state['values'];
$node =& $form_state['node'];
$delta = key($values['multiple_node_menu']['current_links']);
$link = $node->multiple_node_menu_links[$delta];
// Set link to be deleted from database.
if (!empty($link['mlid'])) {
$node->multiple_node_menu_links_deleted[] = $link['mlid'];
}
// Unset the item so it get removed from the form.
unset($node->multiple_node_menu_links[$delta]);
// Cause the form to rebuild so the item is removed from the list.
$form_state['rebuild'] = TRUE;
}
/**
* Determines whether a new menu item should be added based on a node_form form
* state.
*
* @param $form_state
* Form state passed from Node form.
* @return
* Whether or not a new menu item was added.
*/
function _multiple_node_menu_add_link($form_state) {
$values =& $form_state['values']['multiple_node_menu']['add_link'];
if (!empty($values['link_title'])) {
$node =& $form_state['node'];
// Decompose parent into 'menu_name' and 'plid'.
if (!empty($values['parent'])) {
list($values['menu_name'], $values['plid']) = explode(':', $values['parent']);
}
// Append new added link to node object.
_multiple_node_menu_set_defaults($node, $values);
$node->multiple_node_menu_links[] = $values;
// Menu link was added.
return TRUE;
}
// No menu link was added.
return FALSE;
}
/**
* Set default values to menu link array().
*/
function _multiple_node_menu_set_defaults($node, &$link) {
$menu_name = strtok(variable_get('menu_parent_' . $node->type, 'main-menu:0'), ':');
$link += array(
'link_title' => '',
'mlid' => 0,
'plid' => 0,
'menu_name' => $menu_name,
'weight' => 0,
'options' => array(),
'module' => 'menu',
'expanded' => 0,
'hidden' => 0,
'has_children' => 0,
'customized' => 0,
);
// Find the depth limit for the parent select.
if (!isset($link['parent_depth_limit'])) {
$link['parent_depth_limit'] = _menu_parent_depth_limit($link);
}
}
/**
* Load all menu items associated with a node.
*
* @param $node
* The source path to look up.
*
* @return
* Array of paths or NULL if none found.
*/
function _multiple_node_menu_load($node) {
$type_menus = variable_get('menu_options_' . $node->type, array(
'main-menu' => 'main-menu',
));
if (empty($type_menus)) {
return NULL;
}
$links = db_select('menu_links')
->condition('module', 'menu')
->condition('router_path', 'node/%')
->condition('link_path', 'node/' . $node->nid)
->condition('menu_name', $type_menus, 'IN')
->fields('menu_links')
->execute()
->fetchAll(PDO::FETCH_ASSOC);
foreach ($links as &$link) {
// Unserialize options array.
$link['options'] = unserialize($link['options']);
// Set values used in the form.
if (!empty($link['options']['attributes']['title'])) {
$link['description'] = $link['options']['attributes']['title'];
}
$link['enabled'] = !$link['hidden'];
}
return $links;
}