Index: inline.install
===================================================================
RCS file: inline.install
diff -N inline.install
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ inline.install	11 Aug 2009 22:35:03 -0000
@@ -0,0 +1,75 @@
+<?php
+// $Id: inline.module,v 1.39 2009/08/11 20:29:35 sun Exp $
+
+/**
+ * @file
+ * Inline installation functions.
+ */
+
+/**
+ * Implementation of hook_schema().
+ */
+function inline_schema() {
+  $schema['inline'] = array(
+    'description' => t('Stores inline tags.'),
+    'fields' => array(
+      'iid' => array('type' => 'serial'),
+      'parameters' => array('type' => 'text', 'size' => 'normal'),
+      'status' => array('type' => 'int', 'not null' => TRUE, 'default' => 0,
+        'description' => 'A flag indicating whether the tag is temporary (1) or permanent (0).',
+      ),
+      'timestamp' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0,
+        'description' => 'UNIX timestamp for when the tag was added.',
+      ),
+    ),
+    'primary key' => array('iid'),
+    'indexes' => array(
+      'status' => array('status'),
+    ),
+  );
+  return $schema;
+}
+
+/**
+ * Implementation of hook_install().
+ */
+function inline_install() {
+  drupal_install_schema('inline');
+}
+
+/**
+ * Implementation of hook_uninstall()
+ */
+function inline_uninstall() {
+  drupal_uninstall_schema('inline');
+}
+
+/**
+ * Create new {inline} table.
+ */
+function inline_update_6001() {
+  $ret = array();
+  if (db_table_exists('inline')) {
+    return $ret;
+  }
+  // Install new schema.
+  db_create_table($ret, 'inline', array(
+    'description' => t('Stores inline tags.'),
+    'fields' => array(
+      'iid' => array('type' => 'serial'),
+      'parameters' => array('type' => 'text', 'size' => 'normal'),
+      'status' => array('type' => 'int', 'not null' => TRUE, 'default' => 0,
+        'description' => 'A flag indicating whether the tag is temporary (1) or permanent (0).',
+      ),
+      'timestamp' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0,
+        'description' => 'UNIX timestamp for when the tag was added.',
+      ),
+    ),
+    'primary key' => array('iid'),
+    'indexes' => array(
+      'status' => array('status'),
+    ),
+  ));
+  return $ret;
+}
+
Index: inline.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/inline/inline.module,v
retrieving revision 1.37
diff -u -p -r1.37 inline.module
--- inline.module	11 Aug 2009 19:06:42 -0000	1.37
+++ inline.module	12 Aug 2009 02:35:44 -0000
@@ -4,6 +4,8 @@
 /**
  * @file
  * Inline filter tag macro processing and rendering API for Drupal.
+ *
+ * @todo Refactor CRUD functions and inline macro "objects" into real objects.
  */
 
 /**
@@ -55,12 +57,19 @@ function inline_filter($op, $delta = 0, 
       return $text;
 
     case 'process':
-      $processed = FALSE;
+      // @todo Use Inline API for our own tags; only difference is that we probably
+      //   want to keep any macros that cannot be "rendered" for any reason --
+      //   or move this into inline_get_macros() ?
+      preg_match_all('/\[inline\|iid=(\d+)\]/', $text, $matches);
+      foreach ($matches[1] as $key => $iid) {
+        if ($params = inline_tag_load($iid)) {
+          $text = str_replace($matches[0][$key], inline_build_macro($params), $text);
+        }
+      }
       foreach (inline_get_macros($text) as $macro => $params) {
         $validation = inline_validate_params($params);
         if (is_bool($validation) && $validation) {
           $text = str_replace($macro, inline_render($params), $text);
-          $processed = TRUE;
         }
         else {
           $text = str_replace($macro, (is_string($validation) ? $validation : ''), $text);
@@ -72,6 +81,8 @@ function inline_filter($op, $delta = 0, 
 
 /**
  * Invoke a hook_inline() implementation.
+ *
+ * @todo Revamp this; totally weird.
  */
 function inline_invoke($params, $op) {
   // @todo Add static caching for function lookups here.
@@ -92,6 +103,9 @@ function inline_invoke($params, $op) {
 
 /**
  * Return all inline macros as an array.
+ *
+ * @todo Use placeholders for #default_value and/or additional properties.
+ *   Or introduce a new $op 'alter' for hook_inline?
  */
 function inline_get_macros($text) {
   $macros = array();
@@ -157,6 +171,9 @@ function inline_get_macros($text) {
         $params['params'][$key] = $value;
       }
     }
+    if (isset($params['params']['iid'])) {
+      $params['iid'] = $params['params']['iid'];
+    }
     // The full unaltered tag is the key for the filter attributes array.
     $macros[$matches[0][$n]] = $params;
   }
@@ -244,100 +261,6 @@ function inline_render($params) {
 }
 
 /**
- * Replace certain system object ids in inline macros when the object id is known.
- *
- * Currently, ex. Inline v1 filter is the only known input filter that allowed
- * to embed contents of the same node.  Since Drupal's filter system does not
- * provide any context about what system object is actually filtered, we need
- * to update the node id in all inline tags after the node has been saved to the
- * database (so we know its id).
- *
- * This might be enhanced to allow further programmatic replacements.  For example,
- * the same good ol' Inline filter allowed users to reference attached files of
- * a node by numeric pointers that needed to be converted upon node preview/submit.
- *
- * @param &$node
- *   A node object provided by hook_nodeapi().
- * @param $fields
- *   An array of fields in the node object that support Inline filter tags.
- *
- * @see inline_nodeapi()
- */
-function inline_alter_macros(&$node, $fields) {
-  if ($node->inline_altered) {
-    return;
-  }
-
-  // Backup some node properties to undo them later, since we don't know whether
-  // other modules are invoked after hook_nodeapi of Inline.
-  $properties = array(
-    'is_new' => $node->is_new,
-    'revision' => $node->revision,
-    'old_vid' => isset($node->old_vid) ? $node->old_vid : NULL,
-  );
-
-  // @todo When a new node is saved, some modules (like menu) do not update
-  //   node data values accordingly so that another node_save() triggers the
-  //   same addition (here: menu item) twice. This is an ugly workaround.
-  //   Ideally, Inline would just run last, but there is no way we could
-  //   ensure this.
-  $node = node_load($node->nid);
-
-  // Mark this node as processed to prevent recursive loops.
-  $node->inline_altered = TRUE;
-
-  // Temporarily disable revisions for this node.
-  if (!empty($node->revision)) {
-    $node->revision = 0;
-    $node->old_vid = NULL;
-  }
-
-  // Process all node fields.
-  foreach ($fields as $field) {
-    _inline_alter_macros($node, $field);
-  }
-  // Save the altered node (again).
-  node_save($node);
-
-  // Undo our changes.
-  foreach ($properties as $property => $value) {
-    $node->$property = $value;
-  }
-}
-
-/**
- * Helper function for inline_alter_macros().
- */
-function _inline_alter_macros(&$node, $field, $instance = NULL) {
-  // Recursively process multiple value fields.
-  if (is_array($node->$field) && !isset($instance)) {
-    foreach (array_keys($node->$field) as $field_instance) {
-      _inline_alter_macros($node, $field, $field_instance);
-    }
-    return;
-  }
-
-  if (isset($instance)) {
-    $content = &$node->{$field}[$instance]['value'];
-  }
-  else {
-    $content = &$node->{$field};
-  }
-  foreach (inline_get_macros($content) as $macro => $params) {
-    // @todo Use placeholders for #default_value and/or additional properties.
-    //   Or introduce a new $op 'alter' for hook_inline?
-    // @todo Assign the currently processed field name to $params['#field'] to
-    //   allow certain hook_inline implementations to add/ensure this
-    //   information in their macros.
-    if (isset($params['params']['nid']) && $params['params']['nid'] == 0) {
-      $params['params']['nid'] = $node->nid;
-      $content = str_replace($macro, inline_build_macro($params), $content);
-      // @todo Update of reference table perhaps needed.
-    }
-  }
-}
-
-/**
  * Generate an inline macro/tag based on given parameters.
  *
  * @param array $params
@@ -350,6 +273,9 @@ function inline_build_macro($params) {
   $macro_params = array();
 
   $macro_params[] = $params['tag'];
+  if (isset($params['iid'])) {
+    $params['params'] = array_merge(array('iid' => $params['iid']), $params['params']);
+  }
   // @todo Support for #multiple values.
   // @todo Escape |, [, ] chars in values.
   foreach ($params['params'] as $key => $value) {
@@ -363,8 +289,90 @@ function inline_build_macro($params) {
  */
 
 /**
- * Implementation of hook_nodeapi().
+ * @defgroup inline_crud Inline CRUD functions.
+ * @{
+ */
+
+/**
+ * Load an inline macro object by id.
+ */
+function inline_tag_load($iid) {
+  if ($params = db_result(db_query("SELECT parameters FROM {inline} WHERE iid = %d", $iid))) {
+    $params = unserialize($params);
+    $params['iid'] = $iid;
+  }
+  return $params;
+}
+
+/**
+ * Store an inline macro object in the database.
+ */
+function inline_tag_save($params) {
+  $params['params'] = inline_invoke($params, 'presave', $params['params']);
+  $object = new stdClass;
+  $update = array();
+  if (isset($params['iid'])) {
+    $update[] = 'iid';
+    $object->iid = $params['iid'];
+    unset($params['iid']);
+    $object->status = 0;
+  }
+  else {
+    $object->status = 1; // temporary
+  }
+  unset($params['status']);
+  $object->timestamp = time();
+  $object->parameters = serialize($params);
+  drupal_write_record('inline', $object, $update);
+  return $object;
+}
+
+/**
+ * @} End of "defgroup inline_crud".
+ */
+
+/**
+ * @defgroup inline_fapi Inline Form API handling
+ * @{
+ * Inline API solves a few major issues:
+ *
+ * - Provide context for input filters: Inline macros can be used anywhere, but
+ *   hook_filter() does not provide any context about the text being processed.
+ *   An possible inline macro parameter 'nid' may want to reference to the node
+ *   that is currently edited.
+ * - Handling of self-references: A macro parameter 'nid' may want to reference
+ *   to the node that is currently created. During creation, the node has no nid
+ *   yet. $node->nid is first assigned by node_save() after the final form
+ *   submission. But the user already entered inline macros that may have to
+ *   reference to this nid (without the nid, the inline macro could not be
+ *   rendered on other paths than node/%nid). Kind of a chicken-n-egg problem.
+ * - Handling inline macro parameter updates: Existing inline macros in contents
+ *   may have to be updated at some time to accommodate for bugfixes or changing
+ *   APIs of third-party modules.
+ * - Client-side editor integration: Client-side editors may need further
+ *   information to render an inline macro properly, such as a width and height
+ *   for images. They are unable to build something out of a [inline|iid=#] tag.
+ *
+ * Inline API therefore stores all inline macros upon form submission in the
+ * database, optionally flagged with a temporary status. Each time a form is
+ * displayed, all internal '[inline|iid=#]' macros are converted into their
+ * respective macro tags, for example '[upload|file=foo.jpg]'. When the form is
+ * submitted, all macros are converted back into internal inline macros.
+ *
+ * For temporary macros, the implementing module has to update the macro to flag
+ * it as permanent when the corresponding system object (f.e. a node) has been
+ * created. All macros flagged as temporary will be purged during a subsequent
+ * cron run.
+ *
+ * This system allows inline macro implementations to inject certain system
+ * object ids into inline macro parameters when the object id is known. For
+ * example, the Upload module implementation shipped with Inline API (also)
+ * allows the user to refer to a file uploaded to the currently edited node by
+ * using its numeric (human) count (starting from 1), i.e. '[upload|file=2]'
+ * refers to the second uploaded file. The inline macro is automatically
+ * converted into '[upload|file=2nd-attachment.jpg]' upon preview or submit.
  *
+ * @todo Old; anything worth to keep in the following?
  * Inline needs to
  * - insert the node id for new nodes in Inline tags that refer to nid=0, but
  *   must not create a new node revision if revisions are enabled.
@@ -374,16 +382,99 @@ function inline_build_macro($params) {
  * - allow hook_inline() implementations to react on nodeapi operations, f.e. to
  *   replace numeric file references of inline_upload tags (i.e. file=1) with
  *   named file references (i.e. file=foo.jpg) upon node preview and node save.
+ */
+
+/**
+ * Implementation of hook_elements().
+ */
+function inline_elements() {
+  $type['textarea'] = array('#process' => 'inline_process_textarea');
+  return $type;
+}
+
+/**
+ * Converts internal [inline] macros into their original macros.
  *
- * @see DEVELOPER.txt
+ * @see inline_form_submit()
+ * @see inline_nodeapi()
  */
-function inline_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
-  // Only nodes with enabled Inline filter in the format may be processed.
-  $fields = inline_check_node_format($node);
-  if (empty($fields)) {
+function inline_process_textarea(&$form, $edit, &$form_state, $complete_form) {
+  // @todo Only input format-enabled textareas may be processed.
+  /*
+  $parents = $form['#array_parents'];
+  array_pop($parents);
+  foreach ($parents as $key) {
+    $complete_form = $complete_form[$key];
+  }
+  if (isset($complete_form['format'])) {
+    $element = $complete_form['format'];
+    // Make sure we either match a input format selector or input format
+    // guidelines (displayed if user has access to one input format only).
+    if ((isset($element['#type']) && $element['#type'] == 'fieldset') || isset($element['format']['guidelines'])) {
+    }
+  }
+  */
+
+  // Store a reference to this textarea for our submit handler.
+  $form_state['inline_elements'][] = $form['#parents'];
+
+  // @todo Use Inline API for our own tags; only difference is that we probably
+  //   want to keep any macros that cannot be "rendered" for any reason.
+  preg_match_all('/\[inline\|iid=(\d+)\]/', $form['#value'], $matches);
+  foreach ($matches[1] as $key => $iid) {
+    if ($params = inline_tag_load($iid)) {
+      $value = str_replace($matches[0][$key], inline_build_macro($params), $form['#value']);
+      $form['#value'] = $value;
+    }
+  }
+
+  return $form;
+}
+
+/**
+ * Implementation of hook_form_alter().
+ */
+function inline_form_alter(&$form, &$form_state) {
+  $form['#submit'][] = 'inline_form_submit';
+}
+
+/**
+ * Form submit handler to save and convert inline macros into internal [inline] macros.
+ *
+ * This iterates over all form element references stored in
+ * $form_state['inline_elements'] and saves any new inline macros in the
+ * database with a "temporary" status.
+ *
+ * @see inline_process_textarea()
+ * @see inline_nodeapi()
+ */
+function inline_form_submit($form, &$form_state) {
+  // When inline_process_textarea() did not store any references, then there is
+  // nothing to do.
+  if (!isset($form_state['inline_elements'])) {
     return;
   }
+  foreach ($form_state['inline_elements'] as $parents) {
+    // @todo FAPI provides a form_set_value(), but no form_get_value().
+    eval('$content = &$form_state[\'values\'][\'' . implode("']['", $parents) . "'];");
+
+    foreach (inline_get_macros($content) as $macro => $params) {
+      $object = inline_tag_save($params);
+      $GLOBALS['inline'][$object->iid] = $object;
+      $content = str_replace($macro, '[inline|iid=' . $object->iid . ']', $content);
+    }
+  }
+}
 
+/**
+ * Implementation of hook_nodeapi().
+ *
+ * Marks new inline macros as permanent and automatically assigns the parameter
+ * 'nid' for macros that need them (if not referencing another node).
+ *
+ * @todo Does this still work when being moved into inline.node.inc?
+ */
+function inline_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
   switch ($op) {
     case 'update':
       // @todo Some inline tags are referencing to other system objects (f.e.
@@ -391,61 +482,32 @@ function inline_nodeapi(&$node, $op, $a3
       //   filter cache for all contents referencing it.  As long as there is
       //   no generic solution, we completely clear the filter cache.
       cache_clear_all(NULL, 'cache_filter');
-      return;
-
       // Break intentionally left out.
     case 'insert':
       // Update references to 'current' system objects, e.g. the nid after
       // inserting a new node.
-      inline_alter_macros($node, $fields);
-      return;
-
-    case 'prepare':
-    case 'presave':
-      // @todo We should allow Inline implementations to react on node
-      //   (pre)view and submit; some might want to alter macro parameters.
-      //   inline_alter_macros() should be extended to invoke a corresponding
-      //   hook_inline() implementation, f.e.:
-      
-      // inline_alter_macros($node, $op, $fields);
-      
-      //   ...which in turn would
-      //   module_invoke($params['module'], 'inline', 'alter', $params);
-      //   However, we do not want to save a node in $op 'prepare' or 'submit'...
+      // @todo Assign the currently processed field name to $params['#field'] to
+      //   allow certain hook_inline implementations to add/ensure this
+      //   information in their macros.
+      // @todo Introduce a reference table?
+      if (empty($GLOBALS['inline'])) {
+        return;
+      }
+      foreach ($GLOBALS['inline'] as $iid => $object) {
+        $params = unserialize($object->parameters);
+        $params['iid'] = $iid;
+        if (!isset($params['params']['nid'])) {
+          $params['params']['nid'] = $node->nid; // vid here?
+        }
+        inline_tag_save($params);
+      }
       return;
   }
 }
 
 /**
- * Retrieves the field names of a node that are Inline enabled.
+ * @} End of "defgroup inline_fapi".
  */
-function inline_check_node_format(&$node) {
-  $fields = array();
-
-  // Check format of node body.
-  if (!empty($node->body)) {
-    foreach (filter_list_format($node->format) as $filter) {
-      if ($filter->module == 'inline') {
-        $fields[] = 'body';
-        $fields[] = 'teaser';
-        break;
-      }
-    }
-  }
-  // Check format of CCK fields.
-  if (function_exists('content_types')) {
-    $type = content_types($node->type);
-    if (!empty($type['fields'])) {
-      foreach ($type['fields'] as $field) {
-        // Skip fields in plain-text format.
-        if (!empty($field['text_processing'])) {
-          $fields[] = $field['field_name'];
-        }
-      }
-    }
-  }
-  return $fields;
-}
 
 /**
  * @defgroup inline_help Inline help
Index: inline.node.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/inline/inline.node.inc,v
retrieving revision 1.1
diff -u -p -r1.1 inline.node.inc
--- inline.node.inc	8 Aug 2009 06:32:40 -0000	1.1
+++ inline.node.inc	12 Aug 2009 02:39:27 -0000
@@ -32,6 +32,9 @@ function inline_node_inline($op, $params
       // Custom validation of user supplied values.
       return TRUE;
 
+    case 'presave':
+      return $params;
+
     case 'prepare':
       // Load a node object if valid nid is given.
       if (!empty($params['nid'])) {
Index: inline_upload.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/inline/inline_upload.module,v
retrieving revision 1.7
diff -u -p -r1.7 inline_upload.module
--- inline_upload.module	11 Aug 2009 19:06:42 -0000	1.7
+++ inline_upload.module	12 Aug 2009 01:54:02 -0000
@@ -50,6 +50,45 @@ function inline_upload_inline($op, $para
       );
       return $args;
 
+    case 'presave':
+      // Mark this tag as permanent, if we already have a node id.
+      // @todo Reverse the temporary/permanent logic; this is only used by a
+      //   minority of inline macros.
+      if (!empty($params['nid'])) {
+        // @todo Doesn't work yet, we need a proper inline macro object.
+        // $params['status'] = 0;
+      }
+      // Convert a numeric file reference to a named one.
+      if (isset($params['file']) && is_int($params['file'])) {
+        // Account for new uploaded files in node preview.
+        // @see upload_js()
+        // @todo This basically works how it should. However, altered weights are
+        //   applied when doing a subsequent preview only. So we may have to
+        //   additionally derive the attachment weights from the form in $_POST.
+        $cached_form_state = array();
+        if (isset($_POST['form_build_id']) && ($cached_form = form_get_cache($_POST['form_build_id'], $cached_form_state)) && isset($cached_form['#node']) && isset($cached_form['attachments']['wrapper']['files'])) {
+          $files = array();
+          foreach (element_children($cached_form['attachments']['wrapper']['files']) as $fid) {
+            $element = $cached_form['attachments']['wrapper']['files'][$fid];
+            if (empty($element['remove']['#default_value'])) {
+              $file = new stdClass;
+              foreach ($element as $key => $item) {
+                $file->{$key} = (isset($item['#value']) ? $item['#value'] : $item['#default_value']);
+              }
+              $files[$fid] = $file;
+            }
+          }
+        }
+        if (isset($files)) {
+          // Humans start to count from 1.
+          $filekeys = array_keys($files);
+          if (isset($filekeys[$params['file'] - 1])) {
+            $params['file'] = $files[$filekeys[$params['file'] - 1]]->filename;
+          }
+        }
+      }
+      return $params;
+
     case 'validate':
       // Custom validation of user supplied values.
       return TRUE;
@@ -69,33 +108,6 @@ function inline_upload_inline($op, $para
           $params['#node'] = $node;
         }
       }
-      // Add new uploaded files to $node->files in node preview.
-      // @see upload_js()
-      // @todo This basically works how it should. However, altered weights are
-      //   applied when doing a subsequent preview only. So we may have to
-      //   additionally derive the attachment weights from the form in $_POST.
-      $cached_form_state = array();
-      if (isset($_POST['form_build_id']) && ($cached_form = form_get_cache($_POST['form_build_id'], $cached_form_state)) && isset($cached_form['#node']) && isset($cached_form['attachments']['wrapper']['files'])) {
-        $params['#node']->files = array();
-        foreach (element_children($cached_form['attachments']['wrapper']['files']) as $fid) {
-          $element = $cached_form['attachments']['wrapper']['files'][$fid];
-          if (empty($element['remove']['#default_value'])) {
-            $file = new stdClass;
-            foreach ($element as $key => $item) {
-              $file->{$key} = (isset($item['#value']) ? $item['#value'] : $item['#default_value']);
-            }
-            $params['#node']->files[$fid] = $file;
-          }
-        }
-      }
-      // Convert a numeric file reference to a named one.
-      if (is_int($params['file']) && isset($params['#node']->files)) {
-        $files = array_keys($params['#node']->files);
-        // Humans count from 1.
-        if (isset($files[$params['file'] - 1])) {
-          $params['file'] = $params['#node']->files[$files[$params['file'] - 1]]->filename;
-        }
-      }
       return $params;
 
     case 'render':
@@ -305,10 +317,10 @@ function _inline_upload_decide_img_tag($
       list($maxwidth, $maxheight) = explode(',', variable_get('inline_upload_img_dim', '150,150'));
 
       if (!empty($file->in_preview)) {
-        list($width, $height) = getimagesize($file->real_path);
+        list($width, $height) = @getimagesize($file->real_path);
       }
       else {
-        list($width, $height) = getimagesize($file->filepath);
+        list($width, $height) = @getimagesize($file->filepath);
       }
       if (($width && $height) && ($width <= $maxwidth && $height <= $maxheight)) {
         return TRUE;
@@ -347,8 +359,10 @@ function theme_inline_upload_img($file, 
   // Prepare link text with inline title, file description or filename.
   $title = (!empty($file->title) ? $file->title : (!empty($file->description) ? $file->description : $file->filename));
   $inline_upload_preset = ($field == 'teaser' ? 'inline_upload_teaser_preset' : 'inline_upload_full_preset');
-  
+  $url = file_create_url($file->filepath);
+
   if (module_exists('imagecache') && variable_get($inline_upload_preset, '') != '') {
+    // ImageCache implements its own private files handling.
     $output = theme('imagecache',
       variable_get($inline_upload_preset, ''),
       $file->filepath,
@@ -358,12 +372,15 @@ function theme_inline_upload_img($file, 
     );
   }
   else {
+    // Private files support: theme_image() requires us to set $getsize to FALSE
+    // and prepare image attributes on our own.
+    $info = image_get_info($file->filepath);
     $output = theme('image',
-      $file->filepath,
+      $url,
       $title,
       $title,
-      array('class' => 'inline'),
-      !isset($file->in_preview)
+      array('class' => 'inline', 'width' => $info['width'], 'height' => $info['height']),
+      FALSE
     );
   }
   
@@ -372,7 +389,7 @@ function theme_inline_upload_img($file, 
       'class' => 'inline-image-link',
       'title' => t('View: @file', array('@file' => $title)),
     );
-    $output = l($output, $file->filepath, array('attributes' => $attributes, 'html' => TRUE));
+    $output = l($output, $url, array('attributes' => $attributes, 'html' => TRUE));
   }
   
   return $output;
