Commit 9510e4c9 authored by effulgentsia's avatar effulgentsia Committed by JacobSingh

Issue #1086958 by effulgentsia: Use FileEntity instead of Media Entity. ...

Issue #1086958 by effulgentsia: Use FileEntity instead of Media Entity.  Redefine the way files are formatted in Drupal, cold fusion, etc

adding a todo for issue http://drupal.org/node/1086966

Call up 'file' entities instead of 'media' entities in field_* calls; make the core file entity fieldable.

remove some hook_file_CRUD() implementations that are not necessary when we already have the file entity object. make sure that our file entity objects always have a bundle key set when we need it.

Make sure that file entities have a media bundle key ('type') set.

Fully removed definition of media entity. Now, we just have hook_entity_info_alter() for file entity.

Removed creation of file field on file entity. Will replace with 'extra_field'. Also removed dependency on Styles module. Will replace with lightweight formatter routing.

Removed file_usage_delete() hack made unnecessary by removing self-referential file field from the file entity. Also, made 'file' an 'extra_field' so it can be weighted in Field UI.

Removed media_load() and media_load_multiple() functions, since they're now redundant with file_load() and file_load_multiple(). Also, replaced calls to entity_load() with file_load_multiple().

Removed media_file_load() and media_entity_load(). They're unnecessary, as 'type' is automatically populated by virtue of being in the {file_managed} table. And we want loads to be as fast as possible, so removing stack calls helps.

Removing MediaEntityController. Hasn't been necessary for a while now, and especially with the move to file entities.

Unrelated to entity reorganization, but adding comment about why this redundant file is here.

Add a file_entity_api module to contain just the code needed for file entities to be fieldable and viewable. Will move the relevant Media code into here.

Initial migration of some code from Media to File Entity API

Added admin page for getting to the Field UI of a file type.

Removing "api" from file_entity modules, since a bare-bones UI is needed too.

Removing "api" from file_entity modules, since a bare-bones UI is needed too.

Doc cleanup

Some code organization cleanup

Much of the scaffolding for displaying a file entity. Now we just need to implement a UI for the 'file-displays' configuration page.

Doc and API cleanup

Added 'file-displays' UI

Fleshed out the API for assigning $file->type.

Yay. Full add/view/edit workflow is functional!

Updated file_entity_test.info to reflect dependency on file_entity.

Weird. Fieldsets without #weight can't participate in vertical tabs?

A bunch of Media module cleanup to work with new File Entity module. Probably still not working yet though.

Media field is working!

Moved image file formatter into file_entity module. Added file formatter bridge to file field formatters.

Added tester module for Media Youtube integration.

Ensuring that $file->type is never null, since Field API doesn't like it when bundle keys are null.

Removed old Media code that deals with NULL file types. File Entity now handles that.

Update function for switching to File Entity

Reinstated Media code that nags the administrator about files with missing types.

Missing type is now signified by empty string, not NULL.

Declaring all media view modes to have custom settings, as it effectively used to via prior media_enable() implementation.

Added default processing and better wysiwyg integration when Styles module not enabled.

Minor cleanup

