<?php
// $Id: upload_preview.module,v 1.1 2006/12/23 13:38:33 timcn Exp $

/**
 * @file
 * Adds image preview thumbnails to the file attachment section.
 */


/**
 * Implementation of hook_form_alter().
 */
function upload_preview_form_alter($form_id, &$form) {
	if (isset($form['type']) && $form['type']['#value'] .'_node_form' == $form_id) {
		// Alter the file listing.
		_upload_preview_node_form($form['attachments']['wrapper']['files']);
	}
	else {
		switch ($form_id) {
			case 'upload_admin_settings':
				// Add configuration options to the upload settings page.
				_upload_preview_admin_form($form);
				break;
			case 'upload_js':
				// Alter the file listing.
				_upload_preview_node_form($form['files']);
				break;
		}
	}
}

/**
 * Implementation of hook_cron().
 */
function upload_preview_cron() {
	$previews = variable_get('upload_preview_files', array());
	$threshold = time() - variable_get('upload_preview_lifetime', 86400);

	// Delete all files that are older than the maximum preview file lifetime.
	foreach ($previews as $file => $timestamp) {
		if ($timestamp < $threshold) {
			file_delete($file);
			unset($previews[$file]);
		}
	}

	variable_set('upload_preview_files', $previews);
}


/**
 * Helper function for hook_form_alter().
 */
function _upload_preview_node_form(&$files) {
	// Overwrite the theme function with the two-row theme which includes the
	// preview image.
	$files['#theme'] = 'upload_preview_form_current';

	foreach (element_children($files) as $id) {
		// Ensure that the preview image exists and add it to the form.
		_upload_preview_create_thumbnail($files[$id]);
	}
}



function _upload_preview_create_thumbnail(&$file) {
	// Only check for images
	if(strpos($file['filemime']['#value'], 'image/') === 0) {
		// Create the preview image file path based on the actual filename or the
		// preliminary filename (in the temp directory).
		$path = file_create_path(variable_get('upload_preview_path', 'preview')) .'/';
		$path .= md5($file['filepath']['#value']) .'.'. _upload_preview_mime_to_extension($file['filemime']['#value']);

		// If the file does not exist, try to scale it down
		if (!file_exists($path)) {
			$size = explode('x', variable_get('upload_preview_size', '150x150'));
			if (!image_scale($file['filepath']['#value'], $path, $size[0], $size[1])) {
				// If the scaling failed, the preview is not added to the form.
				$path = NULL;
			}
			else {
				// The image creation succeeded. Now, add the image to the preview files
				// array so that we can delete it later when it's not needed anymore.
				$previews = variable_get('upload_preview_files', array());
				$previews[$path] = time();
				variable_set('upload_preview_files', $previews);
			}
		}

		if ($path) {
			// The image exists and the preview can be added.
			$file['preview'] = array(
			'#type' => 'upload_preview_image',
			'#value' => file_create_url($path),
			);
		}
	}

	if (!isset($file['preview'])) {
		// When there is no preview (image could not be created or the file is not
		// an image), add an info text stating that fact.
		$file['preview'] = array(
		'#value' => '('. t('No preview') .')',
		);
	}

	// Add the values for our custom theme function.
	$file['remove']['#title'] = t('Delete');
	$file['list']['#title'] = t('List');
	$file['size']['#prefix'] = t('Size') .': ';

	// Check if the file is already saved or still in the temp directory.
	$dirty = !is_numeric($file['fid']['#value']);
	$url = file_create_url($dirty ? file_create_filename($file['filename']['#value'], file_create_path()) : $file['filepath']['#value']);

	// Modify the description field to be a textarea and change its description text.
	$file['description']['#type'] = 'textarea';
	$file['description']['#rows'] = 3;
	$file['description']['#resizable'] = FALSE;
	if ($dirty) {
		$file['description']['#description'] = t('This file is not yet saved. The prospective URL will be <a href="@url">@url</a>.', array('@url' => $url));
	}
	else {
		$file['description']['#description'] = t('This file can be found at <a href="@url">@url</a>.', array('@url' => $url));
	}

}


/**
 * Helper function. Returns the correct file ending for an image mime type.
 */
function _upload_preview_mime_to_extension($mime) {
	switch ($mime) {
		case 'image/jpeg':
		case 'image/jpg':
			return 'jpg';
			break;
		case 'image/gif':
			return 'gif';
			break;
		case 'image/png':
		default:
			return 'png';
	}
}

/**
 * Helper function for hook_form_alter().
 */
function _upload_preview_admin_form(&$form) {
	// Ensure that the buttons are at the very bottom.
	$form['buttons']['#weight'] = 50;

	$mode = variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC);
	$path = file_directory_path();
	$preview = file_create_path(variable_get('upload_preview_path', 'preview'));

	$form['preview'] = array(
	'#type' => 'fieldset',
	'#title' => t('Previews'),
	'#weight' => 5,
	'#description' => t('Controls the behavior of previews in the file attachment section.'),
	'#collapsible' => TRUE,
	'#collapsed' => FALSE,
	);

	$form['preview']['upload_preview_path'] = array(
	'#type' => 'textfield',
	'#title' => t('Preview path'),
	'#default_value' => substr($preview, strlen($path) + 1),
	'#field_prefix' => $mode == FILE_DOWNLOADS_PUBLIC ? file_create_url('') : '&lt;'. t('Private') .'&gt;/',
	'#description' => t('Subdirectory in the global file directory where previews will be stored.'),
	);

	$form['preview']['upload_preview_size'] = array(
	'#type' => 'textfield',
	'#title' => t('Image size'),
	'#default_value' => variable_get('upload_preview_size', '150x150'),
	'#size' => 15,
	'#maxlength' => 10,
	'#description' => t('The image size for the preview images (e.g. 150x150). The aspect ratio will be maintained while resizing.'),
	'#field_suffix' => '<kbd>'. t('WIDTHxHEIGHT') .'</kbd>'
	);

	$period = drupal_map_assoc(array(600, 1800, 3600, 7200, 14400, 28800, 86400, 172800, 604800), 'format_interval');
	$period['1000000000'] = t('Never');
	$form['preview']['upload_preview_lifetime'] = array(
	'#type' => 'select',
	'#title' => t('File lifetime'),
	'#description' => t('The period until upload preview files are deleted. In case they get deleted before the user saved the post, they are regenerated automatically. Requires crontab.'),
	'#default_value' => variable_get('upload_preview_lifetime', 86400),
	'#options' => $period,
	);

	if (!isset($form['#validate'])) {
		$form['#validate'] = array();
	}

	$form['#validate']['_upload_preview_admin_form_validate'] = array();
	_upload_preview_prerequisites($preview);
}

