Skip to content
file_entity.module 64.8 KiB
Newer Older
<?php

/**
 * @file
 * Extends Drupal file entities to be fieldable and viewable.
 */

/**
 * Modules should return this value from hook_file_entity_access() to allow
 * access to a file.
 */
define('FILE_ENTITY_ACCESS_ALLOW', 'allow');

/**
 * Modules should return this value from hook_file_entity_access() to deny
 * access to a file.
 */
define('FILE_ENTITY_ACCESS_DENY', 'deny');

/**
 * Modules should return this value from hook_file_entity_access() to not affect
 * file access.
 */
define('FILE_ENTITY_ACCESS_IGNORE', NULL);
/**
 * As part of extending Drupal core's file entity API, this module adds some
 * functions to the 'file' namespace. For organization, those are kept in the
 * 'file_entity.file_api.inc' file.
 */
require_once dirname(__FILE__) . '/file_entity.file_api.inc';

// @todo Remove when http://drupal.org/node/977052 is fixed.
require_once dirname(__FILE__) . '/file_entity.field.inc';

/**
 * Implements hook_hook_info().
 */
function file_entity_hook_info() {
  $hooks = array(
    'file_operation_info',
    'file_operation_info_alter',
    'file_type_info',
    'file_type_info_alter',
    'file_formatter_info',
    'file_formatter_info_alter',
    'file_view',
    'file_view_alter',
    'file_displays_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',
    // Miscellanious hooks
    'file_mimetype_mapping_alter',
    'file_url_alter',
  );
  $info += array_fill_keys($hooks, array('group' => 'file'));
}

/**
 * Implements hook_help().
 */
function file_entity_help($path, $arg) {
  switch ($path) {
      $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;
    case 'admin/structure/file-types/manage/%/display/preview':
    case 'admin/structure/file-types/manage/%/file-display/preview':
      drupal_set_message(t('Some modules rely on the Preview view mode to function correctly. Changing these settings may break parts of your site.'), 'warning');
      break;
  }
}

/**
 * Implements hook_menu().
 */