Minor comments tweaking.
parent 7090930e
<?php
/**
* DO NOT EDIT THIS. IT WILL BE REMOVED IN THE NEXT RELEASE.
* This file is only here so that the class autoloader doesn't barf.
*
* It has been moved to includes
*/
/**
* Extends DrupalEntityControllerInterface.
*
* @see: http://drupal.org/project/entity_api (perhaps we should be using this)
*/
class MediaEntityController extends DrupalDefaultEntityController {
public function load($ids = array(), $conditions = array()) {
$this->ids = $ids;
$this->conditions = $conditions;
$entities = array();
// Revisions are not statically cached, and require a different query to
// other conditions, so separate the revision id into its own variable.
if ($this->revisionKey && isset($this->conditions[$this->revisionKey])) {
$this->revisionId = $this->conditions[$this->revisionKey];
unset($this->conditions[$this->revisionKey]);
}
else {
$this->revisionId = FALSE;
}
// Create a new variable which is either a prepared version of the $ids
// array for later comparison with the entity cache, or FALSE if no $ids
// were passed. The $ids array is reduced as items are loaded from cache,
// and we need to know if it's empty for this reason to avoid querying the
// database when all requested entities are loaded from cache.
$passed_ids = !empty($this->ids) ? array_flip($this->ids) : FALSE;
// Try to load entities from the static cache, if the entity type supports
// static caching.
if ($this->cache) {
$entities += $this->cacheGet($this->ids, $this->conditions);
// If any entities were loaded, remove them from the ids still to load.
if ($passed_ids) {
$this->ids = array_keys(array_diff_key($passed_ids, $entities));
}
}
// Load any remaining entities from the database. This is the case if $ids
// is set to FALSE (so we load all entities), if there are any ids left to
// load, if loading a revision, or if $conditions was passed without $ids.
if ($this->ids === FALSE || $this->ids || $this->revisionId || ($this->conditions && !$passed_ids)) {
// Build the query.
$this->buildQuery();
$queried_entities = $this->query
->execute()
->fetchAllAssoc($this->idKey);
}
// Pass all entities loaded from the database through $this->attachLoad(),
// which attaches fields (if supported by the entity type) and calls the
// entity type specific load callback, for example hook_node_load().
if (!empty($queried_entities)) {
$this->attachLoad($queried_entities);
$entities += $queried_entities;
}
if ($this->cache) {
// Add entities to the cache if we are not loading a revision.
if (!empty($queried_entities) && !$this->revisionId) {
$this->cacheSet($queried_entities);
}
}
// Ensure that the returned array is ordered the same as the original
// $ids array if this was passed in and remove any invalid ids.
if ($passed_ids) {
// Remove any invalid ids from the array.
$passed_ids = array_intersect_key($passed_ids, $entities);
foreach ($entities as $entity) {
$passed_ids[$entity->{$this->idKey}] = $entity;
}
$entities = $passed_ids;
}
return $entities;
}
protected function buildQuery() {
$this->query = db_select($this->entityInfo['base table'], 'base');
$this->query->addTag($this->entityType . '_load_multiple');
if ($this->revisionId) {
$this->query->join($this->revisionTable, 'revision', "revision.{$this->idKey} = base.{$this->idKey} AND revision.{$this->revisionKey} = :revisionId", array(':revisionId' => $this->revisionId));
}
elseif ($this->revisionKey) {
$this->query->join($this->revisionTable, 'revision', "revision.{$this->revisionKey} = base.{$this->revisionKey}");
}
// Add fields from the {entity} table.
$entity_fields = drupal_schema_fields_sql($this->entityInfo['base table']);
if ($this->revisionKey) {
// Add all fields from the {entity_revision} table.
$entity_revision_fields = drupal_map_assoc(drupal_schema_fields_sql($this->revisionTable));
// The id field is provided by entity, so remove it.
unset($entity_revision_fields[$this->idKey]);
// Change timestamp to revision_timestamp, and revision uid to
// revision_uid before adding them to the query.
// TODO: This is node specific and has to be moved into NodeController.
unset($entity_revision_fields['timestamp']);
$this->query->addField('revision', 'timestamp', 'revision_timestamp');
unset($entity_revision_fields['uid']);
$this->query->addField('revision', 'uid', 'revision_uid');
// Remove all fields from the base table that are also fields by the same
// name in the revision table.
$entity_field_keys = array_flip($entity_fields);
foreach ($entity_revision_fields as $key => $name) {
if (isset($entity_field_keys[$name])) {
unset($entity_fields[$entity_field_keys[$name]]);
}
}
$this->query->fields('revision', $entity_revision_fields);
}
$this->query->fields('base', $entity_fields);
if ($this->ids) {
$this->query->condition("base.{$this->idKey}", $this->ids, 'IN');
}
if ($this->conditions) {
foreach ($this->conditions as $field => $value) {
$this->query->condition('base.' . $field, $value);
}
}
}
/**
* Attach data to entities upon loading.
*
* This will attach fields, if the entity is fieldable. It calls
* hook_entity_load() for modules which need to add data to all entities.
* It also calls hook_TYPE_load() on the loaded entities. For example
* hook_node_load() or hook_user_load(). If your hook_TYPE_load()
* expects special parameters apart from the queried entities, you can set
* $this->hookLoadArguments prior to calling the method.
* See NodeController::attachLoad() for an example.
*/
protected function attachLoad(&$queried_entities) {
// Attach fields.
if ($this->entityInfo['fieldable']) {
if ($this->revisionId) {
field_attach_load_revision($this->entityType, $queried_entities);
}
else {
field_attach_load($this->entityType, $queried_entities);
}
}
// Call hook_entity_load().
foreach (module_implements('entity_load') as $module) {
$function = $module . '_entity_load';
$function($queried_entities, $this->entityType);
}
// Call hook_TYPE_load(). The first argument for hook_TYPE_load() are
// always the queried entities, followed by additional arguments set in
// $this->hookLoadArguments.
$args = array_merge(array($queried_entities), $this->hookLoadArguments);
foreach (module_implements($this->entityInfo['load hook']) as $module) {
call_user_func_array($module . '_' . $this->entityInfo['load hook'], $args);
}
}
/**
* Get entities from the static cache.
*
* @param $ids
* If not empty, return entities that match these IDs.
* @param $conditions
* If set, return entities that match all of these conditions.
*/
protected function cacheGet($ids, $conditions = array()) {
$entities = array();
// Load any available entities from the internal cache.
if (!empty($this->entityCache) && !$this->revisionId) {
if ($ids) {
$entities += array_intersect_key($this->entityCache, array_flip($ids));
}
// If loading entities only by conditions, fetch all available entities
// from the cache. Entities which don't match are removed later.
elseif ($conditions) {
$entities = $this->entityCache;
}
}
// Exclude any entities loaded from cache if they don't match $conditions.
// This ensures the same behavior whether loading from memory or database.
if ($conditions) {
foreach ($entities as $entity) {
$entity_values = (array) $entity;
if (array_diff_assoc($conditions, $entity_values)) {
unset($entities[$entity->{$this->idKey}]);
}
}
}
return $entities;
}
/**
* Store entities in the static entity cache.
*/
protected function cacheSet($entities) {
$this->entityCache += $entities;
}
}
<?php
/**
* DO NOT EDIT. This file will be removed in the next release. It has been moved
* to the includes folder, but is retained here so that the autoloader doesn't
* trigger a fatal error before media_update_7015() runs.
*/
/**
* A base class for Resource Stream Wrappers.
*
......
<?php
/**
* @file
* Administrative interface for file type configuration.
*/
/**
* Displays the file type admin overview page.
*/
function file_entity_list_types_page() {
$types = file_info_file_types();
$entity_info = entity_get_info('file');
$field_ui = module_exists('field_ui');
$header = array(
array('data' => t('Name')),
array('data' => t('Operations'), 'colspan' => $field_ui ? '3' : '1'),
);
$rows = array();
foreach ($types as $type => $info) {
$row = array(array('data' => theme('file_entity_file_type_overview', $info)));
$path = isset($entity_info['bundles'][$type]['admin']['real path']) ? $entity_info['bundles'][$type]['admin']['real path'] : NULL;
if ($field_ui) {
$row[] = array('data' => isset($path) ? l(t('manage fields'), $path . '/fields') : '');
$row[] = array('data' => isset($path) ? l(t('manage display'), $path . '/display') : '');
}
$row[] = array('data' => isset($path) ? l(t('manage file display'), $path . '/file-display') : '');
$rows[] = $row;
}
$build['file_type_table'] = array(
'#theme' => 'table',
'#header' => $header,
'#rows' => $rows,
'#empty' => t('No file types available.'),
);
return $build;
}
/**
* Form callback; presents file display settings for a given view mode.
*/
function file_entity_file_display_form($form, &$form_state, $file_type, $view_mode) {
$form['#file_type'] = $file_type;
$form['#view_mode'] = $view_mode;
$form['#tree'] = TRUE;
$form['#attached']['js'][] = drupal_get_path('module', 'file_entity') . '/file_entity.admin.js';
// Retrieve available formatters for this file type and load all configured
// filters for existing text formats.
$formatters = file_info_formatter_types();
foreach ($formatters as $name => $formatter) {
if (isset($formatter['file types']) && !in_array($file_type, $formatter['file types'])) {
unset ($formatters[$name]);
}
}
$current_displays = variable_get('file_displays');
$current_displays = isset($current_displays[$file_type][$view_mode]) ? $current_displays[$file_type][$view_mode] : array();
// Formatter status.
$form['displays']['status'] = array(
'#type' => 'item',
'#title' => t('Enabled displays'),
'#prefix' => '<div id="file-displays-status-wrapper">',
'#suffix' => '</div>',
);
$i=0;
foreach ($formatters as $name => $formatter) {
$form['displays']['status'][$name] = array(
'#type' => 'checkbox',
'#title' => $formatter['label'],
'#default_value' => !empty($current_displays[$name]['status']),
'#description' => isset($formatter['description']) ? $formatter['description'] : NULL,
'#parents' => array('displays', $name, 'status'),
'#weight' => $formatter['weight'] + $i/1000,
);
$i++;
}
// Formatter order (tabledrag).
$form['displays']['order'] = array(
'#type' => 'item',
'#title' => t('Display precedence order'),
'#theme' => 'file_entity_file_display_order',
);
foreach ($formatters as $name => $formatter) {
$form['displays']['order'][$name]['label'] = array(
'#markup' => check_plain($formatter['label']),
);
$form['displays']['order'][$name]['weight'] = array(
'#type' => 'weight',
'#title' => t('Weight for @title', array('@title' => $formatter['label'])),
'#title_display' => 'invisible',
'#delta' => 50,
'#default_value' => isset($current_displays[$name]['weight']) ? $current_displays[$name]['weight'] : 0,
'#parents' => array('displays', $name, 'weight'),
);
$form['displays']['order'][$name]['#weight'] = $form['displays']['order'][$name]['weight']['#default_value'];
}
// Formatter settings.
$form['display_settings_title'] = array(
'#type' => 'item',
'#title' => t('Display settings'),
);
$form['display_settings'] = array(
'#type' => 'vertical_tabs',
);
$i=0;
foreach ($formatters as $name => $formatter) {
if (isset($formatter['settings callback']) && ($function = $formatter['settings callback']) && function_exists($function)) {
$defaults = isset($formatter['default settings']) ? $formatter['default settings'] : array();
$settings = isset($current_displays[$name]['settings']) ? $current_displays[$name]['settings'] : array();
$settings += $defaults;
$settings_form = $function($form, $form_state, $settings, $name, $file_type, $view_mode);
if (!empty($settings_form)) {
$form['displays']['settings'][$name] = array(
'#type' => 'fieldset',
'#title' => $formatter['label'],
'#parents' => array('displays', $name, 'settings'),
'#group' => 'display_settings',
'#weight' => $formatter['weight'] + $i/1000,
) + $settings_form;
}
}
$i++;
}
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save configuration'));
return $form;
}
/**
* Process file display settings form submissions.
*/
function file_entity_file_display_form_submit($form, &$form_state) {
$displays = isset($form_state['values']['displays']) ? $form_state['values']['displays'] : array();
$file_displays = variable_get('file_displays');
$file_displays[$form['#file_type']][$form['#view_mode']] = $displays;
variable_set('file_displays', $file_displays);
drupal_set_message(t('Your settings have been saved.'));
}
/**
* Returns HTML for a file type label and description for the file type admin overview page.
*/
function theme_file_entity_file_type_overview($variables) {
return check_plain($variables['label']) . '<div class="description">' . $variables['description'] . '</div>';
}
/**
* Returns HTML for a file display's display order table.
*/
function theme_file_entity_file_display_order($variables) {
$element = $variables['element'];
$rows = array();
foreach (element_children($element, TRUE) as $name) {
$element[$name]['weight']['#attributes']['class'][] = 'file-display-order-weight';
$rows[] = array(
'data' => array(
drupal_render($element[$name]['label']),
drupal_render($element[$name]['weight']),
),
'class' => array('draggable'),
);
}
$output = drupal_render_children($element);
$output .= theme('table', array('rows' => $rows, 'attributes' => array('id' => 'file-displays-order')));
drupal_add_tabledrag('file-displays-order', 'order', 'sibling', 'file-display-order-weight', NULL, NULL, TRUE);
return $output;
}
(function ($) {
Drupal.behaviors.fileDisplayStatus = {
attach: function (context, settings) {
$('#file-displays-status-wrapper input.form-checkbox', context).once('display-status', function () {
var $checkbox = $(this);
// Retrieve the tabledrag row belonging to this display.
var $row = $('#' + $checkbox.attr('id').replace(/-status$/, '-weight'), context).closest('tr');
// Retrieve the vertical tab belonging to this display.
var tab = $('#' + $checkbox.attr('id').replace(/-status$/, '-settings'), context).data('verticalTab');
// Bind click handler to this checkbox to conditionally show and hide the
// display's tableDrag row and vertical tab pane.
$checkbox.bind('click.displayStatusUpdate', function () {
if ($checkbox.is(':checked')) {
$row.show();
if (tab) {
tab.tabShow().updateSummary();
}
}
else {
$row.hide();
if (tab) {
tab.tabHide().updateSummary();
}
}
// Restripe table after toggling visibility of table row.
Drupal.tableDrag['file-displays-order'].restripeTable();
});
// Attach summary for configurable displays (only for screen-readers).
if (tab) {
tab.fieldset.drupalSetSummary(function (tabContext) {
return $checkbox.is(':checked') ? Drupal.t('Enabled') : Drupal.t('Disabled');
});
}
// Trigger our bound click handler to update elements to initial state.
$checkbox.triggerHandler('click.displayStatusUpdate');
});
}
};
})(jQuery);
<?php
/**
* @file
* Hooks provided by the File Entity module.
*/
/**
* Define file types.
*
* @return
* An array whose keys are file type names and whose values are arrays
* describing the file type, with the following key/value pairs:
* - label: The human-readable name of the file type.
* - claim callback: The name of the function that returns if a given file is
* of this type. See hook_file_type_TYPE_claim() for details.
* - default view callback: (optional) The name of the function that returns a
* drupal_render() array for displaying the file. Used when there are no
* administrator configured file formatters, or none of the configured ones
* return a display. See hook_file_type_TYPE_default_view() for details.
* - description: (optional) A short description of the file type.
* - weight: (optional) A number defining the order in which the 'claim
* callback' function for this type is called relative to the claim
* callbacks of other defined types, when the type of a file needs to be
* determined. The type with the lowest weighted claim callback to return
* TRUE is assigned to the file. Also, on administrative pages listing file
* types, the types are ordered by weight.
* - admin: (optional) An array of information, to be added to the
* ['bundles'][TYPE]['admin'] entry for the 'file' entity type, thereby
* controlling the path at which Field UI pages are attached for this file
* type, and which users may access them. Defaults to attaching the Field UI
* pages to the admin/config/media/file-types/manage/TYPE path and requiring
* 'administer site configuration' permission. See hook_entity_info() for
* details about this array. This value can also be set to NULL to suppress
* Field UI pages from attaching at all for this file type.
*
* @see hook_file_type_info_alter()
*/
function hook_file_type_info() {
return array(
'image' => array(
'label' => t('Image'),
),
);
}
/**
* Perform alterations on file types.
*
* @param $info
* Array of information on file types exposed by hook_file_type_info()
* implementations.
*/
function hook_file_type_info_alter(&$info) {
// @todo Add example.
}
/**
* @todo Add documentation.
*
* Note: This is not really a hook. The function name is manually specified via
* 'claim callback' in hook_file_type_info(), with this recommended
* callback name pattern.
*/
function hook_file_type_TYPE_claim($file, $type) {
}
/**
* @todo Add documentation.
*
* Note: This is not really a hook. The function name is manually specified via
* 'default view callback' in hook_file_type_info(), with this recommended
* callback name pattern.
*/
function hook_file_type_TYPE_default_view($file, $view_mode, $langcode) {
}
/**
* Define file formatters.
*
* @return
* An array whose keys are file formatter names and whose values are arrays
* describing the formatter.
*
* @todo Document key/value pairs that comprise a formatter.
*
* @see hook_file_formatter_info_alter()
*/
function hook_file_formatter_info() {
// @todo Add example.
}
/**
* Perform alterations on file formatters.
*
* @param $info
* Array of information on file formatters exposed by
* hook_file_formatter_info() implementations.
*/
function hook_file_formatter_info_alter(&$info) {
// @todo Add example.
}
/**
* @todo Add documentation.
*
* Note: This is not really a hook. The function name is manually specified via
* 'view callback' in hook_file_formatter_info(), with this recommended callback
* name pattern.
*/
function hook_file_formatter_FORMATTER_view($file, $display, $langcode) {
}
/**
* @todo Add documentation.
*
* Note: This is not really a hook. The function name is manually specified via
* 'settings callback' in hook_file_formatter_info(), with this recommended
* callback name pattern.
*/
function hook_file_formatter_FORMATTER_settings($form, &$form_state, $settings) {
}
/**
* @todo Add documentation.
*/
function hook_file_displays_alter($displays, $file, $view_mode) {
}
/**
* @todo Add documentation.
*/
function hook_file_view($file, $view_mode, $langcode) {
}
/**
* @todo Add documentation.
*/
function hook_file_view_alter($build, $type) {
}
This diff is collapsed.
name = "File Entity"
description = "Extends Drupal file entities to be fieldable and viewable."
package = Media
core = 7.x
dependencies[] = field
<?php
/**
* @file
*
* Extends the {file_managed} table with a 'type' column.
*/
/**
* Implement hook_schema_alter().
*/
function file_entity_schema_alter(&$schema) {
$schema['file_managed']['fields']['type'] = array(
'description' => 'The type of this file.',
'type' => 'varchar',
'length' => 50,
'not null' => TRUE,
'default' => '',
);
$schema['file_managed']['indexes']['file_type'] = array('type');
}
/**
* Implements hook_install().
*/
function file_entity_install() {
$schema = array();
file_entity_schema_alter($schema);
$spec = $schema['file_managed']['fields']['type'];
$indexes_new = array('indexes' => $schema['file_managed']['indexes']);
// If another module (e.g., Media) had added a {file_managed}.type field,
// then change it to the expected specification. Otherwise, add the field.
if (db_field_exists('file_managed', 'type')) {
// db_change_field() will fail if any records have type=NULL, so update
// them to the new default value.
db_update('file_managed')->fields(array('type' => $spec['default']))->isNull('type')->execute();
// Indexes using a field being changed must be dropped prior to calling
// db_change_field(). However, the database API doesn't provide a way to do
// this without knowing what the old indexes are. Therefore, it is the
// responsibility of the module that added them to drop them prior to
// allowing this module to be installed.
db_change_field('file_managed', 'type', 'type', $spec, $indexes_new);
}
else {
db_add_field('file_managed', 'type', $spec, $indexes_new);
}
}
/**
* Implement hook_uninstall().
*/
function file_entity_uninstall() {
db_drop_field('file_managed', 'type');
}
This diff is collapsed.
name = "File Entity Test"
description = "Support module for File Entity tests."
package = Testing
core = 7.x
dependencies[] = file_entity
;hidden=TRUE
<?php
/**
* @file
* File Entity Test
*/
/**
* Implements hook_menu().
*/
function file_entity_test_menu() {
$items = array();
$items['file-entity-test/file/add'] = array(
'title' => 'Add file',
'page callback' => 'drupal_get_form',
'page arguments' => array('file_entity_test_add_form'),
'access arguments' => array('administer site configuration'),
'file' => 'file_entity_test.pages.inc',
);
$items['file-entity-test/file/%file'] = array(
'title' => 'View file',
'page callback' => 'file_entity_test_view_page',
'page arguments' => array(2),
'access arguments' => array('administer site configuration'),
'file' => 'file_entity_test.pages.inc',
);
$items['file-entity-test/file/%file/view'] = array(
'title' => 'View',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => -10,
);
$items['file-entity-test/file/%file/preview'] = array(
'title' => 'Preview',
'page callback' => 'file_entity_test_preview_page',
'page arguments' => array(2),
'access arguments' => array('administer site configuration'),
'weight' => 0,
'type' => MENU_LOCAL_TASK,
'file' => 'file_entity_test.pages.inc',
);
$items['file-entity-test/file/%file/edit'] = array(
'title' => 'Edit',
'page callback' => 'drupal_get_form',
'page arguments' => array('file_entity_test_edit_form', 2),
'access arguments' => array('administer site configuration'),
'weight' => 1,
'type' => MENU_LOCAL_TASK,
'file' => 'file_entity_test.pages.inc',
);