/**
 * Validate the preview image size.
 */
function _upload_preview_admin_form_validate($form_id, &$form_values) {
	if (!preg_match('~^[1-9]\d*x[1-9]\d*$~', $form_values['upload_preview_size'])) {
		form_set_error('upload_preview_size', t('The specified preview image size is invalid.'));
	}
}


/**
 * Ensure that the preview image directory exists. If it doesn't, try to create it.
 */
function _upload_preview_prerequisites(&$preview) {
	if (!file_check_directory($preview, FILE_CREATE_DIRECTORY)) {
		drupal_set_message(t('Attachment previews are disabled. The preview directory %directory has not been properly configured.', array('%directory' => $preview)), 'error');
		form_set_error('upload_preview_path');
	}

	// Also ensure, that we have a image toolkit available.
	if (!image_get_toolkit()) {
		drupal_set_message(t('Make sure you have a working image toolkit installed and enabled. Further information can be found on the <a href="@url">Image toolkit settings page</a>.', array('@url' => url('admin/settings/image-toolkit'))), 'error');
	}
}


/**
 * Theme the attachments list.
 */
function theme_upload_preview_form_current(&$form) {
	$header = array(t('Preview'), array('data' => t('Description'), 'colspan' => 3));

	foreach (element_children($form) as $key) {
		// Split up in two rows as the addition image would make the table too wide.
		// The first row contains the image and the description...
		$row = array();
		$row[] = array('data' => drupal_render($form[$key]['preview']), 'rowspan' => 2);
		$row[] = array('data' => drupal_render($form[$key]['description']), 'colspan' => 3);
		$rows[] = $row;

		// ... and the second row contains the list/remove checkboxes and the size information.
		$row = array();
		$row[] = drupal_render($form[$key]['remove']);
		$row[] = drupal_render($form[$key]['list']);
		$row[] = drupal_render($form[$key]['size']);
		$rows[] = $row;
	}
	$output = theme('table', $header, $rows);
	$output .= drupal_render($form);
	return $output;
}

/**
 * Theme a file upload preview image.
 */
function theme_upload_preview_image($element) {
	return '<img src="'. $element['#value'] .'" />';
}
function _element_sort($a, $b) {
	$a_weight = (is_array($a) && isset($a['#weight'])) ? $a['#weight'] : 0;
	$b_weight = (is_array($b) && isset($b['#weight'])) ? $b['#weight'] : 0;
	if ($a_weight == $b_weight) {
		return 0;
	}
	return ($a_weight < $b_weight) ? -1 : 1;
}

function drupal_render(&$elements) {
	if (!isset($elements) || (isset($elements['#access']) && !$elements['#access'])) {
		return NULL;
	}

	$content = '';
	// Either the elements did not go through form_builder or one of the children
	// has a #weight.
	if (!isset($elements['#sorted'])) {
		uasort($elements, "_element_sort");
	}
	if (!isset($elements['#children'])) {
		$children = element_children($elements);
		/* Render all the children that use a theme function */
		if (isset($elements['#theme']) && empty($elements['#theme_used'])) {
			$elements['#theme_used'] = TRUE;

			$previous = array();
			foreach (array('#value', '#type', '#prefix', '#suffix') as $key) {
				$previous[$key] = isset($elements[$key]) ? $elements[$key] : NULL;
			}
			// If we rendered a single element, then we will skip the renderer.
			if (empty($children)) {
				$elements['#printed'] = TRUE;
			}
			else {
				$elements['#value'] = '';
			}
			$elements['#type'] = 'markup';

			unset($elements['#prefix'], $elements['#suffix']);
			$content = theme($elements['#theme'], $elements);

			foreach (array('#value', '#type', '#prefix', '#suffix') as $key) {
				$elements[$key] = isset($previous[$key]) ? $previous[$key] : NULL;
			}
		}
		/* render each of the children using drupal_render and concatenate them */
		if (!isset($content) || $content === '') {
			foreach ($children as $key) {
				$content .= drupal_render($elements[$key]);
			}
		}
	}
	if (isset($content) && $content !== '') {
		$elements['#children'] = $content;
	}

	// Until now, we rendered the children, here we render the element itself
	if (!isset($elements['#printed'])) {
		$content = theme(!empty($elements['#type']) ? $elements['#type'] : 'markup', $elements);
		$elements['#printed'] = TRUE;
	}

	if (isset($content) && $content !== '') {
		$prefix = isset($elements['#prefix']) ? $elements['#prefix'] : '';
		$suffix = isset($elements['#suffix']) ? $elements['#suffix'] : '';
		return $prefix . $content . $suffix;
	}
}