<?php
// $Id$

/**
 * Loads a derivative item.
 *
 * @param $did
 *   The derivative identifier from the database.
 * @param $refresh
 *   Refreshes the memory list.
 * @return
 *   Returns FALSE on failure, derivative object on success.
 */
function mapi_derivative_load($did, $refresh = false) {
  static $derivatives;

  // refresh the cache
  if ($refresh || !isset($derivatives)) {
    $derivatives = array();
  }

  if (!is_numeric($did)) {
    return FALSE;
  }

  // if media cached
  if (array_key_exists($did, $derivatives)) {
    return $derivatives[$did];
  }

  // special case for original (0).
  if ($did === 0 || $did === '0') {
    $d = new stdClass;
    $d->did = $d->parent = 0;
    $d->name = 'original';
    $d->exts = '|'. implode('|', mapi_extension_list()) .'|';
  }
  // load from database
  else {
    $d = db_fetch_object(db_query("SELECT * FROM {mapi_derivatives} WHERE did=%d", $did));
  }

  // invalid derivative
  if (!$d) {
    return FALSE;
  }

  $derivatives[$did] = $d;

  return $derivatives[$did];
}

/**
 * Clears all derivatives generated for a particular identifier.
 *
 * @param $did
 *   The derivative identifier from the database.
 * @param $mid
 *   A specific media identifier. 0 means everything.
 */
function mapi_derivative_flush($did, $mid = 0) {
  // destroys all generated media by the derivative
  if (!($derivative = mapi_derivative_load($did))) {
    return;
  }

  // tells modules that the derivative itself is being flushed
  mapi_invoke_transforms('flush', null, $did);

  // cycle through all generated media (possibility to seperately clear caches for transforms that occur)
  $results = db_query("SELECT name FROM {mapi_transforms} WHERE did=%d", $did);
  while ($obj = db_fetch_object($results)) {
    mapi_invoke_transforms('flush', $obj->name, $did);
  }
}

/**
 *  Removes all the generated references to a path
 */
function mapi_derivative_flush_path($path) {
  $results = db_query("SELECT dest FROM {mapi_generated} WHERE source='%s'", $path);
  while ($object = db_fetch_object($results)) {
    if (is_file($object->dest)) {
      file_delete($object->dest);
    }
  }
  db_query("DELETE FROM {mapi_generated} WHERE source='%s'", $path);
}

/**
 * Returns the derivatives which can apply for a given extension.
 *
 * @param $ext
 *   The extension for which this exists.
 * @return
 *   Returns a list of derivative identifiers which match this extension.
 */
function mapi_derivatives_by_ext($ext) {
  $derivatives = array();
  $results = db_query("SELECT did, name FROM {mapi_derivatives} WHERE exts LIKE '%%|%s|%%'", $ext);
  while ($obj = db_fetch_object($results)) {
    $derivatives[$obj->did] = $obj->name;
  }
  return $derivatives;
}

/**
 * Returns the derivatives which can apply for a given type.
 *
 * @param $type
 *   The type for which this applies.
 * @return
 *   Returns a list of derivative identifiers which match this type.
 */
function mapi_derivatives_by_type($type) {
  $derivatives = array();
  foreach (mapi_type_extensions($type) as $ext) {
    $derivatives = array_merge($derivatives, mapi_derivatives_by_ext($ext));
  }
  return array_unique($derivatives);
}



/**
 * Gives the list of the child derivatives, for a specific derivative.
 *
 * @param $did
 *   The derivative identifier.
 * @return
 *   Returns a list of derivative identifiers which match this derivative.
 */
function mapi_derivatives_children($did) {
  $derivatives = array();
  $results = db_query("SELECT did FROM {mapi_derivatives} WHERE parent=%d", $did);
  while ($obj = db_fetch_object($results)) {
    $derivatives[] = $obj->did;
  }
  return $derivatives;
}

/**
 * Creates a derivative.
 *
 * @param $name
 *   The name for the derivative, will be stripped of all non-conforming characters.
 *   These characters are the alphanumeric, hyphen and underscore.
 * @param $exts
 *   An array of extensions, valid for this derivative
 * @param $parent
 *   The derivative identifier to which this derivative is a subset.
 * @return
 *   Returns derivatve object or FALSE on failure.
 */
