You are here

resource.module in D7 Media 6

Resource API for Drupal, a replacement for files.

The Resource API superceeds Drupal's file api, providing an interface to manage Resources identified by a URL.

There are two base classes, Resource and ResourceScheme, that constitue the ResourceAPI. The Resource class provides a high level interaction with resources including event hooks, DB synchronization, and support methods including, but not limited, to url generation and mime identification. The ResourceScheme is a user defined stream wrapper for PHP.

In most cases each class extending Resource will have a complimentary ResourceScheme class, and will share configuration options and settings.

File

resource/resource.module
View source
<?php

/**
 * @file
 * Resource API for Drupal, a replacement for files.
 *
 * The Resource API superceeds Drupal's file api, providing an interface to
 * manage Resources identified by a URL.
 *
 * There are two base classes, Resource and ResourceScheme, that constitue the ResourceAPI.
 * The Resource class provides a high level interaction with resources including event hooks,
 * DB synchronization, and support methods including, but not limited, to url generation and
 * mime identification. The ResourceScheme is a user defined stream wrapper for PHP.
 *
 * In most cases each class extending Resource will have a complimentary ResourceScheme
 * class, and will share configuration options and settings.
 *
 */

/**
 * @todo:
 * interface PHP stream wrapper
 * interface DrupalStreamWrapperExtends PHP stream wrapper {
 *    function htmlUrl();
 *    function mime();
 *
 *
 */

/**
 * Implementation of hook_init().
 *
 * Registers core stream handlers for public and private files.
 */
function resource_init() {
  module_load_include('inc', 'resource', 'ResourceStreamWrapperManager');
  module_load_include('inc', 'resource', 'ResourceStreamWrapper');
  module_load_include('inc', 'resource', 'ResourcePublicStreamWrapper');
  module_load_include('inc', 'resource', 'ResourcePrivateStreamWrapper');
  $manager = ResourceStreamWrapperManager::singleton();
  $manager
    ->register('public', 'ResourcePublicStreamWrapper');
  $manager
    ->register('private', 'ResourcePrivateStreamWrapper');
}

/**
 *  Implements hook_perm().
 */
function resource_perm() {
  return array(
    'administer resources',
  );
}

/**
 *  Implements hook_menu().
 */
function resource_menu() {
  $items = array();

  // Menu items that are basically just menu blocks.
  $items['admin/settings/resource'] = array(
    'title' => 'Resource configuration',
    'description' => 'Configure Resource and stream handler settings.',
    'page callback' => 'resource_settings_overview',
    'access arguments' => array(
      'access administration pages',
    ),
    'file' => 'resource.admin.inc',
  );
  $items['admin/settings/resource/public'] = array(
    'title' => 'Public File (public://) settings',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'resource_settings_public_form',
    ),
    'access arguments' => array(
      'administer resources',
    ),
    'file' => 'resource.admin.inc',
  );
  $items['admin/settings/resource/private'] = array(
    'title' => 'Private File (private://) settings',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'resource_settings_private_form',
    ),
    'access arguments' => array(
      'administer resources',
    ),
    'file' => 'resource.admin.inc',
  );
  return $items;
}

/**
 * Resource API
 */

/**
 * Debug Logging function.
 */
function resource_debug($message) {
  if (TRUE) {
    watchdog('stream', $message, array(), WATCHDOG_DEBUG);
  }
}

/**
 * Base Resource class.
 *
 * The drupal_file class represents a physical file stored in Drupal's
 * 'File System Path'. It is important to understand that copy, move,
 * delete, etc method affect both the the physical file the object
 * represents and the database record for the file.
 *
 * Example Factory Usage:
 *
 *   $factory = new Resource();
 *   $file = $factory->load_id(87);
 *   $another = $file->load_path('images/logo.png');
 *
 *   $file = Resource::load_id(24);
 *
 *   $yet_another = $file->load('path', 'uploads/myfile.pdf');
 *
 *
 *   if ($yet_another->delete()) unset($yet_another);
 *
 */
class Resource {

  // @see drupal_file::load()
  protected static $files = array();
  protected static $streamWrapper;
  protected static $streamManager;
  protected $rid = 0;
  protected $uid = 1;
  protected $name = '';
  protected $url = '';
  protected $mime = 'application/octet-stream';
  protected $size = 0;
  protected $status = 0;
  protected $timestamp = 0;
  protected $htmlUrl = '';

