You are here

file_entity.module in File Entity (fieldable files) 8.2

Same filename and directory in other branches
  1. 7.3 file_entity.module
  2. 7 file_entity.module
  3. 7.2 file_entity.module

Extends Drupal file entities to be fieldable and viewable.

File

file_entity.module
View source
<?php

/**
 * @file
 * Extends Drupal file entities to be fieldable and viewable.
 */
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\StreamWrapper\StreamWrapperInterface;
use Drupal\file\Entity\File;
use Drupal\file\FileInterface;
use Drupal\file_entity\Entity\FileEntity;
use Drupal\file_entity\Entity\FileType;

/**
 * The {file_managed}.type value when the file type has not yet been determined.
 */
define('FILE_TYPE_NONE', 'undefined');

/**
 * Implements hook_hook_info().
 */
function file_entity_hook_info() {
  $hooks = array(
    'file_operations',
    'file_type_info',
    'file_type_info_alter',
    'file_view',
    'file_view_alter',
    'file_type',
    'file_type_alter',
    'file_download_headers_alter',
  );
  return array_fill_keys($hooks, array(
    'group' => 'file',
  ));
}

/**
 * Implements hook_hook_info_alter().
 *
 * Add support for existing core hooks to be located in modulename.file.inc.
 */
function file_entity_hook_info_alter(&$info) {
  $hooks = array(
    // File API hooks
    'file_copy',
    'file_move',
    'file_validate',
    // File access
    'file_download',
    'file_download_access',
    'file_download_access_alter',
    // File entity hooks
    'file_load',
    'file_presave',
    'file_insert',
    'file_update',
    'file_delete',
    // Miscellaneous hooks
    'file_mimetype_mapping_alter',
    'file_url_alter',
  );
  $info += array_fill_keys($hooks, array(
    'group' => 'file',
  ));
}

/**
 * Implements hook_help().
 */
function file_entity_help($route_name, RouteMatchInterface $route_match) {
  switch ($route_match) {
    case 'entity.file_type.collection':
      $output = '<p>' . t('When a file is uploaded to this website, it is assigned one of the following types, based on what kind of file it is.') . '</p>';
      return $output;
  }
}

/**
 * Implements hook_action_info_alter().
 */
function file_entity_action_info_alter(&$actions) {
  if (\Drupal::moduleHandler()
    ->moduleExists('pathauto')) {
    $actions['pathauto_file_update_action'] = array(
      'type' => 'file',
      'label' => t('Update file alias'),
      'configurable' => FALSE,
    );
  }
}

/**
 * Implements hook_field_formatter_info_alter().
 */
function file_entity_field_formatter_info_alter(array &$info) {

  // Make the entity reference view formatter available for files and images.
  if (!empty($info['entity_reference_entity_view'])) {
    $info['entity_reference_entity_view']['field_types'][] = 'file';
    $info['entity_reference_entity_view']['field_types'][] = 'image';
  }

  // Add descriptions to core formatters.
  $descriptions = array(
    'file_default' => t('Create a simple link to the file. The link is prefixed by a file type icon and the name of the file is used as the link text.'),
    'file_table' => t('Build a two-column table where the first column contains a generic link to the file and the second column displays the size of the file.'),
    'file_url_plain' => t('Display a plain text URL to the file.'),
    'image' => t('Format the file as an image. The image can be displayed using an image style and can optionally be linked to the image file itself or its parent content.'),
  );
  foreach ($descriptions as $key => $description) {
    if (isset($info[$key]) && empty($info[$key]['description'])) {
      $info[$key]['description'] = $description;
    }
  }
}

/**
 * Implements hook_theme().
 */
