arg(1)));
if ($node->nid) {
$items[] = array('path' => 'node/'. arg(1) .'/links', 'title' => t('links'),
'callback' => '_links_related_tab',
'access' => node_access('view', $node),
'weight' => 9,
'type' => MENU_LOCAL_TASK);
$items[] = array('path' => 'node/'. arg(1) .'/links/list', 'title' => t('list links'),
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => -10);
$items[] = array('path' => 'node/'. arg(1) .'/links/edit', 'title' => t('edit links'),
'callback' => '_links_related_tab_edit',
'access' => node_access('update', $node),
'weight' => 10,
'type' => MENU_LOCAL_TASK);
}
}
}
return $items;
}
function links_related_help($section="") {
switch($section) {
case 'admin/modules#description':
return t("Adds an extra URL field to nodes of admin-specified types.");
break;
}
}
// **************** Node functions ***********************
/**
* Implementation of hook_link().
*/
function links_related_link($type='', $node=NULL, $teaser=FALSE) {
// Only node links supported here
if ($type != 'node') {
return array();
}
return _links_related_list($node, FALSE, $teaser);
}
/**
* Retrieve the list of links to display with the node in the
* teaser, page, endnotes, or block.
*/
function _links_related_list(&$node, $page=FALSE, $teaser=FALSE) {
return links_get_list('links_related', $node, $page, $teaser);
}
/**
* Implementation of hook_settings()
*/
function links_related_settings() {
$form = array();
if (!module_exist('links')) {
drupal_set_message(t('The "links" module is disabled or not installed. Node links will not function until this is corrected. Check the availability of that module, and enable if needed, in the %modules.',array('%modules'=>l(t('modules administration page'),'admin/modules'))),'error');
}
$form['link_settings'] = array(
'#type' => 'fieldset',
'#title' => t('Link display settings'),
'#description' => t("Other settings for how the links behave and how they are displayed are available in the %linksettingspage.", array('%linksettingspage'=>l(t('links module settings page'),'admin/settings/links'))),
);
$form['link_settings']['links_related_display_teaser'] = array(
'#type' => 'select',
'#title' => t('Displayed links (teaser mode)'),
'#default_value' => variable_get('links_related_display_teaser','1'),
'#options' => array(0=>t('None'), 1=>1, 2=>2, 3=>3, 4=>4, 5=>5, -1=>t('All')),
'#description' => t('This setting controls how many links are displayed near the node title (in most themes) for the teaser or listing format.'),
);
$form['link_settings']['links_related_display_full'] = array(
'#type' => 'select',
'#title' => t('Displayed links (full-page mode)'),
'#default_value' => variable_get('links_related_display_full','1'),
'#options' => array(0=>t('None'), 1=>1, 2=>2, 3=>3, 4=>4, 5=>5, -1=>t('All')),
'#description' => t('This setting controls how many links are displayed near the node title (in most themes) in full-page displays.'),
);
$form['link_settings']['links_related_display_length'] = array(
'#type' => 'select',
'#title' => t('Display trim length'),
'#default_value' => variable_get('links_related_display_length','0'),
'#options' => array(0=>t('Unlimited'), 20=>20, 30=>30, 40=>40),
'#description' => t('Trims the displayed text (but not the actual URL) for links displayed near the article title. Does not affect display of links as end-notes within the article body.'),
);
$form['link_settings']['links_related_enable_list'] = array(
'#type' => 'checkbox',
'#title' => t('Show end-notes list'),
'#return_value' => 1,
'#default_value' => variable_get('links_related_enable_list','1'),
'#description' => t('If enabled, list of the article\'s related links will be appended to the end of the content, when in full-page display mode. This listing will also appear as part of the separate links tab (if that feature is enabled).'),
);
$form['link_settings']['links_related_enable_embed_list'] = array(
'#type' => 'checkbox',
'#title' => t('List embedded links'),
'#return_value' => 1,
'#default_value' => variable_get('links_related_enable_embed_list','1'),
'#description' => t('If enabled, a list of links contained within the article text will be appended to the end of the content, when in full-page display mode. This listing will also appear as part of the separate links tab (if that feature is enabled).'),
);
$form['link_settings']['links_related_enable_tab'] = array(
'#type' => 'checkbox',
'#title' => t('Show links tab'),
'#return_value' => 1,
'#default_value' => variable_get('links_related_enable_tab','0'),
'#description' => t('If enabled, a tab will be added to node displays to display the list of related and embedded links on their own separate page.'),
);
$form['types'] = array(
'#type' => 'markup',
'#value' =>'
' . t("To choose what content types have link URLs available, use the %workflowpage to set the 'has related links url' attribute for the desired node types. Note that you may need to revisit this setting if you add modules that define new node types, because the default is OFF for each content type." . "
", array('%workflowpage'=>l(t('default content workflow page'),'admin/settings/content-types'))),
);
return $form;
}
/**
* Implementation of hook_nodeapi()
*/
function links_related_nodeapi(&$node, $op, $teaser=NULL, $page=NULL) {
//print("nodeapi $op $node->nid
\n");
switch($op) {
// Load the values of the special table from the database as needed
case 'load':
if (variable_get('links_related_types_'. $node->type, FALSE) && ! is_array($_POST['edit']['links_related'])) {
$links = links_load_links_for_node($node->nid, 'links_related');
return array('links_related'=>$links);
}
break;
// Copy values from the POST array into the working copy of the node
case 'prepare':
if (variable_get('links_related_types_'. $node->type, FALSE) && ! isset($node->links_related)) {
_links_related_sort_post_links();
$node->links_related = is_array($_POST['edit']['links_related']) ? $_POST['edit']['links_related'] : array();
_links_related_sort_links($node->links_related);
}
break;
case 'validate':
if (variable_get('links_related_types_'. $node->type, FALSE) && isset($node->links_related)) {
_links_related_sort_post_links();
_links_related_sort_links($node->links_related);
foreach($node->links_related as $i=>$link) {
$node->links_related[$i]['url'] = links_normalize_url($link['url']);
}
}
break;
// Delete the values from a node that is being erased
// Don't care if it's "supposed" to have links -- delete if found anyway
case 'delete':
links_delete_links_for_node($node, 'links_related');
break;
// Update the values in an existing record, or insert a new record.
case 'insert':
case 'update':
if (variable_get('links_related_types_'. $node->type, FALSE)) {
links_save_links_for_node($node, 'links_related');
}
break;
case 'view':
$links =& links_load_links_for_node($node->nid, 'links_related');
$GLOBALS['links_related'] = array(
'node'=>$node, 'links'=>$links
);
$node->body .= _links_related_prep_view($node, FALSE, TRUE);
break;
}
}
function links_related_form_alter($form_id, &$form) {
$type = $form['type']['#value'];
switch ($form_id) {
case $type .'_node_settings':
// Nodetype-specific settings
$form['links_related'] = array(
'#type' => 'fieldset',
'#title' => t("Related links"),
'#collapsible' => false,
'#tree' => false,
);
$form['links_related']['links_related_types_'. $type] = array(
'#type' => 'radios',
'#title' => t('Allow related links'),
'#options' => array(0=>t('Disabled'), 1=>t('Optional'), 2=>t('Required')),
'#default_value' => variable_get('links_related_types_' .$type, 0),
'#description' => t('This setting enables or disables related links for this content type.'),
);
// Make sure the buttons are last
$form['buttons']['#weight'] = 15;
break;
case $type .'_node_form':
// The actual node edit form. This allows the users to enter the
// links_related into appropriate node types' edit screens.
$node = $form['#node'];
if (variable_get('links_related_types_'.$node->type, 0)) {
_links_related_sort_links($node->links_related);
// _links_related_sort_post_links();
$links =& $node->links_related;
$form['links_related'] = array(
'#type' => 'fieldset',
'#tree' => TRUE,
'#title' => t('Related Links'),
'#description' => t('Links are stored as part of the %page. Monitoring and dead link detection are centrally managed from there. To add more links, just click "Preview" to add another blank row. To remove a link from this article, just blank out its URL field or check the Delete box. If you blank out the title but leave the URL, then the system will suggest a title for you. The Weight allows you to determine the order in which links are displayed; lower numbers float to the top.',array('%page'=>l(t('links management feature'),'admin/links'))),
// Not collapsible if links are required
'#collapsible' => variable_get('links_related_types_'.$node->type, 0) < 2,
'#collapsed' => (count($links) == 0),
//'#weight' => -16,
);
$form['links_related']['#theme'] = 'links_related_form';
// Existing links first
$i = 0;
foreach ($links as $link) {
$form['links_related'][$i] = links_related_form_line($link);
$i++;
}
$form['links_related'][$i] = links_related_form_line(NULL);
if (variable_get('links_related_types_'.$node->type, 0) >= 2) {
// The first one is required
$form['links_related'][0]['url']['#required'] = 1;
$form['links_related']['#title'] .= ' '.t('(at least one link required for this content type)');
}
}
break;
}
}
/**
* This function wraps the related links form fields in a table for display.
*/
function theme_links_related_form(&$form) {
$header = array(t('URL'), t('Title'), t('Weight'), t('Delete'));
$rows = array();
foreach (element_children($form) as $key) {
$row = array();
$row[] = drupal_render($form[$key]['url']);
$row[] = drupal_render($form[$key]['link_title']);
$row[] = drupal_render($form[$key]['weight']);
$row[] = drupal_render($form[$key]['delete']);
$rows[] = $row;
}
$output = theme('table', $header, $rows);
$output .= drupal_render($form);
return $output;
}
/**
* This function wraps the embedded links form fields in a table for display.
*/
function theme_links_related_embedded_form(&$form) {
$header = array(t('Link Title').'
'.t('and URL'), t('Operations'));
$rows = array();
foreach (element_children($form) as $key) {
$row = array();
$row[] = drupal_render($form[$key]['link_title']) . '
' . drupal_render($form[$key]['url']);
$row[] = drupal_render($form[$key]['op']);
$rows[] = $row;
}
$output = theme('table', $header, $rows);
$output .= drupal_render($form);
return $output;
}
/**
* Implementation of hook_block()
*/
function links_related_block($op = 'list', $delta = 0, $edit = array()) {
switch($op) {
case 'list':
$blocks = array(0=>array('info'=>t('Related links')));
return $blocks;
case 'view':
$block = array();
switch($delta) {
case 0:
$block['subject'] = t('Related Links');
if (arg(0) == 'node' && is_numeric(arg(1)) && is_array($GLOBALS['links_related']['links']) && is_object($GLOBALS['links_related']['node'])) {
$links =& $GLOBALS['links_related']['links'];
$node =& $GLOBALS['links_related']['node'];
$list =& _links_related_list($node, TRUE, FALSE);
if (count($links)) {
$content = '';
$block['content'] = $content;
}
}
}
return $block;
}
}
/********************************************************************
* Internal functions
********************************************************************/
/**
* Builds one one line of the data entry form for links
*/
function links_related_form_line($link=NULL) {
$link = (array)$link;
$entry['url'] = array(
'#type' => 'textfield',
'#default_value' => $link['url'],
);
$entry['link_title'] = array(
'#type' => 'textfield',
'#default_value' => $link['link_title'],
);
$entry['weight'] = array(
'#type' => 'weight',
'#default_value' => (empty($link['weight']) ? 0 : $link['weight']),
'#delta' => 5,
);
$entry['delete'] = array(
'#type' => 'checkbox',
'#return_value' => 1,
'#default_value' => $link['delete'],
);
return $entry;
}
/**
* Compares two subarrays from $node->links_related and returns
* an integer -1, 0, or 1 based on that comparison. (See PHP
* function uasort() for details.)
*/
function _links_related_linkcompare($link1, $link2) {
if (intval($link1['weight']) == intval($link2['weight'])) {
if ($link1['link_title'] == $link2['link_title']) {
return 0;
} else {
return ($link1['link_title'] < $link2['link_title']) ? -1 : 1;
}
} else {
//print("Sort by weight ".intval($link1['weight'])." vs ".intval($link2['weight'])."
\n");
return (intval($link1['weight']) < intval($link2['weight'])) ? -1 : 1;
}
}
/**
* Sorts the links by weight, keeping their existing order otherwise.
* Also removes any empty links and those with the "delete" flag set.
* $links is an integer-subscripted array of links.
*/
function _links_related_sort_links(&$links) {
if (is_array($links)) {
foreach ($links as $i=>$link) {
if ($link['delete'] || empty($link['url'])) {
unset($links[$i]);
}
}
uasort($links, '_links_related_linkcompare');
}
}
/**
* Works like _links_related_sort_links(), except operates
* on $_POST['edit']['links_related'] intrinsically, rather
* than accepting a node as a parameter.
*/
function _links_related_sort_post_links() {
if (is_array($_POST['edit']['links_related'])) {
foreach ($_POST['edit']['links_related'] as $i=>$link) {
if ($link['delete'] || (empty($link['url']) && empty($link['link_title']))) {
unset($_POST['edit']['links_related'][$i]);
}
}
uasort($_POST['edit']['links_related'], '_links_related_linkcompare');
}
}
/**
* Prepares a view of the links depending on the node viewing mode
* and the links_related module settings.
*/
function _links_related_prep_view(&$node, $teaser=FALSE, $page=FALSE) {
if ($teaser) {
return '';
}
$do_related = variable_get('links_related_enable_list',TRUE);
$do_embed = variable_get('links_related_enable_embed_list',TRUE);
if ($do_related || $do_embed) {
$links =& links_load_links_for_node($node->nid, 'links_related');
if (! count($links)) {
return '';
}
$html = '';
if ($do_related) {
$list = _links_related_list($node, TRUE, FALSE);
$html .= _links_related_format_list($list, t('Related Links'));
}
if ($do_embed) {
$html .= _links_related_embedded($node, FALSE);
}
}
return $html;
}
/**
* Accepts a list of predefined HTML links (as returned from l() typically)
* and returns the list HTML around them plus an optional level-2 heading
* at the top of the list. The entire thing is surrounded by a DIV tag
* with a class of "links_related_endnotes" to make it more themeable.
*/
function _links_related_format_list($list, $heading='') {
// Trivial rejection
if (count($list) < 1) {
return '';
}
$html = '';
return $html;
}
/**
* Displays the standalone "links" tab for a node
*/
function _links_related_tab() {
$html = '';
if (arg(0) == 'node' && is_numeric(arg(1))) {
$node = node_load(array('nid' => arg(1)));
if ($node->nid) {
drupal_set_title($node->title);
$html .= _links_related_prep_view($node, FALSE, FALSE);
} else {
drupal_not_found();
}
}
print(theme('page',$html));
}
/**
* Displays a page to allow management of the embedded links.
*/
function _links_related_tab_edit() {
$html = _links_related_embedded(NULL, TRUE);
print(theme('page',$html));
}
/**
* Provides either a displayable (read-only) listing, in endnotes
* format, or an editable form, showing all of the embedded links
* in the node body.
*/
function _links_related_embedded($node=NULL, $editable=FALSE) {
if (! is_object($node)) {
// Try to find the node ID from the Drupal path
if (arg(0) == 'node' && is_numeric(arg(1))) {
$nid = intval(arg(1));
$node2 = node_load(array('nid'=>$nid));
} else {
// Dereference to be sure we don't touch original.
$node2 = $node;
}
if (! $node2->nid) {
return array();
}
} else {
// Some node types -- notably book pages -- dink around with the
// body and add extraneous links that we don't want. So we have
// to go back to the database for the original body.
if ($node->type == 'book') {
$node2 = node_load(array('nid'=>$node->nid));
} else {
$node2 = $node;
}
}
$text = $node2->body;
// TODO -- add an API hook that allows modules to expose other text to this
// link-hunting process
$links =& links_find_links($text);
if ($editable) {
return _links_related_format_embedded_edit($links);
} else {
return _links_related_format_embedded_list($links);
}
}
/**
* This function simply displays the embedded links in endnotes-style, without
* providing a form for editing.
*/
function _links_related_format_embedded_list($links) {
$list = array();
foreach ($links as $i=>$link) {
$url = $link['url']; // Get the normalized URL
$display_url = htmlspecialchars(strlen($url) > 60 ? substr($url, 0, 57) . '...' : $url);
$display_text = (empty($link['title']) || $link['title'] == $url) ? $display_url : $link['title'];
$attribs = array('title'=>t('Matched text: %match', array('%match'=>$link['matched'])));
$list[] = links_l($display_text, $url, $attribs);
}
return _links_related_format_list($list, t('Links from Article Text'));
}
/**
* Given an array of links as returned from links_find_links(),
* this function returns an HTML fragment with an editable form
* to manage those links.
*/
function _links_related_format_embedded_edit($links) {
$form = array(
'links_embed' => array(
'#type' => 'fieldset',
'#title' => t('Embedded Links'),
'#collapsible' => 1,
'#tree' => 1,
'#description' => t('These are the links found inside the page content. You can choose to replace the direct URLs with managed links from the site\'s links catalog (adding new links if necessary), or to replace existing catalog links with the actual direct URLs. Titles added or changed here do not override titles for the same link if it appears on other pages.'),
),
);
$goto = url('links/goto') . '/';
foreach ($links as $i=>$link) {
$url = $link['url']; // Get the normalized URL
# Set $reverse TRUE if the URL is already one of our "goto" URLs
if (preg_match('!^(?:/*)'.$goto.'!', $url)) {
$reverse = TRUE;
} else {
$reverse = FALSE;
}
$display_url = htmlspecialchars(strlen($url) > 60 ? substr($url, 0, 57) . '...' : $url);
$attribs = array('title'=>t('Matched text: %match', array('%match'=>$link['matched'])));
$form_row = array();
$form_row['link_title'] = array(
'#type' => 'textfield',
'#title' => t('Cataloged title'),
'#size' => 40,
'#maxlength' => 100,
'#default_value' => $link['title'],
'#description' => t('URL: ') . links_l($display_url, $url, $attribs),
);
$form_row['links_op'] = array(
'#type' => 'checkbox',
'#title' => $reverse ? t('Make non-catalog link') : t('Make catalog link'),
'#default_value' => '0',
'#return_value' => $reverse ? 'uncatalog' : 'catalog',
);
$form['links_embed'][$i] = $form_row;
}
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Save changes -- NOT YET FUNCTIONAL'),
);
$html = drupal_get_form('links_related_embed', $form);
return $html;
}