function mapi_derivative_create($name, $exts, $parent = 0) {
  // guarantee that the name is properly formatted befoe going into the system
  if (!($derivative = mapi_derivative_load($parent))) {
    return FALSE;
  }

  $name = preg_replace('/[^\w\d\-\_]+/', '-', $name);

  $exts = (is_array($exts) ? $exts : array($exts));

  // must be derived on the basis of the parent
  if (count(array_intersect($exts, array_filter(explode('|', $derivative->exts))))) {
    $did = db_next_id("{mapi_derivatives}_did");
    db_query("INSERT INTO {mapi_derivatives} (did,parent,name,exts) VALUES (%d,%d,'%s','%s')",
      $did, $parent, $name, '|'. implode('|', $exts) .'|');

    return mapi_derivative_load($did);
  }

  return FALSE;
}

/**
 * Updates a derivative.
 *
 * @param $did
 *   The derivative identifier
 * @param $public
 *   Determines whether the derivative is public or private.
 * @param $exts
 *   An array of extensions, valid for this derivative
 * @return
 *   Returns derivative object, or FALSE on failure
 */
function mapi_derivative_update($did, $public, $exts) {
  if (!($derivative = mapi_derivative_load($did))) {
    return FALSE;
  }

  // update the database
  db_query("UPDATE {mapi_derivatives} SET exts='%s' WHERE did=%d", '|'. implode('|', $exts) .'|', $derivative->did);

  return mapi_derivative_load($derivative->did);
}

/**
 * Deletes a derivative and it's children.
 *
 * @param $did
 *   The derivative identifier.
 */
function mapi_derivative_delete($did) {
  if (!($derivative = mapi_derivative_load($did))) {
    return;
  }

  // have to kill all the derivatives which rely on this one.
  $results = db_query("SELECT did FROM {mapi_derivatives} WHERE parent=%d", $did);
  while ($obj = db_fetch_object($results)) {
    mapi_derivative_delete($obj->did);
  }

  // remove all generated derivatives
  mapi_derivative_flush($did);

  // delete from database
  db_query("DELETE FROM {mapi_derivatives} WHERE did=%d", $did);
}

/**
 * Gives the derivative name for a given derivative identifier.
 *
 * @param $did
 *   A derivative identifier.
 * @return
 *   Returns the derivative name, or FALSE on failure.
 */
function mapi_derivative_name($did) {
  return db_result(db_query("SELECT name FROM {mapi_derivatives} WHERE did=%d", $did));
}

/**
 * Creates the derivative for a media file, with possibility to force a new
 * generation.
 *
 * @param $source
 *   The source file to transform
 * @param $name
 *   The name of the derivative that is to occur.
 * @param $force
 *   Forces overwriting of any existing file.
 * @param $refresh
 *   Allows refresh of caching of the filenames.
 * @param $private
 *   Detemines whether to keep derivatives private
 * @return
 *   Returns the generated filename, or FALSE on failure.
 */