function file_entity_theme() {
  return array(
    'file' => array(
      'render element' => 'elements',
      'template' => 'file',
    ),
    'file_entity_file_link' => array(
      'variables' => array(
        'file' => NULL,
        'icon_directory' => NULL,
      ),
      'file' => 'file_entity.theme.inc',
    ),
    'file_entity_download_link' => array(
      'variables' => array(
        'file' => NULL,
        'download_link' => NULL,
        'icon' => '',
        'file_size' => NULL,
        'attributes' => NULL,
      ),
    ),
    'file_entity_audio' => array(
      'variables' => array(
        'files' => array(),
        'attributes' => NULL,
      ),
    ),
    'file_entity_video' => array(
      'variables' => array(
        'files' => array(),
        'attributes' => NULL,
      ),
    ),
  );
}

/**
 * Implements hook_entity_info_alter().
 *
 * Extends the core file entity to be fieldable. The file type is used as the
 * bundle key.
 */
function file_entity_entity_type_alter(&$entity_types) {

  /** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
  $keys = $entity_types['file']
    ->getKeys();
  $keys['bundle'] = 'type';
  $entity_types['file']
    ->set('entity_keys', $keys)
    ->set('bundle_entity_type', 'file_type')
    ->set('admin_permission', 'administer files')
    ->setClass('Drupal\\file_entity\\Entity\\FileEntity')
    ->setFormClass('default', 'Drupal\\file_entity\\Form\\FileEditForm')
    ->setFormClass('edit', 'Drupal\\file_entity\\Form\\FileEditForm')
    ->setFormClass('inline_edit', 'Drupal\\file_entity\\Form\\FileInlineEditForm')
    ->setFormClass('delete', 'Drupal\\Core\\Entity\\ContentEntityDeleteForm')
    ->setAccessClass('Drupal\\file_entity\\FileEntityAccessControlHandler')
    ->set('field_ui_base_route', 'entity.file_type.edit_form')
    ->setLinkTemplate('canonical', '/file/{file}')
    ->setLinkTemplate('collection', '/admin/content/files')
    ->setLinkTemplate('edit-form', '/file/{file}/edit')
    ->setLinkTemplate('delete-form', '/file/{file}/delete')
    ->setLinkTemplate('inline-edit-form', '/file/{file}/inline-edit')
    ->setViewBuilderClass('Drupal\\file_entity\\Entity\\FileEntityViewBuilder')
    ->setListBuilderClass('Drupal\\Core\\Entity\\EntityListBuilder');

  /*$entity_types['file']['view modes']['teaser'] = array(
      'label' => t('Teaser'),
      'custom settings' => TRUE,
    );
    $entity_types['file']['view modes']['full'] = array(
      'label' => t('Full content'),
      'custom settings' => FALSE,
    );
    $entity_types['file']['view modes']['preview'] = array(
      'label' => t('Preview'),
      'custom settings' => TRUE,
    );
    $entity_types['file']['view modes']['rss'] = array(
      'label' => t('RSS'),
      'custom settings' => FALSE,
    );*/

  // Enable Metatag support.

  //$entity_types['file']['metatags'] = TRUE;
}

/**
 * Implements hook_entity_operation().
 */
function file_entity_entity_operation(EntityInterface $entity) {
  $operations = [];
  if ($entity instanceof FileEntity && $entity
    ->access('download')) {
    $operations['download'] = array(
      'title' => t('Download'),
      'weight' => 100,
      'url' => $entity
        ->downloadUrl(),
    );
  }
  return $operations;
}

/**
 * Prepares variables for file templates.
 *
 * Default template: file.html.twig.
 *
 * @param array $variables
 *   An associative array containing:
 *   - elements: An array of elements to display in view mode.
 *   - file: The file object.
 */