function file_entity_menu() {
  // @todo Move this back to admin/config/media/file-types in Drupal 8 if
  // MENU_MAX_DEPTH is increased to a value higher than 9.
  $items['admin/structure/file-types'] = array(
    'description' => 'Manage settings for the type of files used on your site.',
    'page callback' => 'file_entity_list_types_page',
    'access arguments' => array('administer file types'),
  $items['admin/structure/file-types/manage/%file_type'] = array(
    'description' => 'Manage settings for the type of files used on your site.',
  );
  $items['admin/content/file'] = array(
    'title' => 'Files',
    'description' => 'Manage files used on your site.',
    'page arguments' => array('file_entity_admin_file'),
    'access arguments' => array('administer files'),
    'type' => MENU_LOCAL_TASK | MENU_NORMAL_ITEM,
    'file' => 'file_entity.admin.inc',
  );
  $items['admin/content/file/list'] = array(
    'title' => 'List',
    'type' => MENU_DEFAULT_LOCAL_TASK,
  $items['file/add'] = array(
    'title' => 'Add file',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('file_entity_add_upload', array()),
    'access callback' => 'file_entity_access',
    'access arguments' => array('create'),
    'file' => 'file_entity.pages.inc',
  );
  if (module_exists('plupload') && module_exists('multiform')) {
    $items['file/add']['page arguments'] = array('file_entity_add_upload_multiple');
  }
  $items['file/add/upload'] = array(
    'title' => 'Upload',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
    'title callback' => 'entity_label',
    'title arguments' => array('file', 1),
    // The page callback also invokes drupal_set_title() in case
    // the menu router's title is overridden by a menu link.
    'page callback' => 'file_entity_view_page',
    'page arguments' => array(1),
    'access callback' => 'file_entity_access',
    'file' => 'file_entity.pages.inc',
  );
  $items['file/%file/view'] = array(
    'title' => 'View',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
  $items['file/%file/usage'] = array(
    'title' => 'Usage',
    'page callback' => 'file_entity_usage_page',
    'page arguments' => array(1),
    'access callback' => 'file_entity_access',
    'access arguments' => array('update', 1),
    'type' => MENU_LOCAL_TASK,
    'context' => MENU_CONTEXT_PAGE,
    'file' => 'file_entity.pages.inc',
  );
  $items['file/%file/download'] = array(
    'title' => 'Download',
    'page callback' => 'file_entity_download_page',
    'page arguments' => array(1),
    'access callback' => 'file_entity_access',
    'access arguments' => array('download', 1),
    'file' => 'file_entity.pages.inc',
    'type' => MENU_CALLBACK,
  );
  $items['file/%file/edit'] = array(
    'title' => 'Edit',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('file_entity_edit', 1),
    'weight' => 0,
    'type' => MENU_LOCAL_TASK,
    'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
    'file' => 'file_entity.pages.inc',
  );
  $items['file/%file/delete'] = array(
    'title' => 'Delete',
    'page callback' => 'drupal_get_form',
    'page arguments'  => array('file_entity_delete_form', 1),
    'weight' => 1,
    'type' => MENU_LOCAL_TASK,
    'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
    'file' => 'file_entity.pages.inc',
  );

  // Attach a "Manage file display" tab to each file type in the same way that
  // Field UI attaches "Manage fields" and "Manage display" tabs. Note that
  // Field UI does not have to be enabled; we're just using the same IA pattern
  // here for attaching the "Manage file display" page.
  $entity_info = entity_get_info('file');
  foreach ($entity_info['bundles'] as $file_type => $bundle_info) {
    if (isset($bundle_info['admin'])) {
      // Get the base path and access.
      $path = $bundle_info['admin']['path'];
      $access = array_intersect_key($bundle_info['admin'], drupal_map_assoc(array('access callback', 'access arguments')));
      $access += array(
        'access callback' => 'user_access',
        'access arguments' => array('administer file types'),
      );

      // The file type must be passed to the page callbacks. It might be
      // configured as a wildcard (multiple file types sharing the same menu
      // router path).
      $file_type_argument = isset($bundle_info['admin']['bundle argument']) ? $bundle_info['admin']['bundle argument'] : $file_type;

      $items[$path] = array(
        'title' => 'Edit file type',
        'title arguments' => array(4),
        'page callback' => 'drupal_get_form',
        'page arguments' => array('file_entity_file_type_form', $file_type_argument),
        'file' => 'file_entity.admin.inc',
      ) + $access;

      // Add the 'File type settings' tab.
      $items["$path/edit"] = array(
        'title' => 'Edit',
        'type' => MENU_DEFAULT_LOCAL_TASK,
      );

      // Add the 'Manage file display' tab.
      $items["$path/file-display"] = array(
        'title' => 'Manage file display',
        'page callback' => 'drupal_get_form',
        'page arguments' => array('file_entity_file_display_form', $file_type_argument, 'default'),
        'type' => MENU_LOCAL_TASK,
        'weight' => 3,
        'file' => 'file_entity.admin.inc',
      ) + $access;

      // Add a secondary tab for each view mode.
      $weight = 0;
      $view_modes = array('default' => array('label' => t('Default'))) + $entity_info['view modes'];
      foreach ($view_modes as $view_mode => $view_mode_info) {
        $items["$path/file-display/$view_mode"] = array(
          'title' => $view_mode_info['label'],
          'page arguments' => array('file_entity_file_display_form', $file_type_argument, $view_mode),
          'type' => ($view_mode == 'default' ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK),
          'weight' => ($view_mode == 'default' ? -10 : $weight++),
          'file' => 'file_entity.admin.inc',
          // View modes for which the 'custom settings' flag isn't TRUE are
          // disabled via this access callback. This needs to extend, rather
          // than override normal $access rules.
          'access callback' => '_file_entity_view_mode_menu_access',
          'access arguments' => array_merge(array($file_type_argument, $view_mode, $access['access callback']), $access['access arguments']),
        );
      }
    }
  }

  // Optional devel module integration
  if (module_exists('devel')) {
    $items['file/%file/devel'] = array(
      'title' => 'Devel',
      'page callback' => 'devel_load_object',
      'page arguments' => array('file', 1),
      'access arguments' => array('access devel information'),
      'type' => MENU_LOCAL_TASK,
      'file' => 'devel.pages.inc',
      'file path' => drupal_get_path('module', 'devel'),
      'weight' => 100,
    );
    $items['file/%file/devel/load'] = array(
      'title' => 'Load',
      'type' => MENU_DEFAULT_LOCAL_TASK,
    );
    $items['file/%file/devel/render'] = array(
      'title' => 'Render',
      'page callback' => 'devel_render_object',
      'page arguments' => array('file', 1),
      'access arguments' => array('access devel information'),
      'file' => 'devel.pages.inc',
      'file path' => drupal_get_path('module', 'devel'),
      'type' => MENU_LOCAL_TASK,
      'weight' => 100,
    );
    if (module_exists('token')) {
      $items['file/%file/devel/token'] = array(
        'title' => 'Tokens',
        'page callback' => 'token_devel_token_object',
        'page arguments' => array('file', 1),
        'access arguments' => array('access devel information'),
        'type' => MENU_LOCAL_TASK,
        'file' => 'token.pages.inc',
        'file path' => drupal_get_path('module', 'token'),
        'weight' => 5,
      );
    }
/**
 * Implements hook_menu_local_tasks_alter().
 */
function file_entity_menu_local_tasks_alter(&$data, $router_item, $root_path) {
  // Add action link to 'file/add' on 'admin/content/file' page.
  if ($root_path == 'admin/content/file') {
    $item = menu_get_item('file/add');
    if (!empty($item['access'])) {
      $data['actions']['output'][] = array(
        '#theme' => 'menu_local_action',
        '#link' => $item,
        '#weight' => $item['weight'],
      );
    }
  }
}

/**
 * Implement hook_permission().
 */
function file_entity_permission() {
  $permissions = array(
    'bypass file access' => array(
      'title' => t('Bypass file access control'),
      'description' => t('View, edit and delete all files regardless of permission restrictions.'),
      'restrict access' => TRUE,
    ),
    'administer file types' => array(
      'title' => t('Administer file types'),
      'restrict access' => TRUE,
    ),
    'administer files' => array(
      'title' => t('Administer files'),
      'restrict access' => TRUE,
    'create files' => array(
      'title' => t('Add and upload new files'),
      'title' => t('View own private files'),
    'view own files' => array(
      'title' => t('View own files'),
    'view private files' => array(
      'title' => t('View private files'),
    'view files' => array(
      'title' => t('View files'),
    'edit own files' => array(
      'title' => t('Edit own files'),
    ),
    'edit any files' => array(
      'title' => t('Edit any files'),
    ),
    'delete own files' => array(
      'title' => t('Delete own files'),
    ),
    'delete any files' => array(
      'title' => t('Delete any files'),
    'download own files' => array(
      'title' => t('Download own files'),
    ),
    'download any files' => array(
      'title' => t('Download any files'),
    ),

  // Add description for the 'View file details' and 'View own private file
  // details' permissions to show which stream wrappers they apply to.
  $wrappers = array();
  foreach (file_get_stream_wrappers(STREAM_WRAPPERS_VISIBLE) as $key => $wrapper) {
    if (empty($wrapper['private'])) {
      $wrappers['public'][$key] = $wrapper['name'];
    }
    else {
      $wrappers['private'][$key] = $wrapper['name'];
    }
  }
  $wrappers += array('public' => array(t('None')), 'private' => array(t('None')));

  $permissions['view files']['description'] = t('Includes the following stream wrappers: %wrappers.', array('%wrappers' => implode(', ', $wrappers['public'])));
  $permissions['view own private files']['description'] = t('Includes the following stream wrappers: %wrappers.', array('%wrappers' => implode(', ', $wrappers['private'])));
/**
 * Gather the rankings from the the hook_ranking implementations.
 *
 * @param $query
 *   A query object that has been extended with the Search DB Extender.
 */
function _file_entity_rankings(SelectQueryExtender $query) {
  if ($ranking = module_invoke_all('file_ranking')) {
    $tables = &$query->getTables();
    foreach ($ranking as $rank => $values) {
      if ($file_rank = variable_get('file_entity_rank_' . $rank, 0)) {
        // If the table defined in the ranking isn't already joined, then add it.
        if (isset($values['join']) && !isset($tables[$values['join']['alias']])) {
          $query->addJoin($values['join']['type'], $values['join']['table'], $values['join']['alias'], $values['join']['on']);
        }
        $arguments = isset($values['arguments']) ? $values['arguments'] : array();
        $query->addScore($values['score'], $arguments, $file_rank);
      }
    }
  }
}

/**
 * Implements hook_search_info().
 */
function file_entity_search_info() {
  return array(
    'title' => 'Files',
    'path' => 'file',
  );
}

/**
 * Implements hook_search_access().
 */
function file_entity_search_access() {
  return user_access('view own private files') || user_access('view own files') || user_access('view private files') || user_access('view files');
}

/**
 * Implements hook_search_reset().
 */
function file_entity_search_reset() {
  db_update('search_dataset')
    ->fields(array('reindex' => REQUEST_TIME))
    ->condition('type', 'file')
    ->execute();
}

/**
 * Implements hook_search_status().
 */
function file_entity_search_status() {
  $total = db_query('SELECT COUNT(*) FROM {file_managed}')->fetchField();
  $remaining = db_query("SELECT COUNT(*) FROM {file_managed} fm LEFT JOIN {search_dataset} d ON d.type = 'file' AND d.sid = fm.fid WHERE d.sid IS NULL OR d.reindex <> 0")->fetchField();
  return array('remaining' => $remaining, 'total' => $total);
}

/**
 * Implements hook_search_admin().
 */
function file_entity_search_admin() {
  // Output form for defining rank factor weights.
  $form['file_ranking'] = array(
    '#type' => 'fieldset',
    '#title' => t('File ranking'),
  );
  $form['file_ranking']['#theme'] = 'file_entity_search_admin';
  $form['file_ranking']['info'] = array(
    '#value' => '<em>' . t('The following numbers control which properties the file search should favor when ordering the results. Higher numbers mean more influence, zero means the property is ignored. Changing these numbers does not require the search index to be rebuilt. Changes take effect immediately.') . '</em>'
  );

  // Note: reversed to reflect that higher number = higher ranking.
  $options = drupal_map_assoc(range(0, 10));
  foreach (module_invoke_all('file_ranking') as $var => $values) {
    $form['file_ranking']['factors']['file_entity_rank_' . $var] = array(
      '#title' => $values['title'],
      '#type' => 'select',
      '#options' => $options,
      '#default_value' => variable_get('file_entity_rank_' . $var, 0),
    );
  }
  return $form;
}

/**
 * Implements hook_search_execute().
 */
function file_entity_search_execute($keys = NULL, $conditions = NULL) {
  global $user;

  // Build matching conditions
  $query = db_select('search_index', 'i', array('target' => 'slave'))->extend('SearchQuery')->extend('PagerDefault');
  $query->join('file_managed', 'fm', 'fm.fid = i.sid');
  $query->searchExpression($keys, 'file');

  if (user_access('bypass file access')) {
    // Administrators don't need to be restricted to only permanent files.
    $query->condition('fm.status', FILE_STATUS_PERMANENT);
  }
  elseif (user_access('view files')) {
    // For non-private files, users can view if they have the 'view files'
    // permission.
    $query->condition('fm.status', FILE_STATUS_PERMANENT);
  }
  elseif (user_access('view private files') && user_is_logged_in()) {
    // For private files, users can view private files if the
    // user is not anonymous, and has the 'view private files' permission.
    $query->condition('fm.uri', db_like('private://') . '%', 'LIKE');
    $query->condition('fm.status', FILE_STATUS_PERMANENT);
  }
  elseif (user_access('view own files')) {
    // For non-private files, allow to see if user owns the file.
    $query->condition('fm.uid', $user->uid, '=');
    $query->condition('fm.status', FILE_STATUS_PERMANENT);
  }
  elseif (user_access('view own private files') && user_is_logged_in()) {
    // For private files, users can view their own private files if the
    // user is not anonymous, and has the 'view own private files' permission.
    $query->condition('fm.uri', db_like('private://') . '%', 'LIKE');
    $query->condition('fm.uid', $user->uid, '=');
    $query->condition('fm.status', FILE_STATUS_PERMANENT);
  }

  // Insert special keywords.
  $query->setOption('type', 'fm.type');
  if ($query->setOption('term', 'ti.tid')) {
    $query->join('taxonomy_index', 'ti', 'fm.fid = ti.fid');
  }
  // Only continue if the first pass query matches.
  if (!$query->executeFirstPass()) {
    return array();
  }

  // Add the ranking expressions.
  _file_entity_rankings($query);

  // Load results.
  $find = $query
    ->limit(10)
    ->execute();
  $results = array();
  foreach ($find as $item) {
    // Render the file.
    $file = file_load($item->sid);
    $build = file_view($file, 'search_result');
    unset($build['#theme']);
    $file->rendered = drupal_render($build);

    $extra = module_invoke_all('file_entity_search_result', $file);

    $types = file_entity_type_get_names();

    $uri = entity_uri('file', $file);
    $results[] = array(
      'link' => url($uri['path'], array_merge($uri['options'], array('absolute' => TRUE))),
      'type' => check_plain($types[$file->type]),
      'title' => $file->filename,
      'user' => theme('username', array('account' => user_load($file->uid))),
      'date' => $file->timestamp,
      'file' => $file,
      'extra' => $extra,
      'score' => $item->calculated_score,
      'snippet' => search_excerpt($keys, $file->rendered),
      'language' => function_exists('entity_language') ? entity_language('file', $file) : NULL,
    );
  }
  return $results;
}

/**
 * Implements hook_file_ranking().
 */
function file_entity_file_ranking() {
  // Create the ranking array and add the basic ranking options.
  $ranking = array(
    'relevance' => array(
      'title' => t('Keyword relevance'),
      // Average relevance values hover around 0.15
      'score' => 'i.relevance',
    ),
  );

  // Add relevance based on creation date.
  if ($file_cron_last = variable_get('file_entity_cron_last', 0)) {
    $ranking['timestamp'] = array(
      'title' => t('Recently posted'),
      // Exponential decay with half-life of 6 months, starting at last indexed file
      'score' => 'POW(2.0, (fm.timestamp - :file_cron_last) * 6.43e-8)',
      'arguments' => array(':file_cron_last' => $file_cron_last),
    );
  }
  return $ranking;
}

/**
 * Returns HTML for the file ranking part of the search settings admin page.
 *
 * @param $variables
 *   An associative array containing:
 *   - form: A render element representing the form.
 *
 * @ingroup themeable
 */
function theme_file_entity_search_admin($variables) {
  $form = $variables['form'];

  $output = drupal_render($form['info']);

  $header = array(t('Factor'), t('Weight'));
  foreach (element_children($form['factors']) as $key) {
    $row = array();
    $row[] = $form['factors'][$key]['#title'];
    $form['factors'][$key]['#title_display'] = 'invisible';
    $row[] = drupal_render($form['factors'][$key]);
    $rows[] = $row;
  }
  $output .= theme('table', array('header' => $header, 'rows' => $rows));

  $output .= drupal_render_children($form);
  return $output;
}

/**
 * Implements hook_update_index().
 */
function file_entity_update_index() {
  $limit = (int)variable_get('search_cron_limit', 100);

  $result = db_query_range("SELECT fm.fid FROM {file_managed} fm LEFT JOIN {search_dataset} d ON d.type = 'file' AND d.sid = fm.fid WHERE d.sid IS NULL OR d.reindex <> 0 ORDER BY d.reindex ASC, fm.fid ASC", 0, $limit, array(), array('target' => 'slave'));

  foreach ($result as $file) {
    _file_entity_index_file($file);
  }
}

/**
 * Index a single file.
 *
 * @param $file
 *   The file to index.
 */
function _file_entity_index_file($file) {
  $file = file_load($file->fid);

  // Save the creation time of the most recent indexed file, for the search
  // results half-life calculation.
  variable_set('file_entity_cron_last', $file->timestamp);

  // Render the file.
  $build = file_view($file, 'search_index');
  unset($build['#theme']);
  $file->rendered = drupal_render($build);

  $text = '<h1>' . check_plain($file->filename) . '</h1>' . $file->rendered;

  // Fetch extra data normally not visible
  $extra = module_invoke_all('file_entity_update_index', $file);
  foreach ($extra as $t) {
    $text .= $t;
  }

  // Update index
  search_index($file->fid, 'file', $text);
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function file_entity_form_search_form_alter(&$form, $form_state) {
  if (isset($form['module']) && $form['module']['#value'] == 'file_entity' && user_access('use advanced search')) {
    // Keyword boxes:
    $form['advanced'] = array(
      '#type' => 'fieldset',
      '#title' => t('Advanced search'),
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
      '#attributes' => array('class' => array('search-advanced')),
    );
    $form['advanced']['keywords'] = array(
      '#prefix' => '<div class="criterion">',
      '#suffix' => '</div>',
    );
    $form['advanced']['keywords']['or'] = array(
      '#type' => 'textfield',
      '#title' => t('Containing any of the words'),
      '#size' => 30,
      '#maxlength' => 255,
    );
    $form['advanced']['keywords']['phrase'] = array(
      '#type' => 'textfield',
      '#title' => t('Containing the phrase'),
      '#size' => 30,
      '#maxlength' => 255,
    );
    $form['advanced']['keywords']['negative'] = array(
      '#type' => 'textfield',
      '#title' => t('Containing none of the words'),
      '#size' => 30,
      '#maxlength' => 255,
    );

    // File types:
    $types = array_map('check_plain', file_entity_type_get_names());
    $form['advanced']['type'] = array(
      '#type' => 'checkboxes',
      '#title' => t('Only of the type(s)'),
      '#prefix' => '<div class="criterion">',
      '#suffix' => '</div>',
      '#options' => $types,
    );
    $form['advanced']['submit'] = array(
      '#type' => 'submit',
      '#value' => t('Advanced search'),
      '#prefix' => '<div class="action">',
      '#suffix' => '</div>',
      '#weight' => 100,
    );

    $form['#validate'][] = 'file_entity_search_validate';
  }
}

/**
 * Form API callback for the search form. Registered in file_entity_form_alter().
 */
function file_entity_search_validate($form, &$form_state) {
  // Initialize using any existing basic search keywords.
  $keys = $form_state['values']['processed_keys'];

  // Insert extra restrictions into the search keywords string.
  if (isset($form_state['values']['type']) && is_array($form_state['values']['type'])) {
    // Retrieve selected types - Form API sets the value of unselected
    // checkboxes to 0.
    $form_state['values']['type'] = array_filter($form_state['values']['type']);
    if (count($form_state['values']['type'])) {
      $keys = search_expression_insert($keys, 'type', implode(',', array_keys($form_state['values']['type'])));
    }
  }

  if (isset($form_state['values']['term']) && is_array($form_state['values']['term']) && count($form_state['values']['term'])) {
    $keys = search_expression_insert($keys, 'term', implode(',', $form_state['values']['term']));
  }
  if ($form_state['values']['or'] != '') {
    if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' ' . $form_state['values']['or'], $matches)) {
      $keys .= ' ' . implode(' OR ', $matches[1]);
    }
  }
  if ($form_state['values']['negative'] != '') {
    if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' ' . $form_state['values']['negative'], $matches)) {
      $keys .= ' -' . implode(' -', $matches[1]);
    }
  }
  if ($form_state['values']['phrase'] != '') {
    $keys .= ' "' . str_replace('"', ' ', $form_state['values']['phrase']) . '"';
  }
  if (!empty($keys)) {
    form_set_value($form['basic']['processed_keys'], trim($keys), $form_state);
  }
}

/**
 * Implements hook_admin_paths().
 */
function file_entity_admin_paths() {
  $paths = array(
/**
 * Implements hook_theme().
 */
function file_entity_theme() {
  return array(
    'file_entity' => array(
      'render element' => 'elements',
    'file_entity_search_admin' => array(
      'render element' => 'form',
    ),
    'file_entity_file_type_overview' => array(
      'variables' => array('label' => NULL, 'description' => NULL),
      'file' => 'file_entity.admin.inc',
    ),
    'file_entity_file_display_order' => array(
      'render element' => 'element',
      'file' => 'file_entity.admin.inc',
    ),
    'file_entity_file_link' => array(
      'variables' => array('file' => NULL, 'icon_directory' => NULL),
 * Extends the core file entity to be fieldable. The file type is used as the
 * bundle key. File types are implemented as CTools exportables, so modules can
 * define default file types via hook_file_default_types(), and the
 * administrator can override the default types or add custom ones via
 * admin/structure/file-types.
 */
function file_entity_entity_info_alter(&$entity_info) {
  $entity_info['file']['fieldable'] = TRUE;
  $entity_info['file']['entity keys']['bundle'] = 'type';
  $entity_info['file']['bundle keys']['bundle'] = 'type';
  $entity_info['file']['bundles'] = array();
  $entity_info['file']['uri callback'] = 'file_entity_uri';
  $entity_info['file']['view modes']['teaser'] = array(
    'label' => t('Teaser'),
    'custom settings' => TRUE,
  );
  $entity_info['file']['view modes']['full'] = array(
    'label' => t('Full content'),
    'custom settings' => FALSE,
  );
  $entity_info['file']['view modes']['preview'] = array(
    'label' => t('Preview'),
    'custom settings' => TRUE,
  // Search integration is provided by file_entity.module, so search-related
  // view modes for files are defined here and not in search.module.
  if (module_exists('search')) {
    $entity_info['file']['view modes']['search_index'] = array(
      'label' => t('Search index'),
      'custom settings' => FALSE,
    );
    $entity_info['file']['view modes']['search_result'] = array(
      'label' => t('Search result'),
      'custom settings' => FALSE,
    );
  }

  foreach (file_type_get_enabled_types() as $type) {
    $entity_info['file']['bundles'][$type->type] = array(
      'label' => $type->label,
        'path' => 'admin/structure/file-types/manage/%file_type',
        'real path' => 'admin/structure/file-types/manage/' . $type->type,
  // Enable Metatag support.
  $entity_info['file']['metatags'] = TRUE;

  // Ensure some of the Entity API callbacks are supported.
  $entity_info['file']['creation callback'] = 'entity_metadata_create_object';
  $entity_info['file']['view callback'] = 'file_view_multiple';
  $entity_info['file']['edit callback'] = 'file_entity_metadata_form_file';
  $entity_info['file']['access callback'] = 'file_entity_access';

  // Add integration with the Title module for file name replacement support.
  $entity_info['file']['field replacement'] = array(
    'filename' => array(
      'field' => array(
        'type' => 'text',
        'cardinality' => 1,
        'translatable' => TRUE,
      ),
      'instance' => array(
        'label' => t('File name'),
        'description' => t('A field replacing file name.'),
        'required' => TRUE,
        'settings' => array(
          'text_processing' => 0,
        ),
        'widget' => array(
          'weight' => -5,
        ),
        'display' => array(
          'default' => array(
            'type' => 'hidden',
          ),
        ),
      ),
      'preprocess_key' => 'filename',
    ),
  );
}

/**
 * Implements hook_entity_property_info().
 */
function file_entity_entity_property_info() {
  $info['file']['properties']['type'] = array(
    'label' => t('File type'),
    'type' => 'token',
    'description' => t('The type of the file.'),
    'setter callback' => 'entity_property_verbatim_set',
    'setter permission' => 'administer files',
    'required' => TRUE,
    'schema field' => 'type',
  );

  return $info;
/**
 * Implements hook_field_display_ENTITY_TYPE_alter().
 */
function file_entity_field_display_file_alter(&$display, $context) {
  // Hide field labels in search index.
  if ($context['view_mode'] == 'search_index') {
    $display['label'] = 'hidden';
  }
}

/**
 * URI callback for file entities.
 */
function file_entity_uri($file) {
  $uri['path'] = 'file/' . $file->fid;
  return $uri;
}

/**
 * Entity API callback to get the form of a file entity.
 */
function file_entity_metadata_form_file($file) {
  // Pre-populate the form-state with the right form include.
  $form_state['build_info']['args'] = array($file);
  form_load_include($form_state, 'inc', 'file_entity', 'file_entity.pages');
  return drupal_build_form('file_entity_edit', $form_state);
/**
 * Implements hook_field_extra_fields().
 *
 * Adds 'file' as an extra field, so that its display and form component can be
 * weighted relative to the fields that are added to file entity bundles.
 */
function file_entity_field_extra_fields() {
  if ($file_type_names = file_entity_type_get_names()) {
    foreach ($file_type_names as $type => $name) {
      $info['file'][$type]['form']['filename'] = array(
        'label' => t('File name'),
        'description' => t('File name'),
        'weight' => -10,
      );
      $info['file'][$type]['form']['preview'] = array(
        'label' => t('File'),
        'description' => t('File preview'),
        'weight' => -5,
      );
      $info['file'][$type]['display']['file'] = array(
        'label' => t('File'),
        'description' => t('File display'),
        'weight' => 0,
      );
    }
}

/**
 * Implements hook_file_formatter_info().
 */
function file_entity_file_formatter_info() {
  $formatters = array();

  // Allow file field formatters to be reused for displaying the file entity's
  // file pseudo-field.
  foreach (field_info_formatter_types() as $field_formatter_type => $field_formatter_info) {
    $is_file_formatter = in_array('file', $field_formatter_info['field types']);
    $is_image_formatter = in_array('image', $field_formatter_info['field types']);

    if ($is_file_formatter || $is_image_formatter) {
      $formatters['file_field_' . $field_formatter_type] = array(
        'label' => $field_formatter_info['label'],
        'view callback' => 'file_entity_file_formatter_file_field_view',
      );
      if (!$is_file_formatter) {
        $formatters['file_field_' . $field_formatter_type]['file types'] = array('image');
      }
      if (!empty($field_formatter_info['settings'])) {
        $formatters['file_field_' . $field_formatter_type] += array(
          'default settings' => $field_formatter_info['settings'],
          'settings callback' => 'file_entity_file_formatter_file_field_settings',