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 @@ + 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;