Index: image_attach.css =================================================================== RCS file: /cvs/drupal/contributions/modules/image/contrib/image_attach/image_attach.css,v retrieving revision 1.1 diff -u -r1.1 image_attach.css --- image_attach.css 7 May 2006 00:54:00 -0000 1.1 +++ image_attach.css 2 Oct 2006 16:25:00 -0000 @@ -9,6 +9,46 @@ .image-attach-preview { margin-bottom: 1em; } +.image-attach-preview a { + display: block; +} .node { clear: both; +} +#attach_image-wrapper { + padding: 5px; +} +#attach_image-wrapper .image-item { + float: left; + width: 170px; + height: 130px; + padding: 5px; + margin: 5px; + border: 1px solid #ccc; + position: relative; +} +#attach_image-wrapper .image-item .picture { + height: 106px; + text-align: center; + vertical-align: middle; +} +#attach_image-wrapper .image-item .form-radio { + position: absolute; + top: 0; + left: 0; +} +#attach_image-wrapper .image-item .form-textfield { + position: absolute; + left: 5px; + bottom: 5px; + width: 165px; + height: 14px; +} +#attach_image-hide { + float: left; + width: 100%; +} +#attach_image-wrapper #uploadprogress { + float: left; + clear: both; } \ No newline at end of file Index: image_attach.install =================================================================== RCS file: /cvs/drupal/contributions/modules/image/contrib/image_attach/image_attach.install,v retrieving revision 1.1 diff -u -r1.1 image_attach.install --- image_attach.install 3 May 2006 20:04:25 -0000 1.1 +++ image_attach.install 26 Oct 2006 15:24:00 -0000 @@ -11,14 +11,65 @@ switch ($GLOBALS['db_type']) { case 'mysqli': case 'mysql': - db_query(" -CREATE TABLE {image_attach} ( - nid int(10) unsigned NOT NULL default '0', - iid int(10) unsigned NOT NULL default '0', - PRIMARY KEY (nid) -) TYPE=MyISAM -/*!40100 DEFAULT CHARACTER SET utf8 */;"); + // modified the original database to accommodate multiple images per node. + // drop the old table if it happens to be present (just incase). + db_query("CREATE TABLE {image_attach} ( + nid int(10) unsigned NOT NULL default '0', + vid int(10) unsigned NOT NULL default '0', + iid int(10) unsigned NOT NULL default '0', + PRIMARY KEY (vid, iid) + ) TYPE=MyISAM; + "); break; } +} + +function image_attach_update_1() { + switch ($GLOBALS['db_type']) { + case 'mysql': + case 'mysqli': + // Save data in a temp table for later reconstruction, and empty the table + $ret[] = update_sql( " + CREATE TEMPORARY TABLE {tmp_image_attach} ( + nid int(10) unsigned, + iid int(10) unsigned + );" ); + $ret[] = update_sql( " + INSERT INTO {tmp_image_attach} + SELECT nid, iid FROM {image_attach}; + " ); + $ret[] = update_sql( " + DELETE FROM {image_attach}; + " ); + + // add `vid` column to the table, recreate primary key and add a second index + $ret[] = update_sql( " + ALTER TABLE {image_attach} + ADD COLUMN `vid` int(10) unsigned NOT NULL default '0' AFTER `nid`, + DROP PRIMARY KEY, + ADD PRIMARY KEY (vid, iid), + ADD INDEX `nid_iid` (nid, iid); + " ); + + // reconstruction of the image_attach table. +/* + // OPTION 1: Attach images to the current node revision only + $ret[] = update_sql( " + INSERT INTO {image_attach} (nid, vid, iid) + SELECT tmp.nid, n.vid, tmp.iid + FROM {tmp_image_attach} tmp INNER JOIN {node} n ON n.nid = tmp.nid; + " ); +*/ + // OPTION 2: Attach images to all node revisions + $ret[] = update_sql( " + INSERT INTO {image_attach} (nid, vid, iid) + SELECT tmp.nid, r.vid, tmp.iid + FROM {tmp_image_attach} tmp INNER JOIN {node_revisions} r ON r.nid = tmp.nid; + " ); + + break; + } + + return $ret; } \ No newline at end of file Index: image_attach.module =================================================================== RCS file: /cvs/drupal/contributions/modules/image/contrib/image_attach/image_attach.module,v retrieving revision 1.7 diff -u -r1.7 image_attach.module --- image_attach.module 20 Nov 2006 04:54:28 -0000 1.7 +++ image_attach.module 20 Nov 2006 18:28:50 -0000 @@ -1,10 +1,26 @@ 'image_attach', - 'title' => t('Image Attachment View'), - 'callback' => 'image_attach_view_image', - 'access' => user_access('access content'), - 'type' => MENU_CALLBACK); - $items[] = array('path' => 'admin/settings/image_attach', - 'title' => t('Image attach'), - 'description' => t('Enable image attach for content'), - 'callback' => 'drupal_get_form', - 'callback arguments' => array('image_attach_admin_settings'), - 'access' => user_access('administer site configuration'), - 'type' => MENU_NORMAL_ITEM); + $items[] = array( + 'path' => 'upload/image_js', + 'callback' => 'image_attach_js', + 'access' => user_access('create images'), + 'type' => MENU_CALLBACK + ); } + return $items; } -function image_attach_admin_settings() { - $form = array(); - - $form['image_attach_existing'] = array( - '#type' => 'radios', - '#title' => t('Attach existing images'), - '#default_value' => variable_get('image_attach_existing', 1), - '#options' => array(0 => 'Disabled', 1 => 'Enabled'), - '#description' => t('When enabled, will allow existing image nodes to be attached instead of uploading new images.') - ); - - return system_settings_form($form); -} - /** * implementation of hook_form_alter() */ function image_attach_form_alter($form_id, &$form) { - if ($form_id == 'node_type_form' && isset($form['identity']['type'])) { - if(function_exists('_image_check_settings')){ - _image_check_settings(); - $form['workflow']['image_attach'] = array( - '#type' => 'radios', - '#title' => t('Attach Images'), - '#default_value' => variable_get('image_attach_'. $form['identity']['type']['default_value'], 1), - '#options' => array(0 => t('Disabled'), 1 => t('Enabled')), - '#description' => t('Should this node allows users to upload an image?'), - ); - - } + if (!isset($form['type']) || $form['type'] == 'image') { + return; } - // Make a copy of the type to shorten up the code - if (isset($form['type'])) { - $node = $form['#node']; - // if enabled adjust the form - if ($form_id == $form['type']['#value'].'_node_form' && variable_get('image_attach_'.$node->type, 0)) { - _image_check_settings(); - $existing = variable_get('image_attach_existing', 1); - $value = ($node->new_image) ? '#value' : '#default_value'; - $form['#attributes'] = array("enctype" => "multipart/form-data"); - - $form['image_attach'] = array( - '#type' => 'fieldset', - '#title' => t('Attached Images'), - '#collapsible' => TRUE, - '#collapsed' => !$node->iid + $type = $form['type']['#value']; + + $upload = variable_get('image_attach_'. $type .'_upload', 0); + $existing = variable_get('image_attach_'. $type .'_existing', 0); + $enabled = $existing || $upload; + + switch ($form_id) { + + // checkbox in the node's content type configuration page. + case $type .'_node_settings': + if (function_exists('_image_check_settings')){ + _image_check_settings(); + $form['workflow']['image_attach_'. $type .'_upload'] = array( + '#type' => 'radios', + '#title' => t('Attach uploaded images'), + '#default_value' => $upload, + '#options' => array(0 => t('Disabled'), 1 => t('Enabled')), + '#description' => t('Should this node allows users to attach uploaded images?'), ); - if ($node->iid) { - $image = node_load($node->iid); - $form['image_attach']['image_thumbnail'] = array( - '#type' => 'item', - '#title' => t('Thumbnail'), - '#value' => image_display($image, 'thumbnail') - ); + $form['workflow']['image_attach_'. $type .'_existing'] = array( + '#type' => 'radios', + '#title' => t('Attach existing images'), + '#default_value' => $existing, + '#options' => array(0 => t('Disabled'), 1 => t('Enabled')), + '#description' => t('Should this node allows users to attach existing images?'), + ); + } else { + drupal_set_message(t('The image module is not installed. The image_attach module will not function without it.'), 'error'); } - if ($existing && user_access('access content')) { - $form['image_attach']['iid'] = array( - '#type' => 'select', - '#title' => t('Existing Image'), - '#options' => _image_attach_get_image_nodes(), - $value => $node->iid, - '#description' => t('Choose an image already existing on the server if you do not upload a new one.') - ); - $form['image_attach'][] = array( - '#type' => 'item', - '#value' => t('-or-'), - '#attributes' => array('class' => 'either-choice') - ); - } - else { - $form['image_attach']['iid'] = array( - '#type' => 'hidden', - '#value' => $node->iid - ); + break; + + // if enabled adjust the form + case $type .'_node_form': + if ($enabled && function_exists('_image_check_settings')) { + _image_check_settings(); + $node = $form['#node']; + + drupal_add_js('misc/progress.js'); + drupal_add_js('misc/upload.js'); + + // Attachments fieldset + $form['image_attach'] = array( + '#type' => 'fieldset', + '#title' => t('Attached Images'), + '#collapsible' => TRUE, + '#collapsed' => empty($node->images), + '#description' => t('Changes made to the attachments are not permanent until you save this post. The first "listed" image will be included in RSS feeds.'), + '#prefix' => '
', + '#suffix' => '
', + '#weight' => 30, + ); + + // Wrapper for fieldset contents (used by upload JS). + $form['image_attach']['wrapper'] = array( + '#prefix' => '
', + '#suffix' => '
', + ); + $form['image_attach']['wrapper'] += _image_attach_form($node, $upload, $existing); + $form['#attributes']['enctype'] = 'multipart/form-data'; } - $form['image_attach']['image'] = array( - '#type' => 'file', - '#title' => t('Upload Image') + break; + } +} + +function _image_attach_form($node, $upload, $existing) { + $form['#theme'] = 'image_attach_form_new'; + + if (count($node->images)) { + $form['images']['#theme'] = 'image_attach_form_current'; + $form['images']['#tree'] = TRUE; + foreach ($node->images as $iid => $image) { + $description = file_create_url($image->images['_original']); + $description = "". check_plain($description) .""; + $form['images'][$iid]['title'] = array('#type' => 'textfield', '#default_value' => (strlen($image->title)) ? $image->title : $image->images['_original'], '#maxlength' => 256, '#description' => $description ); + if ($group = image_display($image, 'thumbnail')) + $form['images'][$iid]['thumbnail'] = array('#type' => 'markup', '#value' => $group); + + if (file_exists($image->images['_original'])) + $filesize = filesize($image->images['_original']); + else + $filesize = filesize(file_directory_path() . '/' . $image->images['_original']); + + $form['images'][$iid]['size'] = array('#type' => 'markup', '#value' => format_size($filesize)); + $form['images'][$iid]['remove'] = array('#type' => 'checkbox', '#default_value' => $image->remove); + $form['images'][$iid]['filepath'] = array('#type' => 'value', '#value' => $image->images['_original']); + $form['images'][$iid]['filesize'] = array('#type' => 'value', '#value' => $filesize); + $form['images'][$iid]['nid'] = array('#type' => 'value', '#value' => $image->nid); + } + } + + $upload = user_access('create images') && $upload; + $existing = user_access('access content') && $existing; + + + if ($upload || $existing) { + // This div is hidden when the user uploads through JS. + $form['new'] = array( + '#prefix' => '
', + '#suffix' => '
', + ); + $form['new']['image_attach_node_type'] = array( + '#type' => 'hidden', + '#value' => $node->type + ); + + if ($existing) { + // The existing image chooser + $form['new']['iid'] = array( + '#type' => 'select', + '#title' => t('Attach an existing Image'), + '#options' => _image_attach_get_image_nodes(), + '#description' => t('Choose an image already existing on the server and attach it.') ); - $form['image_attach']['image_title'] = array( + } + + if ($existing && $upload) { + $form['new']['or'] = array( + '#type' => 'item', + '#value' => t('-or-'), + '#attributes' => array('class' => 'either-choice') + ); + } + + if ($upload) { + // The upload file name field + $form['new']['image'] = array( + '#type' => 'file', + '#title' => t('Upload and attach new image'), + '#size' => 40, + '#description' => t('Upload a new image and attach it.') + ); + $form['new']['image_title'] = array( '#type' => 'textfield', '#title' => t('Image title'), $value => '', '#description' => t('The title the image will be shown with.') ); } + + // The submit button + $form['new']['attach_image'] = array( + '#type' => 'button', + '#value' => t('Attach'), + '#name'=> 'attach_image', + '#attributes' => array('id' => 'attach_image') + ); + // The class triggers the js upload behaviour. + $form['attach_image'] = array( + '#type' => 'hidden', + '#value' => url('upload/image_js', NULL, NULL, TRUE), + '#attributes' => array('class' => 'upload') + ); + // Needed for JS + $form['current']['vid'] = array( + '#type' => 'hidden', + '#value' => $node->vid + ); } + + return $form; } /** - * Implementation of hook_nodeapi(). - */ -function image_attach_nodeapi(&$node, $op, $teaser, $page) { - if(variable_get('image_attach_'. $node->type, 0) == 0){ - return; +* Implementation of hook_nodeapi(). +*/ +function image_attach_nodeapi(&$node, $op, $teaser = FALSE, $page = FALSE) { + $upload = variable_get('image_attach_'. $node->type .'_upload', 0); + $existing = variable_get('image_attach_'. $node->type .'_existing', 0); + + if ($upload == 0 && $existing == 0) { + return; } switch ($op) { + case 'validate': + _image_attach_validate($node); + break; case 'prepare': - $image->title = $_POST['edit']['image_title']; - $image->uid = $node->uid; - $image->name = $node->name; - $image->created = $node->created; - $image->type = 'image'; - image_prepare($image, 'image'); - if ($image->images) { - node_validate($image); - if (!form_get_errors()) { - $image = node_submit($image); - node_save($image); - $node->iid = $image->nid; - $node->new_image = TRUE; - } - } - elseif (variable_get('image_attach_existing', 1) && $_POST['edit']['iid']) { - $node->iid = $_POST['edit']['iid']; - } + _image_attach_prepare($node, $upload, $existing); break; case 'insert': case 'update': - if ($node->iid) { - db_query("DELETE FROM {image_attach} WHERE nid=%d", $node->nid); - db_query("INSERT INTO {image_attach} (nid, iid) VALUES (%d, %d)", $node->nid, $node->iid); - } + _image_attach_save($node); break; case 'delete': - db_query("DELETE FROM {image_attach} WHERE nid=%d", $node->nid); + _image_attach_delete($node); + break; + case 'delete revision': + _image_attach_delete_revision($node); break; case 'load': - $iid = db_result(db_query("SELECT iid FROM {image_attach} WHERE nid=%d", $node->nid)); - return array('iid' => $iid); + return array('images' => _image_attach_load($node)); + break; // Pass the body and teaser objects to the theme again to add the images case 'view': - if($node->iid && function_exists('image_display')){ - if ($teaser) { - $node->content['image_attach'] = array( - '#value' => theme('image_attach_teaser', $node) - ); + if (count($node->images) && function_exists('image_display')){ + if ($teaser){ + $node->teaser = theme('image_attach_teaser', $node) . $node->teaser; + } else { + $node->body = theme('image_attach_body', $node) . $node->body; + } + } + break; + + } + +} + +function _image_attach_validate(&$node) { +} + +function _image_attach_prepare(&$node, $upload, $existing) { + global $user; + + if(count($_POST) == 0) { + if (is_array($_SESSION['image_attach_uploaded']) && count($_SESSION['image_attach_uploaded'])) { + foreach($_SESSION['image_attach_uploaded'] as $iid => $image) { + node_delete($iid); + } + unset($_SESSION['image_attach_uploaded']); + unset($_SESSION['image_attach_existing']); + } + } + + unset($_SESSION['image_attach_current_upload']); + if ($upload && ($file = file_check_upload('image'))) { + $image->uid = $user->uid; + $image->name = $user->name; + $image->type = 'image'; + + node_object_prepare($image, 'image'); + if ($image->images) { + $image->title = trim($node->image_title) ? $node->image_title : $file->filename; + node_validate($image); + if (!form_get_errors()) { + $image = node_submit($image); + node_save($image); + //$image->new_image = TRUE; + $_SESSION['image_attach_uploaded'][$image->nid] = $image; + $_SESSION['image_attach_current_upload'] = $image->nid; + } + } + } + + // Attach image previews to node object. + if (is_array($_SESSION['image_attach_uploaded']) && count($_SESSION['image_attach_uploaded'])) { + foreach($_SESSION['image_attach_uploaded'] as $iid => $image) { + $node->images[$iid] = $image; + } + } + + if ($existing && ($iid = $_POST['edit']['iid'])) { + $image = node_load($iid); + + // Check if we really loaded an image + if ($image->nid && $image->type == 'image') { + $_SESSION['image_attach_existing'][$image->nid] = $image; + } else { + drupal_set_message(t('Attached image with node id %iid not found', array('%iid' => $iid))); + } + } + + // Attach image previews to node object. + if (is_array($_SESSION['image_attach_existing']) && count($_SESSION['image_attach_existing'])) { + foreach($_SESSION['image_attach_existing'] as $iid => $image) { + $node->images[$iid] = $image; + } + } + +} + +function _image_attach_load($node) { + $images = array(); + + $result = db_query("SELECT n.* FROM {image_attach} a, {node} n WHERE a.iid = n.nid AND a.vid = %d AND n.type = 'image'", $node->vid); + while ($row = db_fetch_object($result)) { + if ($image = node_load($row->nid)) { + $images[$row->nid] = $image; + } + } + + return $images; +} + +function _image_attach_save($node) { + if (is_array($node->images)) { + foreach ($node->images as $iid => $image) { + $image = (object)$image; + + if ($image->remove) { + db_query("DELETE FROM {image_attach} WHERE vid = %d AND iid = %d", $node->vid, $image->nid); + // Check if image is uses in other releases, before deleting it + if (!db_num_rows(db_query('SELECT * FROM {image_attach} WHERE iid = %d', $image->nid))) { + node_delete($image->nid); } + } + else { + if (!db_num_rows(db_query('SELECT * FROM {image_attach} WHERE vid = %d AND iid = %d', $node->vid, $image->nid))) { + db_query("INSERT INTO {image_attach} (nid, vid, iid) VALUES (%d, %d, %d)", $node->nid, $node->vid, $image->nid); + } else { - $node->content['image_attach'] = array( - '#value' => theme('image_attach_body', $node) - ); + db_query("UPDATE {node} SET title = '%s' WHERE nid = %d", $image->title, $image->nid); } - } + } + + // remove it from previews, as it is processed. + unset($_SESSION['image_attach_uploaded'][$image->nid]); + unset($_SESSION['image_attach_existing'][$image->nid]); + } + } +} + +function _image_attach_delete($node) { + $images = array(); + $result = db_query('SELECT DISTINCT iid FROM {image_attach} WHERE nid = %d', $node->nid); + while ($row = db_fetch_object($result)) { + $images[] = $row->iid; + } + + foreach ($images as $iid) { + db_query("DELETE FROM {image_attach} WHERE nid = %d AND iid = %d", $node->nid, $iid); + node_delete($iid); + } +} + + +function _image_attach_delete_revision($node) { + $images = array(); + $result = db_query('SELECT iid FROM {image_attach} WHERE vid = %d', $node->vid); + while ($row = db_fetch_object($result)) { + $images[] = $row->iid; + } + + foreach ($images as $iid) { + db_query("DELETE FROM {image_attach} WHERE vid = %d AND iid = %d", $node->vid, $iid); + + // Check if image is still uses in other releases, before deleting it + $result = db_query('SELECT vid FROM {image_attach} WHERE nid = %d AND iid = %d', $node->nid, $iid); + if (!db_num_rows($result)) { + node_delete($iid); + } } } @@ -205,6 +419,7 @@ return $rows; } + /** * Theme the teaser. * @@ -212,15 +427,16 @@ * If you have additional image sizes you defined in image.module, you can use them by theming this function as well. */ function theme_image_attach_teaser($node){ - drupal_add_css(drupal_get_path('module', 'image_attach') .'/image_attach.css'); - - $image = node_load($node->iid); + theme_add_style(drupal_get_path('module', 'image_attach') .'/image_attach.css'); + $image = array_shift($node->images); + $info = image_get_info(file_create_path($image->images['thumbnail'])); $output = ''; $output .= '
'; + $output .= '
'; $output .= l(image_display($image, 'thumbnail'), "node/$node->nid", array(), NULL, NULL, FALSE, TRUE); - $output .= '
'."\n"; + $output .= '
'."\n"; return $output; } @@ -228,15 +444,88 @@ * Theme the body */ function theme_image_attach_body($node){ - drupal_add_css(drupal_get_path('module', 'image_attach') .'/image_attach.css'); + theme_add_style(drupal_get_path('module', 'image_attach') .'/image_attach.css'); + + $max_width = 0; + $images = array(); + foreach ($node->images as $image) { + $info = image_get_info(file_create_path($image->images['thumbnail'])); + $max_width = max($max_width, $info['width']); + + $images[] = '
'.l(image_display($image, 'thumbnail'), "node/$image->nid", array('title' => t('Click to see larger image')), NULL, NULL, FALSE, TRUE).'
'; + } + $output = '
' . implode("\n", $images) . "
\n"; + return $output; +} - $image = node_load($node->iid); - - $info = image_get_info(file_create_path($image->images['thumbnail'])); - $output = ''; - $output .= '
'; - $output .= l(image_display($image, 'thumbnail'), "node/$node->nid", array(), NULL, NULL, FALSE, TRUE); - $output .= '
'."\n"; +function theme_image_attach_form_current(&$form) { + theme_add_style(drupal_get_path('module', 'image_attach') .'/image_attach.css'); + $output = ''; + foreach (element_children($form) as $key) { + $form[$key]['remove']['#printed'] = $form[$key]['title']['#printed'] = TRUE; + + $output .= '
'; + + if (strpos($form[$key]['remove']['#attributes']['class'], 'form-radio') === FALSE) + $form[$key]['remove']['#attributes']['class'] .= ' form-radio'; + $output .= ''; + + $output .= '
'. form_render($form[$key]['thumbnail']) ."
\n"; + + if (strpos($form[$key]['title']['#attributes']['class'], 'form-textfield') === FALSE) + $form[$key]['title']['#attributes']['class'] .= ' form-textfield'; + $output .= ''; + $output .= "
\n"; + } + return $output; } +function theme_image_attach_form_new($form) { + theme_add_style(drupal_get_path('module', 'image_attach') .'/image_attach.css'); + $output = form_render($form); + return $output; +} + +function image_attach_js() { + // We only do the upload.module part of the node validation process. + $node = (object)$_POST['edit']; + $node->type = $node->image_attach_node_type; + + $upload = variable_get('image_attach_'. $node->type .'_upload', 0); + $existing = variable_get('image_attach_'. $node->type .'_existing', 0); + $enabled = $existing || $upload; + + if ($enabled) { + // Load existing node files. + $node->images = _image_attach_load($node); + + // Handle new uploads, and merge tmp files into node-files. + _image_attach_prepare($node, $upload && user_access('create images'), $existing && user_access('access content')); + _image_attach_validate($node); + + // Preserve modifications + if (is_array($_POST['edit']['images'])) { + foreach ($_POST['edit']['images'] as $iid => $image) { + if (array_key_exists($iid, $node->images)) { + $node->images[$iid]->remove = $image['remove']; + $node->images[$iid]->title = $image['title']; + } + } + } + + $form = _image_attach_form($node, $upload, $existing); + } + + foreach (module_implements('form_alter') as $module) { + $function = $module .'_form_alter'; + $function('image_attach_js', $form); + } + + $form = form_builder('image_attach_js', $form); + $output = theme('status_messages') . form_render($form); + + // We send the updated file attachments form. + print drupal_to_js(array('status' => TRUE, 'data' => $output)); + exit; +} \ No newline at end of file Index: TODO.txt =================================================================== RCS file: /cvs/drupal/contributions/modules/image/contrib/image_attach/TODO.txt,v retrieving revision 1.1 diff -u -r1.1 TODO.txt --- TODO.txt 3 May 2006 20:04:25 -0000 1.1 +++ TODO.txt 21 Nov 2006 11:34:07 -0000 @@ -1,3 +1,3 @@ -* support multiple images +* support multiple images - done by Marty Zalega (evil.marty-#AT#-gmail.com) * allow selection of derivative to use (though this can be overriden by the theme) -* allow for using already uploaded images? +* allow for using already uploaded images - done by Danny van der Weide (dvdweide at dvdweide dot nl)