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) } } }