<?php
// $Id: imagecrop.module,v 1.1.4.55 2011/01/18 21:42:50 zuuperman Exp $

/**
 * @file
 * Provides a javascript toolbox through an imagecache action.
 *
 * @author Zuuperman - http://drupal.org/user/361625 - http://www.menhir.be
 * @version this is the drupal 6.x version
 */

/**
 * Implementation of hook_perm().
 */
function imagecrop_perm() {
  return array('crop any image with toolbox', 'crop images with toolbox', 'administer imagecrop');
}

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

  $items['admin/settings/imagecrop'] = array(
    'title' => 'Imagecache javascript crop',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('imagecrop_settings'),
    'access arguments' => array('administer imagecrop'),
    'file' => 'imagecrop.admin.inc',
  );

  $items['imagecrop/showcrop'] = array(
    'page callback' => 'imagecrop_showcrop',
    'type' => MENU_CALLBACK,
    'access arguments' => array('crop images with toolbox'),
    'file' => 'imagecrop.admin.inc',
  );

  $items['imagecrop/docrop'] = array(
    'page callback' => 'imagecrop_docrop',
    'type' => MENU_CALLBACK,
    'access arguments' => array('crop images with toolbox'),
    'file' => 'imagecrop.admin.inc',
  );

  return $items;
}

/**
 * Implementation of hook_theme().
 */
function imagecrop_theme() {
  return array(
    'imagecrop_javascript' => array(
      'arguments' => array('element' => NULL),
    ),
    'imagecrop' => array(
      'arguments' => array('url' => NULL, 'width' => NULL, 'height' => NULL, 'resize' => NULL),
    ),
    'imagecrop_result' => array(
      'arguments' => array('presetname' => NULL, 'filepath' => NULL, 'alt' => NULL, 'attributes' => NULL),
    ),
    'presettabs' => array(
      'arguments' => array('presets' => array(), 'fid' => NULL, 'presetid' => NULL, 'module' => NULL, 'field' => NULL, 'node_type' => NULL),
    ),
  );
}

/**
 * Implementation of hook_theme_registry_alter().
 */
function imagecrop_theme_registry_alter(&$theme_registry) {
  array_unshift($theme_registry['page']['theme paths'], drupal_get_path('module', 'imagecrop'));
}

/**
 * Implementation of hook_imagecrop_popups().
 */
function imagecrop_imagecrop_popups() {

  $popups = array();

  if (module_exists('thickbox')) {
    $popups['imagecrop_thickbox'] = t('Thickbox');
  }

  if (module_exists('modalframe')) {
    $popups['imagecrop_modalframe'] = t('Modalframe');
  }

  if (module_exists('colorbox')) {
    $popups['imagecrop_colorbox'] = t('Colorbox');
  }

  if (module_exists('shadowbox')) {
    $popups['imagecrop_shadowbox'] = t('Shadowbox');
  }

  return $popups;

}

/**
 * Implementation of hook_cron().
 * Delete all references in imagecrop table when
 *   a) file doesn't exist anymore.
 *   b) when preset has been deleted.
 *   c) when javascrip_crop action is removed from a preset.
 */
