? cvs
? links_indexedby.patch
? links_related_resurrection.patch
? links_return_null_on_failure.patch
Index: links.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/links/links.inc,v
retrieving revision 1.20
diff -u -p -r1.20 links.inc
--- links.inc	11 Sep 2006 01:46:21 -0000	1.20
+++ links.inc	25 Apr 2007 15:25:19 -0000
@@ -561,6 +561,7 @@ function links_hash_url($url) {
  */
 function links_get_link_where_sql($link_spec, $tablename="{links}") {
   $where = " WHERE ".$tablename;
+  // @TODO fix this - it just fails if the hash happens to start with a numeric (DOH)
   if (intval($link_spec)) {
     $where = $where . ".lid=" . $link_spec . " ";
   } else {
@@ -577,6 +578,8 @@ function links_get_link_where_sql($link_
 /**
  * Given a URL, URL hash, or link ID (lid), retrieves any existing link record
  * from the database as an array.
+ * 
+ * @param string $link_spec The URL or hash of the URL to be fetched
  */
 function links_get_link($link_spec) {
   $where = links_get_link_where_sql($link_spec);
@@ -586,9 +589,10 @@ function links_get_link($link_spec) {
     watchdog("error","links could not retrieve link matching '".$link_spec."'");
     return FALSE;
   }
-  $link = db_fetch_array($result);
-  $link['link_title'] = trim($link['link_title']);
-  $link['url'] = trim($link['url']);
+  if($link = db_fetch_array($result)){
+    $link['link_title'] = trim($link['link_title']);
+    $link['url'] = trim($link['url']);
+  }
   return $link;
 }
 
@@ -752,22 +756,17 @@ function links_delete_links_for_node($no
  *                 links.module).
  */
 function links_save_links_for_node(&$node, $module='links_links', $global=FALSE) {
-  $oldlinks_byrow =& links_load_links_for_node($node->nid, $module);
-  // Need it arranged by link ID, for lookup
-  $oldlinks = array();
-  for ($i=0; $i<count($oldlinks_byrow); $i++) {
-    $oldlinks[$oldlinks_byrow[$i]['lid']] = $oldlinks_byrow[$i];
-  }
+  $oldlinks =& links_load_links_for_node($node->nid, $module, NULL, NULL, 'lid');
   $links =& $node->$module;
   $all_ok = TRUE;
   // This will track the link IDs that should be kept
   $lids = array();
-  for ($i=0; $i<count($links); $i++) {
-    $url = links_normalize_url($links[$i]['url']);
-    $links[$i]['url'] = $url;
-    $title = trim($links[$i]['link_title']);
-    $links[$i]['link_title'] = $title;
-    if (empty($url) || $links[$i]['delete']) {
+  foreach ($links as $i => &$link) {
+    $url = links_normalize_url($link['url']);
+    $link['url'] = $url;
+    $title = trim($link['link_title']);
+    $link['link_title'] = $title;
+    if (empty($url) || $link['delete']) {
       // Ignore empty URL, either from unused form fields or from
       // blanked-out existing URL field. In the latter case, this
       // link will be removed from the node later in this function.
@@ -777,7 +776,7 @@ function links_save_links_for_node(&$nod
     }
     // Make sure the base link record exists, one way
     // or another.
-    $lid = links_put_link($url, $links[$i]['link_title']);
+    $lid = links_put_link($url, $link['link_title']);
     // $base_rec gets the full record from {links} whether
     // it was inserted or just retrieved
     $base_rec = links_get_link($lid);
@@ -789,9 +788,9 @@ function links_save_links_for_node(&$nod
       $old_rec = $base_rec;
     }
     if ($base_rec['lid']) {
-      $links[$i]['lid'] = $old_rec['lid'];
+      $link['lid'] = $old_rec['lid'];
       $lids[] = $old_rec['lid'];
-      $new_title = trim($links[$i]['link_title']);
+      $new_title = trim($link['link_title']);
       $master_title = trim($base_rec['link_title']);
       if (empty($new_title)) {
         $suggested = TRUE;
@@ -822,7 +821,7 @@ function links_save_links_for_node(&$nod
       drupal_set_message(t('Cannot find or create link record for %url',array('%url'=>$url)),'error');
       $all_ok = FALSE;
     }
-    $weight = intval($links[$i]['weight']);
+    $weight = intval($link['weight']);
     if (isset($oldlinks[$lid])) {
       $sql = "UPDATE {links_node} SET link_title='%s', weight=%d WHERE lid=%d AND nid=%d AND module='%s'";
       $op  = t('update');
@@ -832,7 +831,7 @@ function links_save_links_for_node(&$nod
     }
     $result = db_query($sql, $new_title, $weight, $lid, $node->nid, $module);
     if (! $result) {
-      drupal_set_message(t('Unable to %op link &quot;%t&quot; in database.', array('%op'=>$op, '%t'=>$links[$i]['link_title'])),'error');
+      drupal_set_message(t('Unable to %op link &quot;%t&quot; in database.', array('%op'=>$op, '%t'=>$link['link_title'])),'error');
       $all_ok = FALSE;
     }
   }
@@ -867,7 +866,7 @@ function links_save_links_for_node(&$nod
  * handling a "goto" URL pattern from the browser that specifies nid but not lid.
  * This is used, among other things, for legacy compatibility with weblink.module.
  */
-function links_load_links_for_node($nid, $module='links', $lid=0, $firstonly=FALSE) {
+function links_load_links_for_node($nid, $module='links', $lid=0, $firstonly=FALSE, $indexedby = NULL) {
   $links = array();
   if ($lid) {
     $sql = "SELECT l.lid, ln.nid, url, url_md5, weight, clicks, module, l.link_title AS link_link_title, ln.link_title AS node_link_title FROM {links_node} ln LEFT JOIN {links} l ON l.lid=ln.lid WHERE ln.nid=%d AND l.lid=%d";
@@ -890,7 +889,12 @@ function links_load_links_for_node($nid,
     while ($row = db_fetch_array($result)) {
       $row['link_title'] = trim($row['link_title']);
       $row['link_title'] = trim(empty($row['node_link_title']) ? $row['link_link_title'] : $row['node_link_title']);
-      $links[] = $row;
+      if($indexedby){
+        $links[$row[$indexedby]] = $row;
+      } 
+      else {
+        $links[] = $row;
+      }        
       if ($firstonly) {
         break;
       }
@@ -1281,6 +1285,21 @@ function links_stats_popular($limit=10, 
   return $rows;
 }
 
+/**
+ * return a trimmed rendition of a potentially long URL
+ */
+function theme_display_url($url){
+  $parts = parse_url($url);
+  $parts['host'] = str_replace('www.','',$parts['host']);
+  if(strlen($parts['host']) > 20){
+    $parts['host'] = substr( $parts['host'], 0, 17 ) . '...';
+  }
+  if(strlen($parts['path']) > 20){
+    $parts['path'] = '/...' . substr( $parts['path'], strlen($parts['path']) -17 );
+  }
+  return $parts['host'].$parts['path'];
+}
+
 /*!
  * @}
  */
Index: links_admin.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/links/links_admin.module,v
retrieving revision 1.2.2.1
diff -u -p -r1.2.2.1 links_admin.module
--- links_admin.module	28 Jan 2007 03:39:24 -0000	1.2.2.1
+++ links_admin.module	25 Apr 2007 15:25:20 -0000
@@ -88,7 +88,7 @@ function links_admin_menu($may_cache) {
 
 function links_admin_list_page($lid = NULL) {
 // TODO: The dead code after this is intentional; feature not yet enabled.
-  return t("<p>This feature is still under development.</p>");
+  #return t("<p>This feature is still under development.</p>");
   if (isset($lid)) {
     return drupal_get_form('links_admin_nodes', links_admin_nodes($lid));
   }
@@ -271,7 +271,7 @@ function links_admin_links() {
       '#value' => 'Update',
     );
   }
-  if (!count($rows)) {
+  if (!count($form['links'])) {
     $rows = array(array('data' => t('No links available.'), 'colspan' => '4'));
   } else {
     $form['pager'] = array('#value' => theme('pager', NULL, $pagelines, 0));
Index: links_related.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/links/links_related.module,v
retrieving revision 1.15.2.3
diff -u -p -r1.15.2.3 links_related.module
--- links_related.module	18 Mar 2007 02:11:41 -0000	1.15.2.3
+++ links_related.module	25 Apr 2007 15:25:20 -0000
@@ -249,7 +249,7 @@ function links_related_form_alter($form_
           '#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. <strong>To add more links, just click &quot;Preview&quot; to add another blank row. To remove a link from this article, just blank out its URL field or check the Delete box.</strong> 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'))),
+          '#description' => t('Links are stored as part of the !page. Monitoring and dead link detection are centrally managed from there. <strong>To add more links, just click &quot;Preview&quot; to add another blank row. To remove a link from this article, just blank out its URL field or check the Delete box.</strong> 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/content/links'))),
           // Not collapsible if links are required
           '#collapsible' => variable_get('links_related_types_'.$node->type, 0) < 2,
           '#collapsed' => (count($links) == 0),
@@ -298,24 +298,6 @@ function theme_links_related_form(&$form
 }
 
 /**
- * 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').'<br />'.t('and URL'), t('Operations'));
-
-  $rows = array();
-  foreach (element_children($form) as $key) {
-    $row = array();
-    $row[] = drupal_render($form[$key]['link_title']) . '<br />' . 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()) {
@@ -544,7 +526,7 @@ function _links_related_embedded($node=N
   // link-hunting process
   $links =& links_find_links($text);
   if ($editable) {
-    return _links_related_format_embedded_edit($links);
+    return drupal_get_form('links_related_embedded_form', $links, $node2);
   } else {
     return _links_related_format_embedded_list($links);
   }
@@ -568,10 +550,12 @@ function _links_related_format_embedded_
 
 /**
  * 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.
+ * this function returns an editable form to manage those links.
+ * 
+ * @param array $links link definitions (as extracted from the text)
+ * @param node $node Context node
  */
-function _links_related_format_embedded_edit($links) {
+function links_related_embedded_form($links, $node) {
   $form = array(
     'links_embed' => array(
       '#type' => 'fieldset',
@@ -580,39 +564,227 @@ function _links_related_format_embedded_
       '#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.'),
     ),
+    'nid' => array(
+        '#type' => 'hidden',
+        '#default_value' => $node->nid,
+    )
   );
   $goto = url('links/goto') . '/';
+  $linked_links = links_load_links_for_node($node->nid, '', 0, FALSE, 'url');
   foreach ($links as $i=>$link) {
-    $url = $link['url']; // Get the normalized URL
+
+    $url = links_normalize_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(
+    $exists = FALSE; // already in the catalog
+    $attached = FALSE; // already associated with this node
+    $weblink_exists = FALSE; // whether a stand-alone weblinks node page exists for this already
+    
+    $display_url = theme('display_url',$url);
+    $attributes = array('title'=>t('Matched text: !match', array('!match'=>$link['matched'])));
+
+    $existing_link = links_get_link($url);
+    if($existing_link['url']){
+      $exists = TRUE;
+      $attributes['class'] = 'existing';
+      # dsm("Found an entry for ".$existing_link['url']." already");
+
+      // already attached to this node, cool
+      if($linked_links[$url]){
+        $existing_link = $linked_links[$url];
+        $attributes['class'] .= ' attached';
+        $attached = TRUE;
+      }
+
+        // lookup to see if it has a weblinks node attached
+        $referers = links_get_link_node_refs($existing_link['lid']);
+        foreach($referers as $ref){
+          if($ref['module'] == 'links_weblink'){
+            $weblink_exists = $ref['nid'];
+          }
+        }
+
+    } else {
+      # dsm("$url does not yet have an entry");
+      unset($existing_link); // an empty placeholder gets in the way @TODO ensure the suggested patch to the API goes through 
+    }
+ 
+    $form_row = array(
+      '#attributes' => $attributes,
+    );
+
+    if($existing_link && ($existing_link['link_title'] != $link['title'])){
+      $aka = t('also known as: %aka', array('%aka' => $existing_link['link_title']));
+    } 
+    else {$aka = '';}
+    
+    $form_row['title'] = array(
       '#type' => 'textfield',
-      '#title' => t('Cataloged title'),
+      '#title' => t('Found title'),
       '#size' => 40,
-      '#maxlength' => 100,
       '#default_value' => $link['title'],
-      '#description' => t('URL: ') . l($display_url, $url, $attribs),
+      '#description' => $aka, 
+    );
+    $form_row['url'] = array(
+      '#type' => 'textfield',
+      '#title' => t('URL') ." <small>". l($display_url, $url) ."</small> " ,
+      '#size' => 40,
+      '#maxlength' => 500, // required to handle insane redirect URLs. Drupal sets it to 128, so cannot leave it open
+      '#default_value' => $url,
+    );
+    $form_row['op'] = array(
+      '#tree' => TRUE
     );
-    $form_row['links_op'] = array(
+
+    if(! $exists) {
+      $form_row['op']['link_item'] = array(
+        '#type' => 'checkbox',
+        '#title' => $reverse ? t('Make non-catalog link') : t('Make catalog link'),
+        '#default_value' => '0',
+        '#return_value'  => $reverse ? 'delete' : 'create',
+      );
+    } 
+    else {
+      if(module_exists('links_admin')){
+        $form_row['op']['link_item'] = array(
+          '#value' => l(t('Administer link'),'admin/content/links/'.$existing_link['lid'] )
+        );
+      }
+    }
+
+    $form_row['op']['attach_to_node'] = array(
       '#type' => 'checkbox',
-      '#title' => $reverse ? t('Make non-catalog link') : t('Make catalog link'),
+      '#title' => $attached ? t('Detach from this node') : t('Attach to this node'),
       '#default_value' => '0',
-      '#return_value'  => $reverse ? 'uncatalog' : 'catalog',
+      '#return_value'  => $attached ? 'detach' : 'attach',
     );
+
+    // Add the option to make a weblink page on the fly
+    if(module_exists('links_weblink')){
+      if($weblink_exists){
+        $form_row['op']['weblink'] = array(
+          '#value' => l(t('Edit'),'node/'.$weblink_exists.'/edit') .' '. l(t('Weblink page'),'node/'.$weblink_exists)
+        );
+      } 
+      else {
+        $form_row['op']['weblink'] = array(
+          '#type' => 'checkbox',
+          '#title' => t("Create weblink page"),
+          '#default_value' => '0',
+          '#return_value'  => 'create',
+        );
+      }
+    }
+
+    // if we have an existing link id, remember it. It may come in handy, especially for ajax later on
+    if($existing_link['lid']){
+      $form_row['lid'] = array(
+        '#type' => 'hidden',
+        '#default_value' => $existing_link['lid'],
+      );
+    }
+    
     $form['links_embed'][$i] = $form_row;
   }
   $form['submit'] = array(
     '#type' => 'submit',
-    '#value' => t('Save changes -- NOT YET FUNCTIONAL'),
+    '#value' => t('Save changes'),
   );
-  $html = drupal_get_form('links_related_embedded', $form);
-  return $html;
+  return $form;
+}
+
+function links_related_embedded_form_submit($form_id,$form_values) {
+  $node = node_load($form_values['nid']);
+  foreach($form_values['links_embed'] as $link_entry){
+    foreach($link_entry['op'] as $op => $action){
+      // The op handling is nested so we can do multiple 
+      // maintainence actions independantly 
+      if($action){
+        # dsm("need to modify $op to $action on ".$link_entry['url']); 
+        switch($op){
+          case 'link_item' :
+            if($action == 'create'){
+              // make it but don't link it
+              $link_entry['lid'] = links_put_link($link_entry['url'], $link_entry['title'] );
+            }
+          break; 
+          case 'attach_to_node' :
+            if($action == 'attach'){
+              // the API recommend this action be done in bulk. See later
+              $link_entry['link_title'] = $link_entry['title'];
+              $node->links_related[$link_entry['url']] = $link_entry;
+            }
+            if($action == 'detach'){
+              unset($node->links_related[$link_entry['url']]);
+            }
+          break; 
+          case 'weblink' :
+            if($action == 'create'){
+              global $user;
+              $new_node = (object)array(
+                'title' => $link_entry['title'],
+                'body' => $link_entry['title'],
+                'type' => 'weblink',
+                'uid' => $user->uid,
+                'status' => 1,
+                'links_weblink_url' => $link_entry['url'],
+              );
+              node_save($new_node);
+              drupal_set_message(t('Created a standalone weblink page for the link !page_link. !edit_link',
+                array(
+                  '!page_link' => l($new_node->title,'node/'.$new_node->nid),
+                  '!edit_link' => l(t('Edit'), 'node/'.$new_node->nid.'/edit')
+                )
+              ));
+            }
+          break;
+        }
+      }
+    } 
+  }
+  // @see links_save_links_for_node();
+  links_save_links_for_node(&$node, 'links_related');
 }
+
+/**
+ * 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').'<br />'.t('and URL'), t('Operations'));
+  $rows = array();
+  foreach (element_children($form['links_embed']) as $key) {
+    $row = $form['links_embed'][$key]['#attributes'] ? $form['links_embed'][$key]['#attributes'] : array();
+    $row['data'][] = drupal_render($form['links_embed'][$key]['title']) . '<br />' . drupal_render($form['links_embed'][$key]['url']);
+    #$row['data'][] = $form['links_embed'][$key]['#attributes']['class'];
+    $row['data'][] = drupal_render($form['links_embed'][$key]['op']);
+    $rows[] = $row;
+  }
+  // pre-render this set into a table before letting Drupal do the rest of the form
+  $form['links_embed']['#children'] = theme('table', $header, $rows);
+  $output .= drupal_render($form);
+  return $output;
+}
+
+/**
+ * Return an array of all places the given link is associated with - a reverse
+ * lookup on the links_node table.
+ * 
+ * Used to find if this link is mentioned elsewhere already
+ * 
+ * @TODO move this into the API if appropriate
+ * 
+ */
+function links_get_link_node_refs($lid) {
+  $sql = "SELECT * FROM {links_node} WHERE lid = %d";
+  $result = db_query($sql,$lid);
+  $referrers = array();
+  while($link_ref = db_fetch_array($result)){
+    $referrers[$link_ref['nid']] = $link_ref;
+  }
+  return $referrers;
+}
+