function mapi_derive($source, $name, $options = array()) {
  static $filenames = array();

	// necessary options used throughout the function
	$exists = (isset($options['exists']) ? $options['exists'] : FILE_EXISTS_RENAME);
	$workspace = (isset($options['workspace']) ? $options['workspace'] : null);

  // source file must exist
  if (!is_file($source) || !is_string($name)) {
    return FALSE;
  }

  // refresh cache
  if (isset($options['refresh']) && $options['refresh']) {
    $filenames = array();
  }

  // check the cache for already completed derivatives, doing requested copy to that location
  if (array_key_exists($source, $filenames) && ($exists != FILE_EXIST_REPLACE)) {
    if (array_key_exists($name, $filenames[$source])) {
      return $filenames[$source][$name];
    }
  }

  // check for possibly already generated versions, copying to location specified
	// ignoring if generated already
	$alternatives = mapi_generated_load($source);
	if (!empty($alternatives)) {
    // store all the alternatives for this filename in the filesnames section
    foreach ($alternatives as $key => $object) {
      $filenames[$source][$key] = $object->dest;
    }

    // check the indicator for this
		if (array_key_exists($name, $alternatives)) {
      if ($exists != FILE_EXISTS_REPLACE) {
        return $filenames[$source][$name];
      }
      // overwrite this location
      else {
        $dest = $alternatives[$name]->dest;
      }
    }
	}

  // get the extension for the source
  $ext = mapi_file_ext($source);

  // derivative must exist to be applied
  $derivative = db_fetch_object(db_query("SELECT * FROM {mapi_derivatives} WHERE name='%s' AND exts LIKE '%%|%s|%%'",  $name, $ext));
  if (!$derivative) {
    return FALSE;
  }

  // make sure that destination is actually set
  if (!isset($dest)) {
    $dest = dirname($source) .'/'. mapi_file_name($source) .'-'. $name .'.'. $ext;
  }

  // check for path existance for destination
  if (!mapi_directory_check(dirname($dest), true, false, $workspace)) {
		mapi_error(t('Directory (%dest) does not exist and could not be created.', array('%dest' => dirname($dest))));
		return FALSE;
	}

  // provide temporary working name, in case the destination is the source file
  $temp = mapi_get_workpath($workspace, 'temp') .'/media-'.  md5(chr(rand())) .'.'. $ext;

  // need to check if it already exists, if not don't worry about generating it
	// or we are forcing a replace of the filename
  if (!is_file($dest) || ($exists == FILE_EXISTS_REPLACE)) {
    // original filename
    $fname = $source;

    // first, make sure we generate all the parents which are required for this one
    if ($derivative->parent) {
      $d = mapi_derivative_load($derivative->parent);

      // are we able to generate the appropriate parent media
      if (!($fname = mapi_derive($source, $d->name))) {
        return FALSE;
      }
    }

    // copy the original file to new location, potential to change name
    if (!($temp = mapi_file_copy($fname, $temp))) {
      mapi_error(t("Unable to create derivative filename [%filename].", array('%filename' => $temp)));
      return FALSE;
    }

    // process according to transformation listing
    $transforms = db_query("SELECT * FROM {mapi_transforms} WHERE did=%d ORDER BY weight ASC", $derivative->did);
    while ($transform = db_fetch_object($transforms)) {
      // provide temporary filenames as destination since we're processing constantly on different files.
      $temp_dest = mapi_get_workpath($workspace, 'temp') .'/media-'.  md5(chr(rand())) .'.'. mapi_file_ext($temp);
      $filename = mapi_transform_execute($temp, $temp_dest, $transform->name, unserialize($transform->settings));

      // remove those files which have changed name, possibly extension
      if ($filename != $temp) {
        file_delete($temp);
      }
      // don't keep trying to process on transformation failure.
      if (!$filename) {
        return FALSE;
      }
      $temp = $filename;
    }

    // get the details for the newly generated filename
    $ext = mapi_file_ext($temp);
    $dimensions = mapi_file_dimensions($temp);

    // copy the temporary working file into the destination filename, forcing it to overwrite if necessary.
    if (!($dest = mapi_file_move($temp, $dest, ($force ? FILE_EXISTS_REPLACE : FILE_EXISTS_RENAME), $workspace))) {
      mapi_error(t("Unable to create derivative filename [%filename].", array('%filename' => $temp)));
      return FALSE;
    }

		// If already exists,
		if (array_key_exists($name, $alternatives)) {
			// remove filename, it's a different one
			if ($alternatives[$name]->dest != $dest) {
				file_delete($alternatives[$name]->dest);
			}

			// now update the table
			db_query("UPDATE {mapi_generated} SET dest='%s', dimensions='%s' WHERE source='%s' AND style='%s'", $dest, serialize($dimensions), $source, $name);
		}
		//
		else {
			db_query("INSERT {mapi_generated} (source, dest, style, dimensions) VALUES ('%s', '%s', '%s', '%s')", $source, $dest, $name, serialize($dimensions));
		}
		// reload the cache for this source.
		$alternatives = mapi_generated_load($source, true);
  }

  // save into cache
  $filenames[$source][$name] = $dest;

  // now return the modified filename
  return $dest;
}