function template_preprocess_file(&$variables) {
  $view_mode = $variables['view_mode'] = $variables['elements']['#view_mode'];
  $variables['file'] = $variables['elements']['#file'];

  /** @var FileInterface $file */
  $file = $variables['file'];
  $variables['id'] = $file
    ->id();
  $variables['date'] = \Drupal::service('date.formatter')
    ->format($file
    ->getCreatedTime());
  $username = array(
    '#theme' => 'username',
    '#account' => $file
      ->getOwner(),
    '#link_options' => array(
      'attributes' => array(
        'rel' => 'author',
      ),
    ),
  );
  $variables['name'] = $username;
  $variables['file_url'] = $file
    ->toUrl('canonical')
    ->toString();
  $variables['label'] = $file
    ->label();
  $variables['page'] = $view_mode == 'full' && $file
    ->isPage();

  // Hide the file name from being displayed until we can figure out a better
  // way to control this. We cannot simply not output the title since
  // contextual links require $title_suffix to be output in the template.
  // @see http://drupal.org/node/1245266
  if (!$variables['page']) {
    $variables['title_attributes_array']['class'][] = 'element-invisible';
  }

  // Helpful $content variable for templates.
  $variables += array(
    'content' => array(),
  );
  foreach (\Drupal\Core\Render\Element::children($variables['elements']) as $key) {
    $variables['content'][$key] = $variables['elements'][$key];
  }

  // Attach the file object to the content element.
  $variables['content']['file']['#file'] = $file;
}

/**
 * Implements hook_theme_suggestions_HOOK_alter().
 */
function file_entity_theme_suggestions_file_alter(array &$suggestions, array $variables) {
  $view_mode = $variables['view_mode'] = $variables['elements']['#view_mode'];

  /** @var FileInterface $file */
  $file = $variables['elements']['#file'];

  // Clean up name so there are no underscores.
  $suggestions[] = 'file__' . $file
    ->bundle();
  $suggestions[] = 'file__' . $file
    ->bundle() . '__' . $view_mode;
  $suggestions[] = 'file__' . str_replace(array(
    '/',
    '-',
  ), array(
    '__',
    '_',
  ), $file
    ->getMimeType());
  $suggestions[] = 'file__' . str_replace(array(
    '/',
    '-',
  ), array(
    '__',
    '_',
  ), $file
    ->getMimeType()) . '__' . $view_mode;
  $suggestions[] = 'file__' . $file
    ->id();
  $suggestions[] = 'file__' . $file
    ->id() . '__' . $view_mode;
}

/**
 * Returns a list of available file type names.
 *
 * @return
 *   An array of file type names, keyed by the type.
 */
function file_entity_type_get_names() {
  $names =& drupal_static(__FUNCTION__);
  if (!isset($names)) {
    foreach (FileType::loadMultiple() as $id => $type) {
      $names[$id] = $type
        ->label();
    }
  }
  return $names;
}

/**
 * Return the label for a specific file entity view mode.
 */
function file_entity_view_mode_label($view_mode, $default = FALSE) {
  $labels = \Drupal::getContainer()
    ->get('entity_display.repository')
    ->getViewModeOptions('file');
  return isset($labels[$view_mode]) ? $labels[$view_mode] : $default;
}

/**
 * Implements hook_file_download().
 */
function file_entity_file_download($uri) {

  // Load the file from the URI.
  $file = file_uri_to_object($uri);

  // An existing file wasn't found, so we don't control access.
  // E.g. image derivatives will fall here.
  if (empty($file)) {
    return NULL;
  }

  // Allow the user to download the file if they have appropriate permissions.
  if ($file
    ->access('view')) {
    return file_get_content_headers($file);
  }
  return -1;
}

/**
 * @name pathauto_file Pathauto integration for the core file module.
 * @{
 */

/**
 * Implements hook_entity_base_field_info().
 */
function file_entity_entity_base_field_info(EntityTypeInterface $entity_type) {

  // @todo: Make this configurable and/or remove if
  //   https://drupal.org/node/476294 is resolved.
  if (\Drupal::moduleHandler()
    ->moduleExists('pathauto') && $entity_type
    ->id() == 'file') {
    $fields = array();
    $fields['path'] = BaseFieldDefinition::create('path')
      ->setCustomStorage(TRUE)
      ->setLabel(t('URL alias'))
      ->setTranslatable(TRUE)
      ->setProvider('file_entity')
      ->setDisplayOptions('form', array(
      'type' => 'path',
      'weight' => 30,
    ))
      ->setDisplayConfigurable('form', TRUE);
    return $fields;
  }
}