  /**
   * drupal_file constructor.
   *
   * @param object $file (optional) stdClass object to initialize self with.
   */
  protected function __construct($resource) {
    $this->streamManager = ResourceStreamWrapperManager::singleton();
    $class = $this->streamManager
      ->class(parse_url($url, PHP_URL_SCHEME));
    $this->streamWrapper = new $class();
    $this->rid = isset($resource->rid) ? $resource->rid : 0;
    $this->url = $resource->url;
    $this->mime = $resoruce->mime;
    $this->size = $resource->size;
    $this->name = $resource->name;
    $this->htmlUrl = $this->streamWrapper
      ->htmlUrl($this->url);
  }

  /**
   * Create a new name for a resource by appending a number if a resource
   * exists at $destination.
   */
  function _destination($destination, $replace = FILE_EXISTS_RENAME) {
    if ($replace & FILE_EXISTS_REPLACE) {
      return true;
    }
    if (!file_exists($destination)) {
      return $destination;
    }
    if ($replace & FILE_EXISTS_ERROR) {
      return FALSE;
    }
    $streamdir = dirname($destination);
    $basename = basename($destination);

    // Destination file already exists, generate an alternative.
    $pos = strrpos($basename, '.');
    if ($pos !== FALSE) {
      $name = substr($basename, 0, $pos);
      $ext = substr($basename, $pos);
    }
    else {
      $name = $basename;
      $ext = '';
    }
    $counter = 0;
    do {
      $destination = $streamdir . '/' . $name . '_' . $counter++ . $ext;
    } while (file_exists($destination));
    return $destination;
  }

  /**
   * Create a copy a Resource.
   *
   * @param  string $destination (optional) @see file_copy.
   * @param int $replace (optional) @see file_destination
   * @return object|bool  drupal_file if the copy is successful, or FALSE
   */
  function copy($destination, $replace = FILE_EXISTS_RENAME) {
    $destination = $this
      ->_destination($destination, $replace);
    if (copy($this->url, $destination)) {
      $copy = clone $this;
      $copy->rid = NULL;
      $copy->url = $destination;
      if ($copy
        ->save()) {
        module_invoke_all('resource_copy', $this, $copy);
        return ResourceFactory::loadId($copy->rid);
      }
    }
    return FALSE;
  }

  /**
   * Delete a file and its database record.
   *
   * @param $force
   *   Boolean indicating that the file should be deleted even if
   *   hook_file_references() reports that the file is in use.
   * @return mixed
   *   TRUE for success, array for reference count, or FALSE in the event
   *   of an error.
   *
   * @see hook_file_references()
   */
  function delete($force = FALSE) {

    // If any module returns a value from the reference hook, the
    // file will not be deleted from Drupal, but file_delete will
    // return a populated array that tests as TRUE.
    if (!$force && ($references = module_invoke_all('resource', $this))) {
      return $references;
    }

    // Let other modules clean up on delete.
    module_invoke_all('resource_delete', $this);

    // Make sure the file is deleted before removing its row from the
    // database, so UIs can still find the file in the database.
    if (unlink($this->url)) {
      db_query('DELETE FROM {resource} WHERE rid = %d', $this->rid);

      // remove internally used static cache entries.
      $this
        ->reset_cache('rid::' . $this->rid);
      $this
        ->reset_cache('url::' . $this->url);
      return TRUE;
    }
    return FALSE;
  }

  /**
   * Move a Resource.
   *
   * @param  string $destination (optional) @see file_copy.
   * @param int $replace (optional) @see file_destination
   * @return bool
   * @see file_copy()
   */
  function move($destination, $replace = FILE_EXISTS_RENAME) {
    $destination = $this
      ->_destination($destination, $rename);
    if (copy($this->url, $destination)) {
      unlink($this->url);
      $orig = clone $this;
      $this->url = $destination;
      if ($this
        ->save()) {
        module_invoke_all('resource_move', $this, $orig);
        return ResourceFactory::loadId($this->rid);
      }
    }
    return FALSE;
  }

  /**
   * Save the current state of a drupal_file in the database.
   * If the file->fid is empty a new database record will be added.
   *
   * @return bool TRUE if save succeeded, FALSE if save failed.
   */
  function save() {
    $this->timestamp = time();
    $manager = DrupalStreamWrapperManager::singleton();
    $class = $manager
      ->streamWrapperClass(parse_url($this->url, PHP_URL_SCHEME));
    if ($class != get_class($this
      ->streamWrapperManager())) {
      $this->streamWrapperHandler = new $class();
    }
    $this->size = $this->streamWrapperHandler
      ->size($this->url);
    $this->mime = $this->streamWrapperHandler
      ->mime($this->url);
    if (empty($this->fid)) {
      $result = drupal_write_record('resource', $this);
      module_invoke_all('resource_insert', $this);
    }
    else {
      $result = drupal_write_record('resource', $this, 'rid');
      module_invoke_all('resource_update', $this);
    }
    return $result;
  }