/**
 * Determines whether a derivative name already applies to an extension.
 *
 * @param $name
 *   The name of the derivative
 * @param $exts
 *   An array of extensions
 * @return
 *   Returns an array of extensions for derivative if exists, otherwise returns FALSE.
 */
function mapi_derivative_unique($name, $exts) {
  $values = $exts;
  if (!is_array($exts) || !count($exts)) {
    return FALSE;
  }
  $sql = "SELECT did, exts FROM {mapi_derivatives} WHERE name='%s' AND ";
  $sql .= "(exts LIKE '%%|%s|%%'". str_repeat(" OR exts LIKE '%%|%s|%%'", count($exts)-1) .")";
  array_unshift($values, $name);
  $obj = db_fetch_object(db_query($sql, $values));
  if ($obj) {
    return explode('|', trim($obj->exts, '|'));
  }
  return FALSE;
}

/**
 * Returns a list of the derivatives available for an extension.
 *
 * @param $ext
 *   The extension
 * @return
 *   Returns an associated array of extension identifiers and names.
 */
function mapi_derivative_list($ext = null) {
  $list = array();
  if ($ext) {
    $results = db_query("SELECT did, name FROM {mapi_derivatives} WHERE exts LIKE '%%|%s|%%'", $ext);
  }
  else {
    $results = db_query("SELECT did, name FROM {mapi_derivatives}");
  }

  while ($obj = db_fetch_object($results)) {
    $list[$obj->did] = $obj->name;
  }
  return $list;
}

/**
 * Loads a transform.
 *
 * @param $tid
 *   A transform identifier
 * @return
 *   A transform object, or FALSE on failure.
 */
function mapi_transform_load($tid) {
  $obj = false;
  $obj = db_fetch_object(db_query("SELECT * FROM {mapi_transforms} WHERE tid=%d", $tid));
  if ($obj) {
    $obj->settings = unserialize($obj->settings);
  }
  return $obj;
}

/**
 * Deletes a transform. Forces flushing of all derivatives.
 *
 * @param $tid
 *   A transform identifier
 */
function mapi_transform_delete($tid) {
  // remove transform from database
  if (!($transform = mapi_transform_load($tid))) {
    return;
  }

  // remove all generated media which have the transform as part of it.
  mapi_derivative_flush($transform->did);

  // remove from the transformation database
  db_query("DELETE FROM {mapi_transforms} WHERE tid=%d", $transform->tid);
}

/**
 * Loads a transform list for a given derivative.
 *
 * @param $did
 *   A derivative identifier
 * @return
 *   An associative array with transform identifier is the key, and the name is the value.
 */
function mapi_transform_by_derivative($did) {
  $transforms = array();
  $results = db_query("SELECT tid, name FROM {mapi_transforms} WHERE did=%d ORDER BY weight ASC", $did);
  while ($obj = db_fetch_object($results)) {
    $transforms[$obj->tid] = $obj->name;
  }
  return $transforms;
}

/**
 * Returns a list of the extensions which the transform allows, given it's settings.
 *
 * @param $tid
 *   A transform identifier
 * @param $exts
 *   An array of extensions.
 * @return
 *   An array of extensions.
 */
function mapi_transform_allowable($tid, $exts) {
  $new = array();
  $transform = mapi_transform_load($tid);
  foreach ($exts as $ext) {
    $new = array_merge($new, mapi_invoke_transforms('test', $transform->name, $transform->settings, $ext));
  }
  $new = array_unique(array_filter($new));
  return $new;
}

/**
 * Create a transform.
 *
 * @param $did
 *   A derivative identifier
 * @param $name
 *   The name of the transform
 * @param $settings
 *   The settings that the transform requires
 * @param $insert
 *   The transform identifier, before which this transform will be placed. 0 means at the end of the line.
 * @return
 *   A transform object, or FALSE on failure.
 */