/**
 * @} End of "name pathauto_file".
 */

/**
 * Checks if pattern(s) match mimetype(s).
 */
function file_entity_match_mimetypes($needle, $haystack) {
  $needle = is_array($needle) ? $needle : array(
    $needle,
  );
  $haystack = is_array($haystack) ? $haystack : array(
    $haystack,
  );
  foreach ($haystack as $mimetype) {
    foreach ($needle as $search) {
      if (fnmatch($search, $mimetype) || fnmatch($mimetype, $search)) {
        return TRUE;
      }
    }
  }
  return FALSE;
}

/**
 * Implements hook_admin_menu_map().
 */
function file_entity_admin_menu_map() {
  if (!user_access('administer file types')) {
    return;
  }
  $map['admin/structure/file-types/manage/%file_type'] = array(
    'parent' => 'admin/structure/file-types',
    'arguments' => array(
      array(
        '%file_type' => array_keys(file_entity_type_get_names()),
      ),
    ),
  );
  return $map;
}

/**
 * Implements hook_entity_storage_load().
 */
function file_entity_entity_storage_load($entities, $entity_type) {
  $token_service = \Drupal::token();
  $replace_options = [
    'clear' => TRUE,
    'sanitize' => FALSE,
  ];
  $config = \Drupal::config('file_entity.settings');

  // Loop over all the entities looking for entities with attached images.
  foreach ($entities as $entity) {

    // Skip non-fieldable entities.
    if (!$entity instanceof FieldableEntityInterface) {
      continue;
    }

    /** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */

    // Examine every image field instance attached to this entity's bundle.
    foreach ($entity
      ->getFieldDefinitions() as $field_definition) {
      if ($field_definition
        ->getSetting('target_type') == 'file' && $field_definition
        ->getType() != 'image') {
        $field_name = $field_definition
          ->getName();
        if (!empty($entity->{$field_name})) {
          foreach ($entity->{$field_name} as $delta => $item) {

            // If alt and title text is not specified, fall back to alt and
            // title text on the file.
            if (!empty($item->target_id) && (empty($item->alt) || empty($item->title))) {
              foreach ([
                'alt',
                'title',
              ] as $key) {
                if (empty($item->{$key})) {
                  $token_bubbleable_metadata = new BubbleableMetadata();
                  $item->{$key} = $token_service
                    ->replace($config
                    ->get($key), [
                    'file' => $item->entity,
                  ], $replace_options, $token_bubbleable_metadata);

                  // Add the cacheability metadata of the token to the entity.
                  // This means attachments are discarded, but it does not ever
                  // make sense to have attachments for an image's "alt" and
                  // "title"attribute anyway, so this is acceptable.
                  $entity
                    ->addCacheableDependency($token_bubbleable_metadata);
                }
              }
            }
          }
        }
      }
    }
  }
}
function file_entity_get_public_and_private_stream_wrapper_names($flag = StreamWrapperInterface::VISIBLE) {
  $wrappers = array(
    'public' => [],
    'private' => [],
  );

  // @todo Make the set of private schemes/stream wrappers extendable.
  $private_schemes = [
    'private',
    'temporary',
  ];
  foreach (\Drupal::service('stream_wrapper_manager')
    ->getWrappers($flag) as $key => $wrapper) {

    // Some wrappers, e.g. those set in KernelTestBase, do not provide a name.
    $wrapper_name = isset($wrapper['name']) ? $wrapper['name'] : substr(strrchr($wrapper['class'], '\\'), 1);
    if (in_array($key, $private_schemes)) {
      $wrappers['private'][$key] = $wrapper_name;
    }
    else {
      $wrappers['public'][$key] = $wrapper_name;
    }
  }
  return $wrappers;
}

