620 lines
19 KiB
PHP
620 lines
19 KiB
PHP
<?php
|
|
|
|
/**
|
|
* @file
|
|
* Defines a menu link field type.
|
|
*/
|
|
|
|
/**
|
|
* Implements hook_field_info().
|
|
*
|
|
* Field settings:
|
|
* - link_path_field: Boolean whether or not the link path field is available
|
|
* to users. If set to FALSE enity_uri() is used to determine the link path.
|
|
* Instance settings:
|
|
* - menu_options: The menus available to place links in for this field.
|
|
*/
|
|
function menu_link_field_info() {
|
|
return array(
|
|
'menu_link' => array(
|
|
'label' => t('Menu link'),
|
|
'description' => t('This field stores a reference to a menu link in the database.'),
|
|
'settings' => array(
|
|
'link_path_field' => FALSE,
|
|
),
|
|
'instance_settings' => array(
|
|
'menu_options' => array('main-menu'),
|
|
),
|
|
'default_widget' => 'menu_link_default',
|
|
'default_formatter' => 'menu_link_link',
|
|
),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Implements hook_field_settings_form().
|
|
*/
|
|
function menu_link_field_settings_form($field, $instance, $has_data) {
|
|
$settings = $field['settings'];
|
|
|
|
$form = array();
|
|
|
|
/* @todo Allow menu link fields to be used as a reference field? http://drupal.org/node/1028344
|
|
$form['link_path_field'] = array(
|
|
'#type' => 'checkbox',
|
|
'#title' => t('Enable <em>Path</em> field'),
|
|
'#default_value' => $settings['link_path_field'],
|
|
'#description' => t('Allow users to set the path of menu links. When not checked the uri of the entity being edited is used.'),
|
|
);*/
|
|
|
|
return $form;
|
|
}
|
|
|
|
/**
|
|
* Implements hook_field_instance_settings_form().
|
|
*/
|
|
function menu_link_field_instance_settings_form($field, $instance) {
|
|
$settings = $instance['settings'];
|
|
|
|
$form['menu_options'] = array(
|
|
'#type' => 'checkboxes',
|
|
'#title' => t('Available menus'),
|
|
'#default_value' => $settings['menu_options'],
|
|
'#options' => menu_get_menus(),
|
|
'#description' => t('The menus available to place links in for this field.'),
|
|
'#required' => TRUE,
|
|
'#weight' => -5,
|
|
);
|
|
|
|
return $form;
|
|
}
|
|
|
|
/**
|
|
* Implements hook_field_load().
|
|
*/
|
|
function menu_link_field_load($entity_type, $entities, $field, $instances, $langcode, &$items, $age) {
|
|
foreach ($entities as $id => $entity) {
|
|
foreach ($items[$id] as $delta => &$item) {
|
|
$item['options'] = !empty($item['options']) ? unserialize($item['options']) : array();
|
|
|
|
$item['external'] = (!empty($item['link_path']) && $item['link_path'] != '<front>' && url_is_external($item['link_path'])) ? 1 : 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements hook_field_validate().
|
|
*
|
|
* Possible error codes:
|
|
* - 'menu_link_duplicate': A menu link can only be referenced once per entity
|
|
* per field.
|
|
* - 'menu_link_invalid_parent': Selected parent menu link does not exist or may
|
|
* not be selected.
|
|
* - 'menu_link_no_entity_uri': No link path is provided while entity being
|
|
* edited has no URI of its own.
|
|
*/
|
|
function menu_link_field_validate($entity_type, $entity, $field, $instance, $langcode, &$items, &$errors) {
|
|
if (!empty($entity)) {
|
|
list($id, , ) = entity_extract_ids($entity_type, $entity);
|
|
}
|
|
|
|
// Build an array of existing menu link IDs so they can be loaded with
|
|
// menu_link_load_multiple();
|
|
$mlids = array();
|
|
foreach ($items as $delta => &$item) {
|
|
if (menu_link_field_is_empty($item, $field)) {
|
|
continue;
|
|
}
|
|
if (!empty($item['mlid']) && (int)$item['mlid'] > 0) {
|
|
$mlids[] = (int)$item['mlid'];
|
|
}
|
|
if (!empty($item['plid']) && (int)$item['plid'] > 0) {
|
|
$mlids[] = (int)$item['plid'];
|
|
}
|
|
}
|
|
$menu_links = !empty($mlids) ? menu_link_load_multiple($mlids) : array();
|
|
|
|
$mlids = array();
|
|
foreach ($items as $delta => &$item) {
|
|
if (menu_link_field_is_empty($item, $field)) {
|
|
continue;
|
|
}
|
|
|
|
if (empty($item['menu_name']) || !in_array($item['menu_name'], $instance['settings']['menu_options'])) {
|
|
$errors[$field['field_name']][$langcode][$delta][] = array(
|
|
'error' => 'menu_link_invalid_parent',
|
|
'message' => t('%name: Invalid menu name.', array('%name' => t($instance['label']))),
|
|
);
|
|
}
|
|
|
|
if (!empty($item['plid'])) {
|
|
if (!isset($menu_links[$item['plid']]) || !in_array($menu_links[$item['plid']]['menu_name'], $instance['settings']['menu_options'])) {
|
|
$errors[$field['field_name']][$langcode][$delta][] = array(
|
|
'error' => 'menu_link_invalid_parent',
|
|
'message' => t('%name: Invalid parent menu link.', array('%name' => t($instance['label']))),
|
|
);
|
|
}
|
|
}
|
|
|
|
if (!empty($item['mlid'])) {
|
|
if (isset($mlids[$item['mlid']])) {
|
|
$errors[$field['field_name']][$langcode][$delta][] = array(
|
|
'error' => 'menu_link_duplicate',
|
|
'message' => t('%name: A menu link can only be referenced once per entity per field.', array('%name' => t($instance['label']))),
|
|
);
|
|
}
|
|
else {
|
|
$mlids[$item['mlid']] = $item['mlid'];
|
|
}
|
|
}
|
|
|
|
if (!empty($field['settings']['link_path_field']) && empty($item['link_path']) && !empty($id)) {
|
|
$uri = entity_uri($entity_type, $entity);
|
|
if ($uri === NULL) {
|
|
$errors[$field['field_name']][$langcode][$delta][] = array(
|
|
'error' => 'menu_link_no_entity_uri',
|
|
'message' => t('%name: No link path is provided while entity being edited has no URI of its own.', array('%name' => t($instance['label']))),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements hook_field_presave().
|
|
*
|
|
* @see menu_link_menu_link_update()
|
|
*/
|
|
function menu_link_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) {
|
|
foreach ($items as $delta => &$item) {
|
|
if (empty($item['plid'])) {
|
|
$item['plid'] = 0;
|
|
}
|
|
|
|
$item += array(
|
|
'options' => array(),
|
|
'hidden' => 0,
|
|
'expanded' => 0,
|
|
'weight' => 0,
|
|
);
|
|
|
|
// Add a key to indicate we are saving menu links from within a field. This
|
|
// key is not stored in the database and will only be available during the
|
|
// current request.
|
|
// @see menu_link_menu_link_update()
|
|
$item['menu_link_field_save'] = $field['field_name'];
|
|
|
|
// TODO This could override the module column!
|
|
$item['module'] = 'menu_link';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements hook_field_insert().
|
|
*/
|
|
function menu_link_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items) {
|
|
$path = _menu_link_path($entity_type, $entity, $langcode);
|
|
|
|
foreach ($items as $delta => &$item) {
|
|
if (empty($field['settings']['link_path_field']) || empty($item['link_path'])) {
|
|
$item['link_path'] = $path;
|
|
}
|
|
|
|
if (!menu_link_save($item)) {
|
|
drupal_set_message(t('There was an error saving the menu link.'), 'error');
|
|
}
|
|
|
|
$item['options'] = serialize($item['options']);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements hook_field_update().
|
|
*/
|
|
function menu_link_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) {
|
|
list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
|
|
|
|
// Load the field items as they are stored in the database before update.
|
|
$original = entity_create_stub_entity($entity_type, array($id, $vid, $bundle));
|
|
field_attach_load($entity_type, array($id => $original), FIELD_LOAD_CURRENT, $options = array('field_id' => $field['id']));
|
|
|
|
// Initially asume that all links are being deleted; later on in this function,
|
|
// links that are kept are removed from this array.
|
|
$delete_links = array();
|
|
if (!empty($original->{$instance['field_name']}[$langcode])) {
|
|
foreach($original->{$instance['field_name']}[$langcode] as $item) {
|
|
$delete_links[$item['mlid']] = $item['mlid'];
|
|
}
|
|
}
|
|
|
|
$path = _menu_link_path($entity_type, $entity, $langcode);
|
|
|
|
foreach ($items as $delta => &$item) {
|
|
if (empty($field['settings']['link_path_field']) || empty($item['link_path'])) {
|
|
$item['link_path'] = $path;
|
|
}
|
|
|
|
if (!menu_link_save($item)) {
|
|
drupal_set_message(t('There was an error saving the menu link.'), 'error');
|
|
// TODO what to do?
|
|
}
|
|
|
|
$item['options'] = serialize($item['options']);
|
|
|
|
// Don't remove menu links that are being kept.
|
|
unset($delete_links[$item['mlid']]);
|
|
}
|
|
|
|
// Delete any menu links that are no longer used.
|
|
if (!empty($delete_links)) {
|
|
menu_link_delete_multiple($delete_links);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper function to build a menu link path based on an entity.
|
|
*/
|
|
function _menu_link_path($entity_type, $entity, $langcode) {
|
|
$uri = entity_uri($entity_type, $entity);
|
|
if (url_is_external($uri['path'])) {
|
|
$path = url($uri['path'], $uri['options']);
|
|
}
|
|
else {
|
|
$path = drupal_get_normal_path($uri['path'], $langcode);
|
|
if (!empty($uri['options']['query'])) {
|
|
$path .= (strpos($path, '?') !== FALSE ? '&' : '?') . drupal_http_build_query($uri['options']['query']);
|
|
}
|
|
if (!empty($uri['options']['fragment'])) {
|
|
$path .= '#' . $uri['options']['fragment'];
|
|
}
|
|
}
|
|
return $path;
|
|
}
|
|
|
|
/**
|
|
* Implements hook_field_delete().
|
|
*/
|
|
function menu_link_field_delete($entity_type, $entity, $field, $instance, $langcode, &$items) {
|
|
$mlids = array();
|
|
foreach ($items as $delta => $item) {
|
|
$mlids[] = $item['mlid'];
|
|
}
|
|
|
|
if (!empty($mlids)) {
|
|
// Only delete menu links that are (still) owned by the Menu link module.
|
|
$mlids = db_select('menu_links')
|
|
->fields('menu_links', array('mlid'))
|
|
->condition('module', 'menu_link')
|
|
->condition('mlid', $mlids)
|
|
->execute()
|
|
->fetchCol();
|
|
if (!empty($mlids)) {
|
|
menu_link_delete_multiple($mlids);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements hook_field_is_empty().
|
|
*/
|
|
function menu_link_field_is_empty($item, $field) {
|
|
if (!empty($item['delete']) || !trim($item['link_title']) || empty($item['menu_name']) || (empty($item['plid']) && $item['plid'] != '0')) {
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Implements hook_field_formatter_info().
|
|
*/
|
|
function menu_link_field_formatter_info() {
|
|
return array(
|
|
'menu_link_link' => array(
|
|
'label' => t('Link'),
|
|
'field types' => array('menu_link'),
|
|
),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Implements hook_field_formatter_prepare_view().
|
|
*/
|
|
function menu_link_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $displays) {
|
|
$mlids = array();
|
|
|
|
// Collect every possible menu link attached to any of the fieldable entities.
|
|
foreach ($entities as $id => $entity) {
|
|
foreach ($items[$id] as $delta => $item) {
|
|
// Prevent doing things twice.
|
|
if (empty($item['title'])) {
|
|
// Force the array key to prevent duplicates.
|
|
$mlids[$item['mlid']] = $item['mlid'];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!empty($mlids)) {
|
|
$menu_links = menu_link_load_multiple($mlids);
|
|
|
|
// Iterate through the fieldable entities again to attach the loaded menu
|
|
// link data.
|
|
foreach ($entities as $id => $entity) {
|
|
$rekey = FALSE;
|
|
|
|
foreach ($items[$id] as $delta => $item) {
|
|
// Check whether the menu link field instance value could be loaded.
|
|
if (isset($menu_links[$item['mlid']])) {
|
|
// Replace the instance value with the menu link data.
|
|
$items[$id][$delta] = $menu_links[$item['mlid']];
|
|
}
|
|
// Otherwise, unset the instance value, since the menu link does not exist.
|
|
else {
|
|
unset($items[$id][$delta]);
|
|
$rekey = TRUE;
|
|
}
|
|
}
|
|
|
|
if ($rekey) {
|
|
// Rekey the items array.
|
|
$items[$id] = array_values($items[$id]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements hook_field_formatter_view().
|
|
*/
|
|
function menu_link_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
|
|
$element = array();
|
|
|
|
// If the default formatter is set to "hidden" but the field is being
|
|
// displayed using this formatter, some module don't properly invoke
|
|
// hook_field_formatter_prepare_view() which is essential for this formatter.
|
|
// Let's make sure it has been invoked.
|
|
// TODO remove if resolved.
|
|
if ($instance['display']['default']['type'] == 'hidden') {
|
|
list($id, , ) = entity_extract_ids($entity_type, $entity);
|
|
$items = array($id => &$items);
|
|
menu_link_field_formatter_prepare_view(
|
|
$entity_type,
|
|
array($id => $entity),
|
|
$field,
|
|
array($id => $instance),
|
|
$langcode,
|
|
$items,
|
|
array($id => $display)
|
|
);
|
|
$items = $items[$id];
|
|
}
|
|
|
|
switch ($display['type']) {
|
|
case 'menu_link_link':
|
|
foreach ($items as $delta => $item) {
|
|
$element[$delta] = array(
|
|
'#type' => 'link',
|
|
'#title' => $item['title'],
|
|
'#href' => $item['href'],
|
|
'#options' => $item['localized_options'],
|
|
);
|
|
}
|
|
break;
|
|
}
|
|
|
|
return $element;
|
|
}
|
|
|
|
/**
|
|
* Implements hook_field_widget_info().
|
|
*/
|
|
function menu_link_field_widget_info() {
|
|
return array(
|
|
'menu_link_default' => array(
|
|
'label' => t('Select list'),
|
|
'field types' => array('menu_link'),
|
|
'settings' => array(
|
|
'description_field' => TRUE,
|
|
'expanded_field' => FALSE
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Implements hook_field_widget_settings_form().
|
|
*/
|
|
function menu_link_field_widget_settings_form($field, $instance) {
|
|
$widget = $instance['widget'];
|
|
$settings = $widget['settings'];
|
|
|
|
$form['description_field'] = array(
|
|
'#type' => 'checkbox',
|
|
'#title' => t('Enable <em>Description</em> field'),
|
|
'#default_value' => $settings['description_field'],
|
|
'#description' => t('The description field is used as a tooltip when the mouse hovers over the menu link.'),
|
|
'#weight' => 5,
|
|
);
|
|
$form['expanded_field'] = array(
|
|
'#type' => 'checkbox',
|
|
'#title' => t('Enable <em>Expanded</em> field'),
|
|
'#default_value' => $settings['expanded_field'],
|
|
'#description' => t('The expanded field is a checkbox which, if selected and the menu link has children, makes the menu link always appear expanded.'),
|
|
'#weight' => 5,
|
|
);
|
|
|
|
return $form;
|
|
}
|
|
|
|
/**
|
|
* Implements hook_field_widget_form().
|
|
*/
|
|
function menu_link_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
|
|
$module_path = drupal_get_path('module', 'menu_link');
|
|
|
|
$element += array(
|
|
'#input' => TRUE,
|
|
'#type' => ($field['cardinality'] == 1) ? 'fieldset' : 'container',
|
|
'#element_validate' => array('menu_link_field_widget_validate'),
|
|
'#attached' => array(
|
|
'js' => array($module_path . '/menu_link.field.js'),
|
|
'css' => array($module_path . '/menu_link.field.css'),
|
|
),
|
|
'#attributes' => array(
|
|
'class' => array('menu-link-item', 'menu-link-auto-title', 'menu-link-auto-fieldset-summary'),
|
|
)
|
|
);
|
|
|
|
// Populate the element with the link data.
|
|
foreach (array('mlid', 'link_path', 'options', 'hidden', 'expanded') as $key) {
|
|
if (isset($items[$delta][$key])) {
|
|
$element[$key] = array('#type' => 'value', '#value' => $items[$delta][$key]);
|
|
}
|
|
}
|
|
|
|
$menus = menu_get_menus();
|
|
$available_menus = array_combine($instance['settings']['menu_options'], $instance['settings']['menu_options']);
|
|
$options = _menu_get_options($menus, $available_menus, array('mlid' => 0));
|
|
|
|
$element['parent'] = array(
|
|
'#type' => 'select',
|
|
'#title' => t('Parent menu item'),
|
|
'#options' => $options,
|
|
'#empty_value' => '_none',
|
|
'#attributes' => array(
|
|
'class' => array('menu-link-item-parent'),
|
|
'title' => t('Choose the menu item to be the parent for this link.'),
|
|
),
|
|
'#required' => !empty($element['#required']),
|
|
);
|
|
if (isset($items[$delta]['menu_name'], $items[$delta]['plid'])) {
|
|
$element['parent']['#default_value'] = $items[$delta]['menu_name'] . ':' . $items[$delta]['plid'];
|
|
}
|
|
|
|
$element['weight'] = array(
|
|
'#type' => 'weight',
|
|
'#title' => t('Weight'),
|
|
'#delta' => 50,
|
|
'#default_value' => isset($items[$delta]['weight']) ? $items[$delta]['weight'] : 0,
|
|
'#attributes' => array(
|
|
'class' => array('menu-link-item-weight'),
|
|
'title' => t('Menu links with smaller weights are displayed before links with larger weights.'),
|
|
),
|
|
);
|
|
|
|
$element['link_title'] = array(
|
|
'#type' => 'textfield',
|
|
'#title' => t('Title'),
|
|
'#default_value' => isset($items[$delta]['link_title']) ? $items[$delta]['link_title'] : '',
|
|
'#size' => 50,
|
|
'#maxlength' => 255,
|
|
'#attributes' => array(
|
|
'class' => array('menu-link-item-title'),
|
|
),
|
|
'#description' => t('The text to be used for this link in the menu.'),
|
|
'#required' => !empty($element['#required']),
|
|
);
|
|
|
|
if (!empty($field['settings']['link_path_field'])) {
|
|
$element['link_path'] = array(
|
|
'#type' => 'textfield',
|
|
'#title' => t('Path'),
|
|
'#default_value' => isset($items[$delta]['link_path']) ? $items[$delta]['link_path'] : '',
|
|
'#size' => 50,
|
|
'#maxlength' => 255,
|
|
'#attributes' => array(
|
|
'class' => array('menu-link-item-path'),
|
|
),
|
|
'#description' => t('The path for this menu link.'),
|
|
);
|
|
}
|
|
|
|
if (!empty($instance['widget']['settings']['fragment_field'])) {
|
|
$element['fragment'] = array(
|
|
'#type' => 'textfield',
|
|
'#title' => t('URL Fragment'),
|
|
'#field_prefix' => '#',
|
|
'#default_value' => isset($items[$delta]['options']['attributes']['fragment']) ? $items[$delta]['options']['attributes']['fragment'] : '',
|
|
'#size' => 10,
|
|
'#maxlength' => 255,
|
|
'#attributes' => array(
|
|
'class' => array('menu-link-item-fragment'),
|
|
),
|
|
);
|
|
}
|
|
|
|
if (!empty($instance['widget']['settings']['expanded_field'])) {
|
|
$element['expanded'] = array(
|
|
'#type' => 'checkbox',
|
|
'#title' => t('Expanded'),
|
|
'#title_display' => 'before',
|
|
'#default_value' => isset($items[$delta]['expanded']) ? $items[$delta]['expanded'] : 0,
|
|
'#attributes' => array(
|
|
'class' => array('menu-link-item-expanded'),
|
|
'title' => t('If selected and this menu link has children, the menu will always appear expanded.'),
|
|
),
|
|
'#weight' => 5,
|
|
);
|
|
}
|
|
|
|
if (!empty($instance['widget']['settings']['description_field'])) {
|
|
$element['description'] = array(
|
|
'#type' => 'textarea',
|
|
'#title' => t('Description'),
|
|
'#default_value' => isset($items[$delta]['options']['attributes']['title']) ? $items[$delta]['options']['attributes']['title'] : '',
|
|
'#rows' => 1,
|
|
'#description' => t('Shown when hovering over the menu link.'),
|
|
'#attributes' => array('class' => array('menu-link-item-description')),
|
|
'#weight' => 10,
|
|
);
|
|
}
|
|
|
|
return $element;
|
|
}
|
|
|
|
/**
|
|
* Form element validate handler for menu link field widget.
|
|
*/
|
|
function menu_link_field_widget_validate($element, &$form_state) {
|
|
$item = drupal_array_get_nested_value($form_state['values'], $element['#parents']);
|
|
if (!empty($item['parent']) && $item['parent'] != '_none') {
|
|
list($item['menu_name'], $item['plid']) = explode(':', $item['parent']);
|
|
}
|
|
|
|
$item['link_title'] = trim($item['link_title']);
|
|
|
|
if (!empty($item['description']) && trim($item['description'])) {
|
|
$item['options']['attributes']['title'] = trim($item['description']);
|
|
}
|
|
else {
|
|
// If the description field was left empty, remove the title attribute
|
|
// from the menu link.
|
|
unset($item['options']['attributes']['title']);
|
|
}
|
|
|
|
if (!empty($item['fragment']) && trim($item['fragment'])) {
|
|
$item['options']['fragment'] = trim($item['fragment']);
|
|
}
|
|
else {
|
|
unset($item['options']['fragment']);
|
|
}
|
|
|
|
form_set_value($element, $item, $form_state);
|
|
}
|
|
|
|
/**
|
|
* Implements hook_field_widget_error().
|
|
*/
|
|
function menu_link_field_widget_error($element, $error, $form, &$form_state) {
|
|
switch ($error['error']) {
|
|
case 'menu_link_invalid_parent':
|
|
$error_element = $element['parent'];
|
|
break;
|
|
|
|
default:
|
|
$error_element = $element['link_title'];
|
|
break;
|
|
}
|
|
|
|
form_error($error_element, $error['message']);
|
|
}
|