  /**
   * Mark a file as permanent.
   *
   * @return bool
   */
  function set_status($bitmask = NULL) {
    if (is_null($bitmask)) {
      return $file->status;
    }
    elseif (db_query('UPDATE {files} SET status=%d', $bitmask, $this->fid)) {
      $this->status = $status;
      module_invoke_all('file_set_status', $this, $status);
      return TRUE;
    }
    return FALSE;
  }
  function refresh() {
    $this->htmlUrl = $this->streamWrapper
      ->htmlUrl($this->url);
    $this->size = $this->streamWrapper
      ->size($this->url);
    $this->mime = $this->streamWrapper
      ->mime($this->url);
    return $this;
  }

}
class ResourcePublic extends Resource {
  function url() {
  }
  function mime() {
  }

}
class ResourcePrivate extends ResourcePublic {
  function url() {
  }

}
class ResourceUpload extends Resource {
  public $source = '';
  public $errors;
  function save() {

    // validate if not already validated, error is validation didn't pass.
    if (!$this
      ->validate()) {
      return FALSE;
    }

    // rename the file to its original name.
    parent::save();
  }
  function validate($validators = array()) {
    if (!isset($this->errors)) {

      // Default validation for all uploads.
      $validators['file_validate_name_length'] = array();
      $this->errors = array();
      foreach ($validators as $function => $args) {
        array_unshift($args, $file);
        $errors = array_merge($errors, call_user_func_array($function, $args));
      }
    }
    return !empty($this->errors);
  }
  function errors() {
    return $this->errors;
  }
  function move($dest) {

    // validate if not already validated, error is validation didn't pass.
    if (!$this->fid && !$this
      ->save()) {
      return FALSE;
    }

    // for upload files we changed the filename in the path to a junk string...
  }

  /**
   * Saves a file upload to a temporary location
   *
   * The file will be added to the files table as a temporary file. Temporary
   * files are periodically cleaned. To make the file permanent file call
   * it's set_permanent() method.
   *
   * @param $source
   *   A string specifying the name of the upload field to save.
   * @param $validators
   *   An optional, associative array of callback functions used to validate the
   *   file. The keys are function names and the values arrays of callback
   *   parameters which will be passed in after the user and file objects. The
   *   functions should return an array of error messages, an empty array
   *   indicates that the file passed validation. The functions will be called in
   *   the order specified.
   * @param $destination
   *   A string containing the directory $source should be copied to. If this is
   *   not provided or is not writable, the temporary directory will be used.
   * @param $replace
   *   A boolean indicating whether an existing file of the same name in the
   *   destination directory should overwritten. A FALSE value will generate a
   *   new, unique filename in the destination directory.
   * @return
   *   An object containing the file information, or FALSE in the event of an
   *   error.
   */
  static function save_upload($source) {

    // check and see if there were any errors.
    if (drupal_file_upload::upload_error($source)) {
      return FALSE;
    }

    // Begin building file object.
    $file = new stdClass();
    $file->source = $source;
    $file->uid = $user->uid;
    $file->filename = basename($_FILES['files']['name'][$source]);

    // create a tmp path to use until the file is save to a final location else where.
    // we just use a random string to defang the file for processing in tmp.
    $file->filepath = file_destination(uniqid(), file_directory_temp(), FALSE);
    $file->filemime = $_FILES['files']['type'][$source];
    $file->filesize = $_FILES['files']['size'][$source];

    // Move uploaded files from PHP's upload_tmp_dir to Drupal's temporary
    // directory. This overcomes open_basedir restrictions for future file
    // operations.
    if (!move_uploaded_file($_FILES['files']['tmp_name'][$source], $file->filepath)) {
      form_set_error($source, t('File upload error. Could not move uploaded file.'));
      watchdog('file api', 'Upload error. Could not move uploaded file %file to destination %destination.', array(
        '%file' => $file->filename,
        '%destination' => $file->filepath,
      ));
      return FALSE;
    }
    return $file;
  }
  function upload_error($errno) {

    // If no file was uploaded there is an error. :)
    if (empty($_FILES['files']['tmp_name'][$source]) || !is_uploaded_file($_FILES['files']['tmp_name'][$source])) {
      return t('No file uploaded');
    }

    // @see http://php.net/manual/en/features.file-upload.errors.php
    switch ($_FILES['files']['error'][$source]) {
      case UPLOAD_ERR_OK:
        return FALSE;
      case UPLOAD_ERR_INI_SIZE:
      case UPLOAD_ERR_FORM_SIZE:
        return t('The file %file could not be saved, because it exceeds %maxsize, the maximum allowed size for uploads.', array(
          '%file' => $source,
          '%maxsize' => format_size(file_upload_max_size()),
        ));
      case UPLOAD_ERR_PARTIAL:
      case UPLOAD_ERR_NO_FILE:
        return t('The file %file could not be saved, because the upload did not complete.', array(
          '%file' => $source,
        ));
      case UPLOAD_ERR_NO_TMP_DIR:
        return t('The file %file could not be saved, because the PHP upload_tmp_dir does not exist.', array(
          '%file' => $source,
        ));
      case UPLOAD_ERR_CANT_WRITE:
        return t('The file %file could not be saved, because the file could not be written to the disk.', array(
          '%file' => $source,
        ));
      case UPLOAD_ERR_EXTENSION:
        return t('The file %file could not be saved, because the upload was stopped by a php extension.', array(
          '%file' => $source,
        ));

      // Unknown error
      default:
        return t('The file %file could not be saved. An unknown error has occurred.', array(
          '%file' => $source,
        ));
    }
  }