/**
 * Returns a file object which can be passed to file_save().
 *
 * @param string $uri
 *   A string containing the URI, path, or filename.
 * @param bool $use_existing
 *   (Optional) If TRUE and there's an existing file in the {file_managed}
 *   table with the passed in URI, then that file object is returned.
 *   Otherwise, a new file object is returned. Default is TRUE.
 *
 * @return FileInterface|bool
 *   A file object, or FALSE on error.
 *
 * @todo This should probably be named
 *   file_load_by_uri($uri, $create_if_not_exists).
 * @todo Remove this function when http://drupal.org/node/685818 is fixed.
 */
function file_uri_to_object($uri, $use_existing = TRUE) {
  $file = FALSE;
  $uri = \Drupal::service('stream_wrapper_manager')
    ->normalizeUri($uri);
  if ($use_existing) {

    // We should always attempt to re-use a file if possible.
    $files = \Drupal::entityTypeManager()
      ->getStorage('file')
      ->loadByProperties([
      'uri' => $uri,
    ]);
    $file = !empty($files) ? reset($files) : FALSE;
  }
  if (empty($file)) {
    $file = File::create(array(
      'uid' => \Drupal::currentUser()
        ->id(),
      'uri' => $uri,
      'status' => FILE_STATUS_PERMANENT,
    ));
  }
  return $file;
}

/**
 * Implements hook_preprocess_responsive_image_formatter().
 */
function file_entity_preprocess_responsive_image_formatter(&$variables) {
  if (empty($variables['responsive_image']['#width']) || empty($variables['responsive_image']['#height'])) {
    foreach ([
      'width',
      'height',
    ] as $key) {
      $variables['responsive_image']["#{$key}"] = $variables['item']->entity
        ->getMetadata($key);
    }
  }
}

/**
 * Implements hook_preprocess_image_formatter().
 */
function file_entity_preprocess_image_formatter(&$variables) {
  if (empty($variables['image']['#width']) || empty($variables['image']['#height'])) {
    foreach ([
      'width',
      'height',
    ] as $key) {
      $variables['image']["#{$key}"] = $variables['item']->entity
        ->getMetadata($key);
    }
  }
}

Functions

Namesort descending Description
file_entity_action_info_alter Implements hook_action_info_alter().
file_entity_admin_menu_map Implements hook_admin_menu_map().
file_entity_entity_base_field_info Implements hook_entity_base_field_info().
file_entity_entity_operation Implements hook_entity_operation().
file_entity_entity_storage_load Implements hook_entity_storage_load().
file_entity_entity_type_alter Implements hook_entity_info_alter().
file_entity_field_formatter_info_alter Implements hook_field_formatter_info_alter().
file_entity_file_download Implements hook_file_download().
file_entity_get_public_and_private_stream_wrapper_names
file_entity_help Implements hook_help().
file_entity_hook_info Implements hook_hook_info().
file_entity_hook_info_alter Implements hook_hook_info_alter().
file_entity_match_mimetypes Checks if pattern(s) match mimetype(s).
file_entity_preprocess_image_formatter Implements hook_preprocess_image_formatter().
file_entity_preprocess_responsive_image_formatter Implements hook_preprocess_responsive_image_formatter().
file_entity_theme Implements hook_theme().
file_entity_theme_suggestions_file_alter Implements hook_theme_suggestions_HOOK_alter().
file_entity_type_get_names Returns a list of available file type names.
file_entity_view_mode_label Return the label for a specific file entity view mode.
file_uri_to_object Returns a file object which can be passed to file_save().
template_preprocess_file Prepares variables for file templates.

Constants

Namesort descending Description
FILE_TYPE_NONE The {file_managed}.type value when the file type has not yet been determined.