function imagecrop_cron() {
  // get all files which do not exist anymore from the files table
  $result = db_query("SELECT ic.fid,ic.presetid FROM {imagecrop} ic WHERE NOT EXISTS (SELECT fid FROM {files} f WHERE ic.fid = f.fid) AND ic.reference = 'files'");
  while ($row = db_fetch_object($result)) {
    $records[] = array('fid' => $row->fid, 'presetid' => $row->presetid, 'reference' => 'files');
  }
  // get all files which do not exist anymore from the node_images table
  if (module_exists('node_images')) {
    $result = db_query("SELECT ic.fid,presetid FROM {imagecrop} ic WHERE NOT EXISTS (SELECT id FROM {node_images} ni WHERE ic.fid = ni.id) AND ic.reference = 'node_images'");
    while ($row = db_fetch_object($result)) {
      $records[] = array('fid' => $row->fid, 'presetid' => $row->presetid, 'reference' => 'node_images');
    }
  }
  /*
   * Get all records
   *  a) from presets which do not exist anymore.
   *  b) and/or from presets with no imagecrop_javascript action anymore.
   */
  // files table
  $result = db_query("SELECT ic.fid,ic.presetid FROM {imagecrop} ic WHERE NOT EXISTS (SELECT presetid FROM {imagecache_action} ia where ic.presetid = ia.presetid AND ia.action = 'imagecrop_javascript') AND ic.reference = 'files'");
  while ($row = db_fetch_object($result)) {
    $records[] = array('fid' => $row->fid, 'presetid' => $row->presetid, 'reference' => 'files');
  }
  // node_images table
  if (module_exists('node_images')) {
    $result = db_query("SELECT ic.fid,ic.presetid FROM {imagecrop} ic WHERE NOT EXISTS (SELECT presetid FROM {imagecache_action} ia where ic.presetid = ia.presetid AND ia.action = 'imagecrop_javascript') AND ic.reference = 'node_images'");
    while ($row = db_fetch_object($result)) {
      $records[] = array('fid' => $row->fid, 'presetid' => $row->presetid, 'reference' => 'node_images');
    }
  }
  if (!empty($records)) {
    while (list($key, $val) = each($records)) {
      db_query("DELETE FROM {imagecrop} WHERE fid=%d AND presetid=%d AND reference = '%s'", $val['fid'], $val['presetid'], $val['reference']);
    }
  }
}

/**
 * Implementation of filefield.module's hook_file_delete().
 *
 * Remove imagecrop settings + temp files after a file has been deleted.
 */
function imagecrop_file_delete($file) {

  db_query("DELETE FROM {imagecrop} WHERE fid = %d", $file->fid);
  file_delete(imagecache_create_path('_imagecrop_temp', $file->filepath));

}

/**
 * Implementation of hook_imagecache_actions().
 */
function imagecrop_imagecache_actions() {
  $actions = array(
    'imagecrop_javascript' => array(
      'name' => 'Javascript crop',
      'description' => 'Create a crop with a javascript toolbox.',
      'file' => 'imagecrop_actions.inc',
    ),
  );
  return $actions;
}

/**
 * Implementation of hook_widget_settings_alter().
 */
function imagecrop_widget_settings_alter(&$settings, $op, $widget) {
  // Only support modules that implement hook_insert_widgets().
  $widget_type = isset($widget['widget_type']) ? $widget['widget_type'] : $widget['type'];
  if ($widget_type != 'imagefield_widget' && $widget_type != 'image_fupload_imagefield_widget') {
    return;
  }

  // Add our new options to the list of settings to be saved.
  if ($op == 'save') {
    $settings = array_merge($settings, imagecrop_widget_settings());
  }

  // Add the additional settings to the form.
  if ($op == 'form') {
    $settings = array_merge($settings, imagecrop_widget_form($widget));
  }
}

/**
 * A list of settings needed by Imagecrop module on widgets.
 */
function imagecrop_widget_settings() {
  return array(
    'imagecrop',
    'imagecrop_presets',
  );
}

/**
 * Configuration form for editing Imagecrop settings for a field instance.
 */