  // Use static load as the entry point to keep the API interface
  // consistent.
  function load($source) {
    if (isset(self::$files[$source])) {
      return self::$files[$source];
    }

    // attempt to save the upload.
    if ($file = self::save_upload($source)) {
      return new ResourcePublic($file);
    }
  }

}
class ResourceFactory {
  static $resources;
  function __construct() {
  }

  /**
   * Return the first matching file in the files table. This is a simple single
   * object loader it in combination with the static $files variable allows all
   * drupal file objects to also act as factories and share the same static cache.
   *
   * @param string key (required) database column to use in where condition.
   * @param int|string value (required) the value of the column to use in the where condition.
   * @return object|bool A Drupal file object or FALSE if a file was not found.
   * @see drupal_file::load(), drupal_file::load_path()
   */
  public function _load($column, $value) {

    // set a cache id based on key and value so we can statically cache
    // all simple loads.
    $cid = $column . '::' . $value;
    if (empty(self::$files[$cid])) {
      $resource = db_fetch_object(db_query('SELECT f.* FROM {resource} r WHERE r.%s = %d', $column, $value));
      $resource = new Resource($r);
    }
    module_invoke_all('resource_load', $r);
    self::$resources[$cid] = $r;

    // Files are not cloned, because there is in fact only one.
    return self::$resources[$cid];
  }

  /**
   * Load a file object from the database by id.
   *
   * @param int   $id     A file id. (required)
   * @return object|bool A Drupal file object or FALSE if a file was not found.
   * @see: drupal_file::load()
   */
  public function loadId($id) {
    return $this
      ->_load('rid', $id);
  }

  /**
   * Load a file object from the database by path.
   *
   * @param string  $path   A path to a file. (required)
   * @return object|bool A Drupal file object or FALSE if a file was not found.
   * @see: drupal_file::load()
   */
  public function loadUrl($url) {
    return $this
      ->_load('url', $url);
  }
  public function loadUpload($key) {
  }

  /**
   * Reset the shared static cache.
   */
  public function resetCache($cid = FALSE) {

    // no cache id, reset the entire cache.
    if (!$cid) {
      self::$resources = array();
    }
    elseif (isset(self::$resources[$cid])) {
      unset(self::$resources[$cid]);
    }
  }

  // table relationship
  // relationship_id
  // left, (resource_id)
  // relationship ( parent_of, member_of, friend_of, ... )
  // right, (resource_id)
  // table resource_formatter
  // relationship_id
  // display_context(node_full, node_teaser, comment, block, profile, default)
  // media(screen, print),
  // formatter
  // formatter_arguments
  function loadChildren($rid, $context = NULL, $media = NULL) {

    // select * from {resource_relationships} where parent = $resource->id;
    // foreach child { $children[] = $this->loadId($child->rid)->context($context); }
    // return $children;
  }
  function loadParents($rid, $context = NULL, $media = NULL) {

    // select * from {resource_relationships} where child = $resource->id;
    // foreach parent { $parent[] = $this->loadId($child->rid)->context($context); }
    // return $parents;
  }
  function loadPeers($rid, $context, $media) {
  }

  /**
  * @param key    primary key for where clause
  * @param value  to limit key to.
   think about this context thing later..
  */
  function loadRelationship($key, $id, $context = NULL, $media = NULL) {

    // select * from relationships where $key = $candidate AND context = $context
    // foreach row { $relatives[] = $this->loadId($relative->rid)->context($row) }
  }

}

Functions

Namesort descending Description
resource_debug Debug Logging function.
resource_init Implementation of hook_init().
resource_menu Implements hook_menu().
resource_perm Implements hook_perm().

Classes