function mapi_transform_create($did, $name, $settings, $insert = 0) {
  if (!($derivative = mapi_derivative_load($did))) {
    return FALSE;
  }

  // reorder the weight system
  $weight = 0;
  $found = false;

  $results = db_query("SELECT * FROM {mapi_transforms} WHERE did=%d ORDER BY weight ASC", $did);
  while ($obj = db_fetch_object($results)) {
    // insert if found
    if ($obj->tid == $insert) {
      // save id in found
      $found = db_next_id("{mapi_transforms}_tid");
      db_query("INSERT INTO {mapi_transforms} (tid,did,name,weight,settings) VALUES (%d,%d,'%s',%d,'%s')",
        $found, $did, $name, $weight++, serialize($settings));
    }

    // reorder weighting of the transforms
    db_query("UPDATE {mapi_transforms} SET weight=%d WHERE tid=%d", $weight++, $obj->tid);
  }

  if (!$found) {
    $found = db_next_id("{mapi_transforms}_tid");
    db_query("INSERT INTO {mapi_transforms} (tid,did,name,weight,settings) VALUES (%d,%d,'%s',%d,'%s')",
      $found, $did, $name, $weight++, serialize($settings));
  }

  return mapi_transform_load($found);
}

/**
 * Updates a transform.
 *
 * @param $tid
 *   A transform identifier
 * @param $settings
 *   The settings that the transform requires
 * @return
 *   A transform object, or FALSE on failure.
 */
function mapi_transform_update($tid, $settings) {
  if (!($transform = mapi_transform_load($tid))) {
    return FALSE;
  }

  db_query("UPDATE {mapi_transforms} SET settings='%s' WHERE tid=%d", serialize($settings), $transform->tid);

  $transform->settings = $settings;

  return $transform;
}

/**
 * Returns the entire derivatives name list.
 *
 * @return
 *   Returns an array of the unique names within the list.
 */
function mapi_derivatives_name_list() {
  $list = array();

  // get the generated detail information
  $results = db_query("SELECT DISTINCT(name) FROM {mapi_derivatives} ORDER BY name ASC");
  while ($obj = db_fetch_object($results)) {
    $list[] = $obj->name;
  }

  return $list;
}


/**
 * Creates the branches of a subtree, used by mapi_derivatives_tree()
 *
 * @param $did
 *   The derivatives identifier.
 * @param $name
 *   The name of this branch
 * @return
 *   Returns a structured subtree or FALSE.
 */
function mapi_derivatives_subtree($did, $name) {
  if (mapi_installed()) {
    $tree = mapi_derivatives_subderivatives($did);
    $form = array('name' => $name);
    foreach ($tree as $key => $value) {
      $form[$key] = mapi_derivatives_subtree($key, $value);
    }
    return $form;
  }
  return FALSE;
}

/**
 * Creates the tree of the entire derivatives.
 *
 * @return
 *   Returns a structured tree, or FALSE.
 */
function mapi_derivatives_tree() {
  // load folders
  if (mapi_installed()) {
    $tree = mapi_derivatives_subderivatives(0);

    $form = array();
    foreach ($tree as $key => $value) {
      $form[$key] = mapi_derivatives_subtree($key, $value);
    }

    return $form;
  }
  return FALSE;
}

/**
 * Creates the list of subfolders for a folder, based on permissions for user.
 *
 * @param $did
 *   The derivatives identifier.
 * @return
 *   Returns an associative array, using the sub-derivative identifier to reference the name
 */
function mapi_derivatives_subderivatives($did) {
  $children = array();
  if (mapi_installed()) {
    $results = db_query("SELECT did, name, exts FROM {mapi_derivatives} WHERE parent=%d ORDER BY name ASC", $did);
    while ($obj = db_fetch_object($results)) {
      $exts = implode(', ', explode('|', trim($obj->exts, '|')));
      $children[$obj->did] = $obj->name .' - '. $exts;
    }
  }
  return $children;
}

/**
 *  Caches the derivatives listing
 */