function imagecrop_widget_form($widget) {

  $form['imagecrop'] = array(
    '#type' => 'fieldset',
    '#title' => t('Imagecrop'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
    '#description' => t('These options allow the user to alter JavaScript crops for specific ImageCache presets.'),
    '#weight' => 15,
  );

  $presets = imagecrop_presets_list();
  if (count($presets) > 0) {
    $form['imagecrop']['imagecrop'] = array(
      '#type' => 'checkbox',
      '#title' => t('Enable JavaScript crop'),
      '#default_value' => (bool) $widget['imagecrop'],
      '#description' => t('Enable JavaScript image crop tool for this widget.'),
    );

    $form['imagecrop']['imagecrop_presets'] = array(
      '#title' => t('Enabled imagecrop presets'),
      '#type' => 'checkboxes',
      '#options' => $presets,
      '#default_value' => (array) $widget['imagecrop_presets'],
      '#description' => t('Select which Imagecache presets should be available for cropping. If no presets are selected, the option to crop the image is not displayed.'),
    );
  }
  else {
    $form['imagecrop']['imagecrop_warning'] = array(
      '#value' => t('No preset is found with the javascript_crop action so far. If you want to take advantage of this module, you will need to create at least one preset with that action.'),
    );
  }

  return $form;
}

/**
 * Get a list of styles suitable for an #options array.
 */
function imagecrop_presets_list() {

  $presets = array();
  foreach (imagecache_presets() as $preset) {
    foreach ($preset['actions'] as $action) {
      if ($action['action'] == 'imagecrop_javascript') {
        $presets[$preset['presetname']] = $preset['presetname'];
      }
    }
  }
  return $presets;

}

/**
 * Implementation of hook_elements().
 */
function imagecrop_elements() {

  // Add our function to the form element declared by imagefield.
  $elements = array();
  $elements['imagefield_widget'] = array('#after_build' => array('imagecrop_process_cck_field'));
  $elements['image_fupload_imagefield_widget'] = $elements['imagefield_widget'];

  return $elements;

}

/**
 * Process function for imagecrop-enabled cck fields.
 */
function imagecrop_process_cck_field($element) {

  $field = content_fields($element['#field_name'], $element['#type_name']);

  // Bail out if user does not have permission to crop images.
  if (!user_access('crop images with toolbox')) {
    return $element;
  }

  // Bail out of imagecrop is not enabled on this field.
  if (!$field['widget']['imagecrop']) {
    return $element;
  }

  $imagecache_presets = array_filter((array) $field['widget']['imagecrop_presets']);
  if (empty($imagecache_presets)) {
    return $element;
  }

  $element['imagecrop'] = array(
    '#type' => 'markup',
    '#widget' => $field['widget'],
    '#weight' => 10,
    '#suffix' => '</div>',
  );

  if ($element['fid']['#value']) {
    $element['imagecrop']['#prefix'] = '<div class="imagecrop form-item container-inline">';
    $element['imagecrop']['#value'] = imagecrop_linkitem($element['fid']['#value'], 'imagefield', $element);
  }
  else {
    $element['imagecrop']['#prefix'] = '<div class="description">';
    $element['imagecrop']['#value'] = t('After uploading an image you\'ll be able to crop it.');
  }

  return $element;

}

/**
 * Implementation of hook_form_alter().
 * Hook into several existing image modules/fields.
 */
function imagecrop_form_alter(&$form, $form_state, $form_id) {

  // No need to do checks on forms, other then node form.
  if ($form['#id'] != 'node-form' && $form_id != '_node_images_edit_page' && $form_id != 'user_profile_form') {
    return;
  }

  // Only show cropping link when editing a node.
  if ($form['#id'] == 'node-form' && $form['nid']['#value'] == NULL) {
    return;
  }

  $access = user_access('crop images with toolbox');

  if (!$access) {
    return;
  }

  // get array of available modules
  $hooks = variable_get('imagecrop_modules', array());

  // hook into image module
  if (isset($form['images']['thumbnail']) && module_exists('image') && in_array('image', $hooks)) {

    // do we have presets with javascript_crop ?
    if (count(imagecrop_presets_list()) == 0) {
      return;
    }

    // it's anonying we have to make a database call to get the right fid.
    $fid = db_result(db_query("SELECT i.fid FROM {image} i LEFT JOIN {files} f on f.fid = i.fid WHERE i.nid=%d AND filepath='%s' AND filename = '_original'", $form['nid']['#value'], $form['images']['_original']['#value']));
    $form['croptab'] = array(
      '#type' => 'item',
      '#value' => imagecrop_linkitem($fid),
      '#weight' => -10,
    );

  }

  // hook into node_images module
  elseif ($form_id == '_node_images_edit_page' && module_exists('node_images')) {

    // do we have presets with javascript_crop ?
    if (count(return_presets()) == 0) {
      return;
    }

    $type = $form['#parameters'][2]->type;
    if (variable_get('node_images_position_'. $type, 'hide') != 'hide' && in_array('node_images_position_'. $type, $hooks)) {
      $form['node_images']['wrapper']['node_images']['imagecrop'] = array('#type' => 'hidden', '#value' => '1');
    }

  }

  // hook into profile picture
  elseif ($form_id == 'user_profile_form' && isset($form['picture']['current_picture']) && in_array('profile_picture', $hooks)) {
    $form['picture']['current_picture']['#value'] .= imagecrop_linkitem($form['_account']['#value']->uid, 'user');
  }

}

/**
 * Helper function to add click link
 *
 * @return $form form markup
 */
function imagecrop_linkitem($fid, $module = '', $element = '') {

  $popup_link_function = variable_get('imagecrop_popup', 'basic');
  $width = variable_get('imagecrop_popup_width', 700);
  $height = variable_get('imagecrop_popup_height', 600);

  $module = $module ? ('/0/'. $module) : '';
  $field = $element['#field_name'] ? ('/'. $element['#field_name']) : '';
  $node_type = $element['#type_name'] ? ('/'. $element['#type_name']) : '';
  $url = url('imagecrop/showcrop/'. $fid . $module . $field . $node_type, array('absolute' => TRUE));

  if ($popup_link_function != 'basic' && function_exists($popup_link_function) && $link = $popup_link_function($url, $width, $height)) {
    return $link;
  }
  else {
    return '[<a href="javascript:;" onclick="window.open(\''. $url .'\',\'imagecrop\',\'menubar=0,scrollbars=1,resizable=1,width='. $width .',height='. $height .'\');">'. t('Crop this image') .'</a>]';
  }

}

/**
 * Helper function to determine which preset exists and which to load
 *
 * @param $presetid id of preset
 * @return $presets array with presetid to load and list of all other possible presets
 */
function return_presets($presetid = '', $module = '', $field = '', $node_type = '') {

  $filter = array();
  // get possible presets for current imagefield
  if ($module == 'imagefield' && $field) {
    $element = content_fields($field, $node_type);
    if ($element['widget']['imagecrop']) {
      $filter = $element['widget']['imagecrop_presets'];
    }
  }

  $all_presets = imagecache_presets();
  $presets = array();
  foreach ($all_presets as $preset) {
    foreach ($preset['actions'] as $action) {
      if ($action['action'] == 'imagecrop_javascript') {
        if (!count($filter) || !empty($filter[$preset['presetname']])) {
          $presets['tabs'][] = array('id' => $preset['presetid'], 'name' => $preset['presetname']);
          if ($presetid == $preset['presetid']) {
            $presets['presetid'] = $preset['presetid'];
          }
        }
      }
    }
  }

  if (!isset($presets['presetid']) && count($presets) > 0) {
    $presets['presetid'] = $presets['tabs'][0]['id'];
  }

  return $presets;

}

/**
 * Render the imagecrop links for thickbox.
 */
function imagecrop_thickbox($url, $width, $height) {

  if (!module_exists('thickbox')) {
    return FALSE;
  }

  return '[<a class="thickbox" href="'. $url .'?KeepThis=true&TB_iframe=true&height='. $height .'&width='. $width .'">'. t('Crop this image') .'</a>]';

}

/**
 * Render the imagecrop links for colorbox.
 */
function imagecrop_colorbox($url, $width, $height) {

  if (!module_exists('colorbox')) {
    return FALSE;
  }

  return '[<a class="colorbox-load" href="'. $url .'?iframe=true&height='. $height .'&width='. $width .'">'. t('Crop this image') .'</a>]';

}

/**
 * Render the imagecrop links for modalframe.
 */
function imagecrop_modalframe($url, $width, $height) {

  if (!module_exists('modalframe')) {
    return FALSE;
  }

  // Send the Modal Frame javascript for parent windows to the page.
  modalframe_parent_js();
  $modalFrame = "Drupal.modalFrame.open({url:'$url',width:'$width',height:'$height'});";
  return '[<a href="javascript:;" onclick="'. $modalFrame .'">'. t('Crop this image') .'</a>]';

}

/**
 * Render the imagecrop links for shadowbox.
 */
function imagecrop_shadowbox($url, $width, $height) {

  if (!module_exists('shadowbox')) {
    return FALSE;
  }

  return '[<a rel="shadowbox;height='. $height .';width='. $width .';player=iframe" href="'. $url .'">'. t('Crop this image') .'</a>]';

}

/**
 * Helper function to create image
 *
 * @param $fid file id in files table
 * @param $presetid id of the preset
 * @param $cutoff delete the javascript crop action when user wants to define the offsets
 * @return $file with file, javascript crop and preset properties
 */
function create_image_object($fid, $presetid, $module = '', $cutoff = FALSE) {

  $file = _imagecrop_file_load($fid, $module);
  if ($file != FALSE) {
    $preset = imagecache_preset($presetid);

    if ($cutoff == FALSE) {

      // get the actions from the preset and and throw out the javascript_crop action
      // and every other action which comes after it.
      $break = FALSE;
      while (list($key, $val) = each($preset['actions'])) {

        if ($val['action'] == 'imagecrop_javascript') {
          $crop_width = $preset['actions'][$key]['data']['width'];
          $crop_height = $preset['actions'][$key]['data']['height'];
          $resizable = $preset['actions'][$key]['data']['resizable'];
          $aspect = $preset['actions'][$key]['data']['aspect'];
          $break = TRUE;
        }
        if ($break == TRUE) {
          unset($preset['actions'][$key]);
        }

      }

      // see if we have stored values already for this file
      $file->xoffset = 0;
      $file->yoffset = 0;
      $file->crop_width = $crop_width;
      $file->crop_height = $crop_height;
      $reference = ($module == 'node_images' || $module == 'user') ? $module : 'files';
      $row = db_fetch_object(db_query(
        "SELECT xoffset, yoffset, width, height, scale
            FROM {imagecrop} ic
         WHERE ic.fid = %d AND ic.presetid = %d AND ic.reference = '%s'", $fid, $presetid, $reference));
      $firstscale = FALSE;
      if (!empty($row)) {
        $file->xoffset = $row->xoffset;
        $file->yoffset = $row->yoffset;
        $file->crop_width = $row->width;
        $file->crop_height = $row->height;
        $file->scale = $row->scale;
        $firstscale = TRUE;
      }

      // resizable or not
      $file->resizable = $resizable;

      // start with original image size for upcoming actions.
      $src = $file->filepath;
      $download_method = variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC);
      $dst = imagecache_create_path(($cutoff || ($download_method == FILE_DOWNLOADS_PRIVATE)) ? $preset['presetname'] : '_imagecrop_temp', $file->filepath);
      file_delete($dst);
      imagecache_build_derivative($preset['actions'], $src, $dst);

      $orig = getimagesize($dst);
      $file->orig_width = $orig[0];
      $file->orig_height = $orig[1];

      // aspect ratio
      if ($aspect == 'CROP') {
        $aspect = $crop_width / $crop_height;
      }
      elseif ($aspect == 'KEEP') {
        $aspect = $file->orig_width / $file->orig_height;
      }
      $file->aspect = $aspect;

      // add scale action if necessary
      if ($row->scale != 'original' && $firstscale == TRUE) {
        $preset['actions'][] = array(
          'action' => 'imagecache_scale',
          'data' => array('width' => $row->scale, 'height' => '', 'upscale' => 'false'));
      }

    }

    $file->presetname = $preset['presetname'];

    $file->dst = $dst;
    $file->preset_destination = imagecache_create_path($file->presetname, $file->filepath);

    // create the file to display for the crop,
    // we also set a global presetid variable, so I can use this in
    // the javascript_crop action
    $GLOBALS['imagecrop_presetid'] = $presetid;

    // if we have a dst, flush the image with all the needed imagecache actions.
    if (!empty($dst)) {
      file_delete($dst);
      imagecache_build_derivative($preset['actions'], $src, $dst);
    }

    return $file;

  }
  else {
    return FALSE;
  }

}

/**
 * Helper function to load a file into an object
 *
 * @param $fid file id
 * @param $module specific module which does not use the files table
 * @return $file with properties of the file or false
 */
function _imagecrop_file_load($fid, $module) {

  global $user;

  if (empty($module) || $module == 'imagefield') {
    $file = db_fetch_object(db_query('SELECT * FROM {files} WHERE fid = %d', $fid));
  }
  elseif ($module == 'node_images') {
    $file = db_fetch_object(db_query('SELECT * FROM {node_images} WHERE id = %d', $fid));
  }
  elseif ($module == 'user') {

    $filepath = db_result(db_query('SELECT picture FROM {users} WHERE uid = %d', $fid));
    if ($filepath) {
      $file = new stdClass();
      $file->uid = $fid;
      $file->filepath = $filepath;
      $file->filemime = file_get_mimetype($filepath);
    }

  }

  if ($file) {

    // make sure it's an image. Any other mime extensions possible?
    // return false if it's not the right mime type
    $filemime = array('image/jpeg', 'image/gif', 'image/png', 'image/pjpeg');
    if (!in_array($file->filemime, $filemime)) return FALSE;

    // access denied if current user hasn't enough permissions
    if (!user_access('crop any image with toolbox')) {

      if ($module != 'user' && !user_access('administer nodes') && $user->uid != $file->uid) {
        drupal_access_denied();
        exit();
      }
      elseif ($user->uid != $file->uid) {
        drupal_access_denied();
        exit();
      }

    }

    // all seems ok, return file
    return $file;

  }

  // return false if no file was found.
  return FALSE;

}

/**
 * Add imagecrop css & javascript
 */
function imagecrop_markup($js, $css) {
  $path = drupal_get_path('module', 'imagecrop');
  if ($js == TRUE) drupal_add_js($path .'/imagecrop.js');
  if ($css == TRUE) drupal_add_css($path .'/imagecrop.css');
}

/**
 * Theme image crop.
 *
 * @param $url url of image
 * @param $width width of image
 * @param $height height of image
 * @param $resize wether the cropping box is resizeble or not
 * @return $output html of the javascript crop area
 */
function theme_imagecrop($url, $width, $height, $resize = 0) {

  $url = str_replace("'", "\\'", $url);

  $output = '
    <div><div class="imagefield-crop-wrapper" id="imagefield-crop-wrapper" style="position: absolute; margin-top: 45px; width: '. $width .'px; height: '. $height .'px;">
      <div id="image-crop-container" style="background-image: url(\''. $url .'\'); width:'. $width  .'px; height:'. $height  .'px;"></div>
      <div id="resizeMe" style="background-image: url(\''. $url .'\'); width:'. $width  .'px; height:'. $height  .'px; top: 20px;">';
  $output .= '</div></div></div><div style="clear:both;"></div>';

  return $output;
}

/**
 * Theme cropped image result.
 *
 * @param string $presetname
 * @param string $filepath
 * @param string $alt
 * @param string $attributes
 * @return image
 */
function theme_imagecrop_result($presetname, $filepath, $alt = '', $attributes = NULL) {
  $url = imagecache_create_url($presetname, $filepath, TRUE);
  return '<img src="'. $url .'" alt="'. check_plain($alt) .'" title="'. check_plain($alt) .'" '. $attributes .' />';
}

/**
 * Theme preset tabs
 *
 * @param $tabs array of available presets
 * @param fid file id
 * @param $presetid preset to highlight
 * @return $output html of the tabs
 */
function theme_presettabs($presets, $fid, $presetid, $module = '', $field = '', $node_type = '') {

  $module = $module ? ('/'. $module) : '';
  $field = $field ? ('/'. $field) : '';
  $node_type = $node_type ? ('/'. $node_type) : '';

  $tabs[0] = array(
    'data' => t('Select a preset to crop &raquo;'),
    'class' => 'preset-label'
  );

  foreach ($presets['tabs'] as $key => $value) {

    $key++;
    $class = ($value['id'] == $presetid) ? 'imagecrop_highlight' : '';
    $url = 'imagecrop/showcrop/'. $fid .'/'. $value['id'] . $module . $field . $node_type;
    $tabs[$key] = array(
      'data' => l($value['name'], $url),
    );

    if ($value['id'] == $presetid) {
      $tabs[$key]['class'] = 'active';
    }

  }

  return '<div id="imagecrop_presettabs">'. theme('item_list', $tabs, NULL, 'ul', array('id' => 'preset-tabs')) .'<br style="clear: both;"/></div>';

}