<?php
// $Id: image_attach.module,v 1.75 2010/01/16 11:34:42 joachim Exp $

/**
 * @file image_attach.module
 */

define('IMAGE_ATTACH_HIDDEN', 'hidden');

/**
 * Implementation of hook_help().
 */
function image_attach_help($path, $arg) {
  switch ($path) {
  }
}

/**
 * Implementation of hook_menu()
 */
function image_attach_menu() {
  $items = array();

  $items['image_attach'] = array(
    'title' => 'Image attachment view',
    'page callback' => 'image_attach_view_image',
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
  );
  $items['admin/settings/image/image_attach'] = array(
    'title' => 'Image attach',
    'description' => 'Enable image attach for content.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('image_attach_admin_settings'),
    'access arguments' => array('administer site configuration'),
    'type' => MENU_LOCAL_TASK,
  );
  return $items;
}

/**
 * Implementation of hook_perm()
 */
function image_attach_perm() {
  return array('attach images');
}


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 => t('Disabled'),
      1 => t('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_block().
 */
function image_attach_block($op = 'list', $delta = 0, $edit = array()) {
  switch ($op) {
    case 'list':
      $blocks[0] = array(
        'info'       => t('Attached images'),
        'weight'     => 0,
        'visibility' => 1,
        'pages'      => 'node/*',
      );
      return $blocks;

    case 'view':
      if ($delta == 0) {
        if (arg(0) == 'node' && is_numeric(arg(1))) {
          $node = node_load(arg(1));
          if (isset($node->iids)) {
            $output['subject'] = t('Attached images');
            foreach ($node->iids as $iid) {
              $image = node_load($iid);
              if (node_access('view', $image)) {
                $image_nodes[] = $image;
              }
            }
            $output['content'] = theme('image_attach_block_attached', $nid, $image_nodes);
            return $output;
          }
        }
      }
      break;

    case 'configure':
      if ($delta == 0) {
        $image_sizes = array();
        foreach (image_get_sizes() as $key => $size) {
          $image_sizes[$key] = $size['label'];
        }
        $form['image_attach_block_0_size'] = array(
          '#type' => 'select',
          '#title' => t('Image size'),
          '#default_value' => variable_get('image_attach_block_0_size', IMAGE_THUMBNAIL),
          '#options' => $image_sizes,
          '#description' => t('This determines the size of the image that appears in the block.'),
        );
        return $form;
      }
      break;

    case 'save':
      if ($delta == 0) {
        variable_set('image_attach_block_0_size', $edit['image_attach_block_0_size']);
      }
      break;
  }
}

/**
 * Implementation of hook_form_FORM_ID_alter().
 *
 * Add settings to the content type settings form.
 */
function image_attach_form_node_type_form_alter(&$form, $form_state) {
  if($form['#node_type']->type != 'image') {
    _image_check_settings();

    $image_sizes = array(IMAGE_ATTACH_HIDDEN => t('<Hidden>'));
    foreach (image_get_sizes() as $key => $size) {
      $image_sizes[$key] = $size['label'];
    }
    
    $form['image_attach'] = array(
      '#type' => 'fieldset',
      '#title' => t('Image Attach settings'),
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
    );
    $form['image_attach']['image_attach'] = array(
      '#type' => 'radios',
      '#title' => t('Attach images'),
      '#default_value' => variable_get('image_attach_'. $form['#node_type']->type, 0),
      '#options' => array(0 => t('Disabled'), 1 => t('Enabled')),
      '#description' => t('Should this node allows users to upload an image?'),
    );
    $form['image_attach']['image_attach_maximum'] = array(
      '#type' => 'select',
      '#title' => t('Maximum number of images'),
      '#default_value' => variable_get('image_attach_maximum_' . $form['#node_type']->type, 0),
      '#options' => array(0 => t('Unlimited')) + drupal_map_assoc(range(1, 10)),
      '#description' => t('The maximum number of images that may be attached to nodes of this type.'),
    );
    $form['image_attach']['image_attach_size_teaser'] = array(
      '#type' => 'select',
      '#title' => t('Teaser image size'),
      '#default_value' => variable_get('image_attach_size_teaser_'. $form['#node_type']->type, IMAGE_THUMBNAIL),
      '#options' => $image_sizes,
      '#description' => t("This determines the size of the image that appears when the node is displayed as a teaser. 'Hidden' will not show the image.")
    );
    // Hide the weight if CCK is doing the weight for us.
    if (!module_exists('content')) {
      $form['image_attach']['image_attach_weight_teaser'] = array(
        '#type' => 'weight',
        '#title' => t('Teaser image weight'),
        '#default_value' => variable_get('image_attach_weight_teaser_'. $form['#node_type']->type, 0),
        '#description' => t('This value determines the order of the image when displayed in the teaser.'),
      );
    }
    $form['image_attach']['image_attach_size_body'] = array(
      '#type' => 'select',
      '#title' => t('Full node image size'),
      '#default_value' => variable_get('image_attach_size_body_'. $form['#node_type']->type, IMAGE_THUMBNAIL),
      '#options' => $image_sizes,
      '#description' => t("This determines the size of the image that appears when the full node is displayed. 'Hidden' will not show the image.")
    );
    if (!module_exists('content')) {
      $form['image_attach']['image_attach_weight_body'] = array(
        '#type' => 'weight',
        '#title' => t('Full node image weight'),
        '#default_value' => variable_get('image_attach_weight_body_'. $form['#node_type']->type, 0),
        '#description' => t('This value determines the order of the image when displayed in the body.'),
      );
    }
    if (module_exists('content')) {
      $link = l(t('Manage fields'), $_GET['q'] .'/fields');
      $form['image_attach']['#description'] = t('Since you installed CCK module, you can change the image weight in the !link page.', array('!link' => $link));
    } 
  }
}

/**
 * Implementation of hook_form_alter().
 */
function image_attach_form_alter(&$form, $form_state, $form_id) {
  // Node edit form.
  if (isset($form['type']['#value']) && $form['type']['#value'] != 'image') {
    $type = $form['type']['#value'];
    // If enabled adjust the form.
    if ($form_id == $type .'_node_form' && variable_get('image_attach_'. $type, 0)) {
      $node = $form['#node'];
      _image_check_settings();
      $value = !empty($node->new_image) ? '#value' : '#default_value';
      $form['#attributes'] = array("enctype" => "multipart/form-data");
      // Add a custom submit handler so we can handle image creation on-the-fly
      $form['#validate'][] = 'image_attach_validate'; 
      // Add a custom submit handler so we can clean up the selection box items.
      $form['#submit'][] = 'image_attach_node_form_submit'; 
      
      // Check permissions and settings
      $may_attach           = user_access('attach images');
      $may_attach_existing  = variable_get('image_attach_existing', 1);
      $may_upload           = user_access('create images');
      $has_existing_images  = !empty($node->iids);
      $maximum_images       = variable_get('image_attach_maximum_' . $type, 0);

      // Display the image attach form only if user can attach images, AND
      // it is allowed to attach existing images or the user is allowed to create new images 
      if ($may_attach && ($may_attach_existing || $may_upload)) {
        $form['image_attach'] = array(
          '#type' => 'fieldset',
          '#title' => t('Attached images'),
          '#collapsible' => TRUE,
          '#collapsed' => empty($node->iids),
        );

        if ($maximum_images) {
          $form['image_attach']['#description'] = format_plural(
            $maximum_images,
            'You may attach 1 image.',
            'You may attach up to @count images.'
          );
        }

        if ($has_existing_images) {
          foreach ($node->iids as $iid) {
            $image = node_load($iid);
            $form['image_attach']['image_thumbnail'][$iid] = array(
              '#type' => 'item',
              '#title' => t('Thumbnail'),
              '#value' => image_display($image, 'thumbnail'),
            );
          }
        }
        
        // Only show selection box of image nodes if the user may attach some,
        // or if there are existings ones that may be removed.
        if ($may_attach_existing || $has_existing_images) {
          $form['image_attach']['iids'] = array(
            '#type' => 'select',
            $value => empty($node->iids) ? NULL : $node->iids,
            '#multiple' => TRUE,
            '#size' => 6,
            // Title, options and description are set just below.
          );
  
          // User may attach already existing images: show a selection box containing all images.
          if ($may_attach_existing) {
            $form['image_attach']['iids']['#title'] = t('Existing images');
            $form['image_attach']['iids']['#options'] = _image_attach_get_image_nodes();
            if ($may_upload) {
              $form['image_attach']['iids']['#description'] = t('Choose existing images if you do not upload a new one.');
            }
            else {
              $form['image_attach']['iids']['#description'] = t('Choose existing images to attach.');
            }
          }
          // User may only upload new images: show a selection box containing only attached images.
          else {
              $form['image_attach']['iids']['#title'] = t('Attached images');
              $form['image_attach']['iids']['#options'] = _image_attach_get_image_nodes($node->iids);
              $form['image_attach']['iids']['#description'] = t('You can remove a previously attached image by unselecting it.');
          }
        }

        // User may create images, add upload form elements.
        if ($may_upload) {
          $form['image_attach']['image'] = array(
            '#type' => 'file',
            '#title' => t('Upload image')
          );
          $form['image_attach']['image_title'] = array(
            '#type' => 'textfield',
            '#title' => t('Image title'),
            $value => '',
            '#description' => t('The title the image will be shown with. Leave blank to use the filename.')
          );
          // Provide an additional submit button, which adds an image and redirects
          // the user to the node edit form.
          $form['image_attach']['image_attach_multiple'] = array(
            '#type' => 'submit',
            '#value' => t('Attach'),
            '#validate' => array('image_attach_validate'),
            '#submit' => array('image_attach_image_add_submit'),
          );
        }
      }
    }
  }
}

/**
 * Save attached image nids and rebuild form.
 *
 * This submit function adds the new images and returns to the
 * node edit form directly afterwards, without creating the new node yet.
 */
function image_attach_image_add_submit(&$form, &$form_state) {
  // Rebuild the attached image data.
  if (isset($form_state['values']['iids'])) {
    db_query("DELETE FROM {image_attach} WHERE nid = %d", $form['nid']['#value']);
    if (count($form_state['values']['iids'])) {
      $weight = 0;
      foreach ($form_state['values']['iids'] as $iid) {
        db_query("INSERT INTO {image_attach} (nid, iid, weight) VALUES (%d, %d, %d)", $form['nid']['#value'], $iid, $weight++);
      }
    }
  }

  // Convert taxonomy format from Preview to Object.
  if (module_exists('taxonomy') && !empty($form_state['values']['taxonomy'])) {
    $temp_node = new stdClass();
    $temp_node->taxonomy = $form_state['values']['taxonomy'];
    $form_state['values']['taxonomy'] = taxonomy_preview_terms($temp_node);
    unset($temp_node);
  }

  // Rebuild the node edit form.
  node_form_submit_build_node($form, $form_state);
}

/**
 * Image attach validation handler for node edit form.
 *
 * Check that the number of images has not exceeded the maximum.
 * Capture node form submission and immediately create an image if one has been
 * uploaded.
 * Note that the new image nodes are created even on preview. Taking several
 * attempts may create trash.
 */
function image_attach_validate(&$form, &$form_state) {
  // Validate the number of attached images. Filter out the 'None' with array_filter.
  if ($maximum_images = variable_get('image_attach_maximum_' . $form['#node']->type, 0)) {
    $uploading_new_image = ($form_state['clicked_button']['#value'] == t('Attach'));
    $num_images = count(array_filter($form_state['values']['iids']));
    
    $node_type_label = node_get_types('name', $form['#node']->type);
    if ($num_images >= $maximum_images && $uploading_new_image) {
      // This error will be set when attempting to upload a new image. 
      // The number already selected may be equal to the maximum, in which case 
      // the error is just to alert the user that their upload has not been performed, and allow
      // them to unselect an image and proceed to upload the new one.
      form_set_error('iids', t('You have selected @count_images but the maximum for a %type is @maximum. The new image has not been uploaded.', array(
        '@count_images' => format_plural($num_images, '1 image', '@count images'), 
        '@maximum'      => $maximum_images, 
        '%type'         => $node_type_label,
      )));
    }
    elseif ($num_images > $maximum_images) {
      form_set_error('iids', t('You have selected @count_images but the maximum for a %type is @maximum.', array(
        '@count_images' => format_plural($num_images, '1 image', '@count images'), 
        '@maximum'      => $maximum_images, 
        '%type'         => $node_type_label,
      )));
    }
  }

  // Validate and save the uploaded image, providing that there are no errors set.
  if (!count(form_get_errors())) {
    $validators = array(
      'file_validate_is_image' => array(),
    );
    if ($file = file_save_upload('image', $validators)) {
      $image_title = $_POST['image_title'] ? $_POST['image_title'] : basename($file->filepath);
      // Initialize an image properly.
      $image = image_create_node_from($file->filepath, $image_title, '');
      if ($image && !form_get_errors()) {
        drupal_set_message(t("Created new image to attach to this node. !image_link", array('!image_link' => l($image_title, 'node/'. $image->nid) )));
        // Append image nid to array of images.
        $form_state['values']['iids'][$image->nid] = $image->nid;
      }
    }
    else {
      // Only raise error if user clicked specific Attach button.
      if ($form_state['clicked_button']['#value'] == t('Attach')) {
        form_set_error('image_attach', t('Invalid or missing image file for upload and attach.'));
      }
    }
  }
}

/**
 * Extra submit handler for node forms.
 */
function image_attach_node_form_submit(&$form, &$form_state) {
  // Clear the 0 key in the iids array that arises from selecting the 'None' 
  // option. We do this here so image_attach_nodeapi() gets clean data.
  unset($form_state['values']['iids'][0]);
}

/**
 * Implementation of hook_nodeapi().
 */
function image_attach_nodeapi(&$node, $op, $teaser, $page) {
  // Make sure that if an image is deleted it is detached from any nodes.
  if ($node->type == 'image') {
    switch ($op) {
      case 'delete':
        db_query("DELETE FROM {image_attach} WHERE iid = %d", $node->nid);
    }
    return;
  }
  else if (variable_get('image_attach_'. $node->type, 0) == 0) {
    return;
  }
  switch ($op) {
    case 'insert':
    case 'update':
      db_query("DELETE FROM {image_attach} WHERE nid = %d", $node->nid);
      if (!empty($node->iids)) {
        // Populate weight column with placeholder values.
        $weight = 0;
        foreach ($node->iids as $iid) {
          db_query("INSERT INTO {image_attach} (nid, iid, weight) VALUES (%d, %d, %d)", $node->nid, $iid, $weight++);
        }
      }
      break;

    case 'delete':
      db_query("DELETE FROM {image_attach} WHERE nid = %d", $node->nid);
      break;

    case 'load':
      $res = db_query("SELECT iid FROM {image_attach} WHERE nid = %d ORDER BY weight", $node->nid);
      $iids = array();
      while ($iid = db_fetch_array($res)) {
        $iids[] = $iid['iid'];
      }
      return array('iids' => $iids);

    // Pass the body and teaser objects to the theme again to add the images.
    case 'view':
      if (!empty($node->iids)) {
        $teaser_or_body = $teaser ? 'teaser' : 'body';
        // Set weight, either from CCK or our own settings. Cribbed from signup!
        if (module_exists('content')) {
          // Due to a bug in CCK (http://drupal.org/node/363456), we need 
          // to call this function twice to ensure we get the real value.
          // The bug is present when you first enable the setting to
          // display in the node instead of a separate tab, or when you
          // first upgrade to the version that contains this code.
          content_extra_field_weight($node->type, 'image_attach');
          $weight = content_extra_field_weight($node->type, 'image_attach');
        }
        else {
          $weight = variable_get("image_attach_weight_{$teaser_or_body}_{$node->type}", 0);
        }        
        $node->content['image_attach'] = array('#weight' => $weight);
        if ($teaser_or_body == 'teaser') {
          // Reliably get the first element: this array is keyed by id rather 
          // than from 0 when we are showing a node preview.
          $first_iid = reset($node->iids);
          $node->content['image_attach'][$first_iid]['#value'] = theme("image_attach_{$teaser_or_body}", $node, $first_iid);
        }
        else {
          $content = '';
          foreach ($node->iids as $iid) {
            $content .= theme("image_attach_{$teaser_or_body}", $node, $iid);
          }
          $node->content['image_attach']['#value'] = '<div class="all-attached-images">' .$content. '</div>';
        }
      }
      break;

    case 'rss item':
      $ret = array();
      if (!empty($node->iids) && $image = node_load($node->iids[0])) {
        $info = image_get_info(file_create_path($image->images[IMAGE_PREVIEW]));
        $ret[] = array(
          'key' => 'enclosure',
          'attributes' => array(
            'url' => url("image/view/{$node->iids[0]}/". IMAGE_PREVIEW, array('absolute' => TRUE)),
            'length' => $info['file_size'],
            'type' => $info['mime_type'],
          )
        );
      }
      return $ret;
  }
}

/**
 * Fetch an array of all candidate referenced nodes, for use in presenting the 
 * selection form to the user.
 *
 * @param $nids
 *  A list of nids to filter on. If not passed, all image nids are returned. 
 */
function _image_attach_get_image_nodes($nids = array()) {
  $placeholder = '';
  // If $nids was passed, build placeholders to put in the query
  if (count($nids)) {
    $placeholder = 'AND n.nid IN ('. implode(', ', array_fill(0, sizeof($nids), '%d')) .') ';
  }

  $rows = array(0 => t('- None -'));

  $result = db_query(db_rewrite_sql("SELECT n.nid, n.title, n.sticky FROM {node} n WHERE n.status = 1 AND n.type = 'image' ". $placeholder ."ORDER BY n.sticky DESC, n.title ASC"), $nids);
  while ($node = db_fetch_object($result)) {
    $rows[$node->nid] = $node->title;
  }

  return $rows;
}


/**
 * Views 2 API handler
 * 
 */

function image_attach_views_api() {
return array(
  'api' => 2,
  'path' => drupal_get_path('module', 'image_attach'),
);
}

/**
 * Implementation of hook_theme() registry.
 **/
function image_attach_theme() {
  return array(
    'image_attach_teaser' => array(
      'arguments' => array('node' => new stdClass()),
    ),
    'image_attach_body' => array(
      'arguments' => array('node' => new stdClass()),
    ),
    'image_attach_block_attached' => array(
      'arguments' => array('nid' => 0, 'image_nodes' => array()),
    ),
  );
}

/**
 * Theme an image shown in teaser.
 *
 * @param $node
 *   The node object to theme.
 * @param $iid
 *   Node id of image to display.
 *
 * Override this in template.php to include a case statement if you want different node types to appear differently.
 * 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, $iid) {
  $img_size = variable_get('image_attach_size_teaser_'. $node->type, 'thumbnail');

  if ($img_size != IMAGE_ATTACH_HIDDEN) {
    drupal_add_css(drupal_get_path('module', 'image_attach') .'/image_attach.css');

    $image = node_load($iid);
    if (!node_access('view', $image)) {
      // If the image is restricted, don't show it as an attachment.
      return NULL;
    }
    $class = 'image-attach-teaser' . ($image->status ? '' : ' image-unpublished');
    $info = image_get_info(file_create_path($image->images[$img_size]));

    $output = '<div style="width: ' . $info['width'] . 'px" class="' . $class . '">';
    $output .= l(image_display($image, $img_size), "node/$node->nid", array('html' => TRUE));
    $output .= '</div>'."\n";

    return $output;
  }
}

/**
 * Theme an image shown in body
 *
 * @param $node
 *   The node object to theme.
 * @param $iid
 *   Nid of image to display.
 */
function theme_image_attach_body($node, $iid) {
  $img_size = variable_get('image_attach_size_body_'. $node->type, IMAGE_THUMBNAIL);

  if ($img_size != IMAGE_ATTACH_HIDDEN) {
    drupal_add_css(drupal_get_path('module', 'image_attach') .'/image_attach.css');

    $image = node_load($iid);
    if (!node_access('view', $image)) {
      // If the image is restricted, don't show it as an attachment.
      return NULL;
    }
    $class = 'image-attach-body' . ($image->status ? '' : ' image-unpublished');
    $info = image_get_info(file_create_path($image->images[$img_size]));

    $output = '<div style="width: ' . $info['width'] . 'px" class="' . $class . '">';
    $output .= l(image_display($image, $img_size), "node/$iid", array('html' => TRUE));
    $output .= '</div>'."\n";

    return $output;
  }
}

/**
 * Theme the attached images block.
 *
 * @param $nid
 *  The attaching node's id.
 * @param $image_nodes
 *  The attached image nodes.
 */
function theme_image_attach_block_attached($nid, $image_nodes = array()) {
  // Only return block content if there are images.
  if (is_array($image_nodes) && count($image_nodes)) {
    foreach ($image_nodes as $image) {
      // Render each image from the node.
      $img = image_display($image, variable_get('image_attach_block_0_size', IMAGE_THUMBNAIL));
      $content .= '<div class="attached-image">' .l($img, 'node/' . $image->nid, array('html' => TRUE)). '</div>';
    }
    return '<div class="all-attached-images">' .$content. '</div>';
  }
}

/**
 * Implementation of hook_content_extra_fields().
 */
function image_attach_content_extra_fields($type_name) {
  if (variable_get('image_attach_' . $type_name, 0)) {
    $extra['image_attach'] = array(
      'label' => t('Attached images'),
      'description' => t('Image Attach module form.'),
      'weight' => 0,
    );
    return $extra;
  }
}