function _mapi_derivatives_cache($refresh = false) {
  static $derivatives;

  if (!isset($derivatives) || $refresh) {
    // get entire list of derivatives
    $list = array();
    $results = db_query("SELECT did, name, parent FROM {mapi_derivatives} ORDER BY parent ASC, name ASC");
    while ($object = db_fetch_object($results)) {
      $list[$object->did] = $object;
    }

    // now calculate the structure
    $derivatives = array('line' => array());
    foreach ($list as $key => $object) {
      $line = $derivatives[$object->parent]['line'];
      $line = ($line ? $line .'/' : '') . $object->name;
      $derivatives[$key] = array('name' => $object->name, 'line' => $line);
      $derivatives['line'][$line] = $key;
    }
    ksort($derivatives['line']);
  }

  return $derivatives;
}

/**
 *  Loads the generated details from the database.
 */
function mapi_generated_load($source, $refresh = false) {
	static $cache = array();

	if (!array_key_exists($source, $cache) || ($refresh)) {
		$cache[$source] = array();
		$results = db_query("SELECT * FROM {mapi_generated} WHERE source = '%s'", $source);
		while ($object = db_fetch_object($results)) {
			$object->dimensions = unserialize($object->dimensions);
			$cache[$source][$object->style] = $object;
		}
	}

	return $cache[$source];
}

/**
 *  Returns the specific details for the generated files
 *  as an array.
 */
function mapi_generated_details($source, $name) {
	$details = mapi_generated_load($source);
	if (array_key_exists($name, $details)) {
		return array(
			'filename' => $details[$name]->dest,
			'dimensions' => $details[$name]->dimensions,
		);
	}

	return false;
}

/**
 *  Deletes a generated derivative
 */
function mapi_generated_delete($source, $name) {
  $details = mapi_generated_load($source);
  if (array_key_exists($name, $details)) {
    // remove the file if it exists
    if (is_file($details[$name]->dest)) {
      @unlink($details[$name]->dest);
    }
    db_query("DELETE FROM {mapi_generated} WHERE source='%s' AND style='%s'", $source, $name);

    // refresh cache of generated
    mapi_generated_load($source, true);
  }
}

/**
 *  Deletes a generated derivative
 */
function mapi_generated_deleteall($source) {
  $details = mapi_generated_load($source);
  foreach ($details as $name => $object) {
    if (is_file($object->dest)) {
      @unlink($object->dest);
    }
  }
  db_query("DELETE FROM {mapi_generated} WHERE source='%s'", $source);

  mapi_generated_load($source, true);
}

/**
 *  Will move the generated derivative to location, either into the directory or as a file.
 *  Only will move the file if it is generated
 *
 *  Returns false.
 */
function mapi_generated_move($original, $new, $name, $dest, $workspace = null) {
  $details = mapi_generated_load($original);
  if (array_key_exists($name, $details)) {
    // remove the file if it exists
    if (is_file($details[$name]->dest)) {
      $filename = (is_dir($dest) ? rtrim($dest, '/') .'/'. mapi_file_basename($details[$name]->dest) : $dest);
      if ($temp = mapi_file_move($details[$name]->dest, $filename, FILE_EXISTS_RENAME, $workspace)) {
        // update the database
        db_query("UPDATE {mapi_generated} SET dest='%s', source='%s' WHERE source='%s' AND style='%s'", $temp, $new, $original, $name);
        // refresh cache of old generated media
        mapi_generated_load($original, true);
      }
    }
  }
}

/**
 *  A more efficient way to move all the generated files for a source
 */
function mapi_generated_moveall($original, $new, $dest, $workspace = null) {
  if (is_dir($dest)) {
    $details = mapi_generated_load($original);
    foreach ($details as $name => $object) {
      $filename = rtrim($dest, '/') .'/'. mapi_file_basename($object->dest);
      if ($temp = mapi_file_move($object->dest, $filename, FILE_EXISTS_RENAME, $workspace)) {
        // update the database
        db_query("UPDATE {mapi_generated} SET dest='%s', source='%s' WHERE source='%s' AND style='%s'", $temp, $new, $original, $name);
      }
    }
    // update the cache
    mapi_generated_load($original, true);
  }
}
