You are here

media_browser_plus.module in Media Browser Plus 7.3

Same filename and directory in other branches
  1. 7 media_browser_plus.module
  2. 7.2 media_browser_plus.module

Media Browser Plus - enhanced file management functions.

File

media_browser_plus.module
View source
<?php

/**
 * @file
 * Media Browser Plus - enhanced file management functions.
 */

/**
 * Implements hook_views_api().
 */
function media_browser_plus_views_api() {
  return array(
    'api' => 3,
    'path' => drupal_get_path('module', 'media_browser_plus') . '/views',
  );
}

/**
 * Implements hook_menu().
 * @see hook_menu()
 */
function media_browser_plus_menu() {
  $path = drupal_get_path('module', 'media_browser_plus');
  $items['admin/content/file/%file/move-to-folder/%taxonomy_term'] = array(
    'title' => 'Load Media Entities',
    'page callback' => 'media_browser_plus_move_file_callback',
    'page arguments' => array(
      3,
      5,
    ),
    'access callback' => 'file_entity_access',
    'access arguments' => array(
      'update',
      3,
    ),
    'delivery callback' => 'drupal_json_output',
    'type' => MENU_CALLBACK,
  );
  $items['admin/config/media/media_browser_plus_settings'] = array(
    'title' => 'Media Browser Plus Settings',
    'description' => 'Change the behaviour and layout of the media browser plus UI',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'media_browser_plus_media_settings',
    ),
    'access arguments' => array(
      'administer media browser',
    ),
    'file path' => $path . '/includes',
    'file' => 'media_browser_plus.admin.inc',
  );

  // Expose further "Mutliple" actions.
  if (module_exists('multiform')) {

    // Following approach comes from the media module.
    // @todo Investigate passing file IDs in query string rather than a menu
    // argument and then deprecate media_multi_load().
    // @TODO Version compatibility cleanup.
    $callback_function = '%media_multi';
    if (module_exists('media_bulk_upload')) {
      $callback_function = '%media_bulk_upload_multi';
    }
    $items['admin/content/file/delete-multiple/' . $callback_function] = array(
      'title' => 'Delete multiple files',
      'page callback' => 'drupal_get_form',
      'page arguments' => array(
        'file_entity_multiple_delete_confirm',
        4,
      ),
      'access callback' => 'media_browser_plus_file_entity_access_wrapper',
      'access arguments' => array(
        'delete',
        4,
      ),
      'file' => 'file_entity.admin.inc',
      'file path' => drupal_get_path('module', 'file_entity'),
    );
  }

  // If there's an archiver available provide the multi download.
  if (count(archiver_get_info())) {

    // @TODO Version compatibility cleanup.
    $callback_function = '%media_multi';
    if (module_exists('media_bulk_upload')) {
      $callback_function = '%media_bulk_upload_multi';
    }
    $items['admin/content/file/download-multiple/' . $callback_function] = array(
      'title' => 'Download multiple files',
      'page callback' => 'media_browser_plus_download_multiple_files',
      'page arguments' => array(
        4,
      ),
      'access callback' => 'media_browser_plus_file_entity_access_wrapper',
      'access arguments' => array(
        'view',
        4,
      ),
      'file' => 'file_entity.admin.inc',
      'file path' => drupal_get_path('module', 'file_entity'),
    );
  }
  return $items;
}

/**
 * Implements hook_menu_alter().
 */
function media_browser_plus_menu_alter(&$items) {

  // If enabled and possible replace the default file browser by mbp.
  if (variable_get('media_browser_plus_thumbnails_as_default_browser', TRUE) && isset($items['admin/content/file/mbp']) && isset($items['admin/content/file'])) {
    $items['admin/content/file/list'] = $items['admin/content/file'];
    $file_title = $items['admin/content/file']['title'];
    $items['admin/content/file'] = $items['admin/content/file/mbp'];
    $items['admin/content/file']['title'] = $file_title;
    $items['admin/content/file/mbp']['type'] = MENU_DEFAULT_LOCAL_TASK;
    $items['admin/content/file/mbp']['weight'] = -1;
  }
}

/**
 * Implements hook_menu_local_tasks_alter().
 */
function media_browser_plus_menu_local_tasks_alter(&$data, $router_item, $root_path) {

  // Add action links on 'admin/content/file/mbp' page.
  // @todo can this be done in the related view?
  if ($root_path == 'admin/content/file/mbp') {
    $item = menu_get_item('file/add');
    if (!empty($item['access'])) {
      $data['actions']['output'][] = array(
        '#theme' => 'menu_local_action',
        '#link' => $item,
        '#weight' => $item['weight'],
      );
    }
    $item = menu_get_item('admin/content/file/import');
    if (!empty($item['access'])) {
      $data['actions']['output'][] = array(
        '#theme' => 'menu_local_action',
        '#link' => $item,
        '#weight' => $item['weight'],
      );
    }
  }
  if ($root_path == 'admin/content/file/mbp' || variable_get('media_browser_plus_thumbnails_as_default_browser', TRUE) && $root_path == 'admin/content/file') {
    $item = menu_get_item('admin/structure/taxonomy/media_folders');
    if (!empty($item['access'])) {
      $data['actions']['output'][] = array(
        '#theme' => 'menu_local_action',
        '#link' => $item,
        '#weight' => 10,
      );
    }
  }
}

/**
 * Implements hook_library().
 */
function media_browser_plus_library() {
  $path = drupal_get_path('module', 'media_browser_plus');
  $libraries['media_browser_plus'] = array(
    'title' => 'Media Browser Plus',
    'version' => '1',
    'js' => array(
      $path . '/js/media_browser_plus.js' => array(),
    ),
    'css' => array(
      $path . '/css/media_browser_plus.views.css' => array(
        'type' => 'file',
        'media' => 'screen',
      ),
    ),
    'dependencies' => array(
      array(
        'system',
        'ui.draggable',
      ),
      array(
        'system',
        'ui.droppable',
      ),
      array(
        'system',
        'jquery.cookie',
      ),
    ),
  );
  return $libraries;
}

/**
 * Implements hook_field_attach_create_bundle().
 */
function media_browser_plus_field_attach_create_bundle($entity_type, $bundle) {

  // Ensure the folder field is added if a new file bundle is created.
  if ($entity_type == 'file') {
    $field = field_info_field('field_folder');
    $field['bundle'] = $bundle;
    $instance_info = field_info_instance($field['entity_type'], $field['field_name'], $field['bundle']);
    if (empty($instance_info)) {
      field_create_instance($field);
    }
  }
}

/**
 * Implements hook_action_info().
 */
function media_browser_plus_action_info() {

  // If there's an archiver available provide the download functionality.
  if (count(archiver_get_info())) {
    return array(
      'media_browser_plus_download_action' => array(
        'type' => 'file',
        'label' => t('Download file(s)'),
        'configurable' => TRUE,
        'vbo_configurable' => TRUE,
        'triggers' => array(
          'any',
        ),
      ),
    );
  }
}

/**
 * Configuration form shown to the user before the action gets executed.
 *
 * @todo Replace with proper integration as soon as VBO supports non batch
 * operations.
 */
function media_browser_plus_download_action_form($context, $form_state) {

  // We hijack the whole process here because there's now way yet to skip the
  // batch processing in VBO 3.1.
  $vbo = _views_bulk_operations_get_field($context['view']);
  $selection = _views_bulk_operations_get_selection($vbo, $form_state);
  $files = file_load_multiple($selection);

  // Check permissions. If one fails - stop whole operation!
  if (!media_browser_plus_file_entity_access_wrapper('view', $files)) {
    drupal_access_denied();
    drupal_exit();
  }
  media_browser_plus_download_multiple_files($selection);
}

/**
 * Callback for the action.
 */
function media_browser_plus_download_action($file, &$context = array()) {
  drupal_set_message('How the heck did you reach this function? Please open an issue in the issue queue, thanks! :)', 'warning');
}

/**
 * Download multiple files.
 *
 * Creates n archive for multiple files - directly sends a single file.
 *
 * @param array $files
 *   A list if file id's or file objects.
 */
function media_browser_plus_download_multiple_files($files) {
  $fids = array();

  // Check if the list consists of /contains file ids.
  foreach ($files as $key => $file) {
    if (!is_object($file)) {
      $fids[] = $file;
      unset($files[$key]);
    }
  }

  // If file ids were found populate list of file objects.
  if (!empty($fids)) {
    $files = array_merge($files, file_load_multiple($fids));
  }
  if (count($files) > 1) {
    $file_name = 'file_download_' . time() . '.zip';
    $archive = drupal_tempnam('temporary://', 'mbp');

    // Abuse the existing archiver action. Do like we run an action ;)
    module_load_include('inc', 'views_bulk_operations', 'actions/archive.action');
    $archiver_context['destination'] = $archive;
    $archiver_context['progress']['current'] = 1;
    $archiver_context['progress']['total'] = count($files);
    $archiver_context['settings']['temporary'] = TRUE;
    foreach ($files as $file) {
      views_bulk_operations_archive_action($file, $archiver_context);
    }

    // Register cleanup function. The created archive has to be removed again.
    drupal_register_shutdown_function('media_browser_plus_download_action_cleanup');
  }
  elseif (count($files) == 1) {
    $file = reset($files);
    $archive = $file->uri;
    $file_name = drupal_basename($file->uri);
  }
  else {
    drupal_not_found();
    drupal_exit();
  }

  // Ensure we've the latest file information.
  clearstatcache();

  // Prepare headers.
  $headers['Pragma'] = 'public';
  $headers['Expires'] = '0';
  $headers['Cache-Control'] = 'must-revalidate, post-check=0, pre-check=0';
  $headers['Content-type'] = 'application/zip';
  $headers['Content-Disposition'] = 'attachment; filename=' . $file_name;
  $headers['Content-length'] = filesize($archive);
  file_transfer($archive, $headers);
}

/**
 * Delete temporary download archive.
 */
function media_browser_plus_download_action_cleanup() {
  $file =& drupal_static('media_browser_plus_download_action', array());
  if (!empty($file)) {
    drupal_unlink($file);
  }
}

/**
 * Move the file to another folder.
 *
 * @param object $file
 *   The file object to update.
 * @param object $folder
 *   The folder object to use for the file.
 *
 * @return bool
 *   FALSE on error.
 */
function media_browser_plus_move_file_callback($file, $folder) {
  if (empty($file->field_folder[LANGUAGE_NONE][0]['tid']) || $file->field_folder[LANGUAGE_NONE][0]['tid'] != $folder->tid) {
    return media_browser_plus_move_file($folder->tid, $file);
  }
  return TRUE;
}

/**
 * Manages access for media browser plus actions.
 *
 * @param string $op
 *   The permission, such as "administer nodes", being checked for.
 *
 * @return bool
 *   TRUE if the user has the permission.
 */
function media_browser_plus_access($op) {
  return user_access('administer files') || user_access($op);
}

/**
 * Wrapper around file_entity_access() to deal with multiple files.
 */
function media_browser_plus_file_entity_access_wrapper($op, $files = NULL, $account = NULL) {

  // If there's files parameter, ensure it is an array to handle.
  if (!is_array($files) && !empty($files)) {
    $files = array(
      $files,
    );
  }
  if (!empty($files)) {
    foreach ($files as $file) {

      // Even if one is not accessible return FALSE.
      if (!file_entity_access($op, $file, $account)) {
        return FALSE;
      }
    }
    return TRUE;
  }
  return file_entity_access($op, $files, $account);
}

/**
 * Loads and (if $autocreate is set) creates the default media folder object.
 *
 * @param bool $autocreate
 *   Creates the folder if necessary.
 *
 * @return object|FALSE
 *   The folder term or FALSE if not found. During installation this can return
 *   FALSE!
 */
function media_browser_plus_get_media_root_folder($autocreate = FALSE) {
  $root_folder = FALSE;
  $vocabulary = taxonomy_vocabulary_machine_name_load('media_folders');
  if (!$vocabulary) {
    $t = get_t();
    $vocabulary = (object) array(
      'name' => 'Media Folders',
      'description' => $t('Use media folders to organize your media'),
      'machine_name' => 'media_folders',
      'hierarchy' => 1,
      'help' => $t('Enter a concise name for the media folder'),
    );
    taxonomy_vocabulary_save($vocabulary);
  }
  if ($vocabulary) {
    $root_folder = taxonomy_term_load(variable_get('media_browser_plus_root_folder_tid'));
    if ($root_folder === FALSE) {
      if ($autocreate) {
        $root_folder = new stdClass();
        $root_folder->name = 'Media Root';
        $root_folder->description = 'default media folder';
        $root_folder->vid = $vocabulary->vid;
        $root_folder->weight = '-10';
        $root_folder->parent = 0;
        $root_folder->autocreate = TRUE;
        taxonomy_term_save($root_folder);
        variable_set('media_browser_plus_root_folder_tid', $root_folder->tid);
      }
      elseif (!drupal_installation_attempted()) {
        watchdog('media_browser_plus', 'Unable to load the media root folder term. Please check the folder management!', array(), WATCHDOG_ERROR, 'admin/structure/taxonomy/' . $vocabulary->machine_name);
      }
    }
  }
  return $root_folder;
}

/**
 * Construct the path of a media_folder term without scheme.
 *
 * Always returns the same path if the filesystem handling is disabled.
 *
 * @param object|NULL $term
 *   Containing term id and term name. If left empty the root folder will be
 *   returned.
 *
 * @return string
 *   The path to the requested folder. Without the scheme and without a trailing
 *   slash.
 */
function media_browser_plus_construct_dir_path($term = NULL) {
  $path = '';
  if ($root_folder = variable_get('media_root_folder')) {
    $path = $root_folder;
  }

  // Always return the path to the defined media_root_folder if the folder
  // handling is disabled.
  if (!variable_get('media_browser_plus_filesystem_folders', TRUE)) {
    return trim($path, '/');
  }

  // $root_folder_term can be FALSE during the installation of the module.
  $root_folder_term = media_browser_plus_get_media_root_folder();
  if ($term && $root_folder_term && $term->tid != $root_folder_term->tid) {
    $parents = array_reverse(taxonomy_get_parents_all($term->tid));
    array_pop($parents);
    if (is_array($parents) && !empty($parents)) {
      foreach ($parents as $parent) {
        if ($parent->tid != $root_folder_term->tid) {
          if (function_exists('transliteration_clean_filename')) {
            $parent->name = transliteration_clean_filename($parent->name);
          }
          $path = file_create_filename($parent->name, $path);
        }
      }
    }
    if (function_exists('transliteration_clean_filename')) {
      $term->name = transliteration_clean_filename($term->name);
    }
    $path = file_create_filename($term->name, $path);
  }
  $path = trim($path, '/');
  return $path;
}

/**
 * Moves and saves a file.
 *
 * Every managed file that is saved or updated, should pass through this to
 * ensure the filesystem location matches the folder term.
 *
 * @param int $tid
 *   The folder's term id.
 * @param object $file
 *   The file object.
 * @param int $replace
 *   Replace behavior when the destination file already exists.
 * @param bool $save
 *   Enables or disables saving the file object. Handy for cases in which the
 *   file object is saved anyway.
 *
 * @return bool
 *   TRUE on success.
 */
function media_browser_plus_move_file($tid, $file, $replace = NULL, $save = TRUE) {

  // See which file replace handling has to be used.
  if (is_null($replace)) {
    $replace = FILE_EXISTS_RENAME;

    // See if the file entity provides the file_replace property to indicate how
    // this has to be handled.
    if (isset($file->file_replace) && in_array($file->file_replace, array(
      FILE_EXISTS_REPLACE,
      FILE_EXISTS_RENAME,
      FILE_EXISTS_ERROR,
    ))) {
      $replace = $file->file_replace;
    }
  }

  // Ensure the new location is stored in the file entity.
  $file->field_folder[LANGUAGE_NONE] = array(
    array(
      'tid' => $tid,
    ),
  );

  // No need to process the file path if mpb doesn't mirror to filesystem.
  if (!variable_get('media_browser_plus_filesystem_folders', TRUE)) {
    if ($save) {
      file_save($file);
    }
    return TRUE;
  }
  $local_stream_wrappers = media_get_local_stream_wrappers();
  $scheme = file_uri_scheme($file->uri);
  if (function_exists('transliteration_clean_filename')) {
    $file->filename = transliteration_clean_filename($file->filename);
  }

  // Don't change the uri for non-local files.
  if (!isset($local_stream_wrappers[$scheme])) {
    if ($save) {
      file_save($file);
    }
  }
  else {

    // Media translation module does need this since it allows the creation of
    // file references which shouldn't move the referenced file itself when
    // moved. See http://drupal.org/node/1331818 for details.
    if (module_exists('media_translation') && media_translation_is_virtual_file($file->fid)) {
      file_save($file);
      return TRUE;
    }

    // If folder sync is enable move the file and then update the file entity.
    if (variable_get('media_browser_plus_filesystem_folders', TRUE)) {
      $folder = taxonomy_term_load($tid);
      $path = file_stream_wrapper_uri_normalize(file_uri_scheme($file->uri) . '://' . media_browser_plus_construct_dir_path($folder));
      file_prepare_directory($path, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
      if ($save) {
        return file_move($file, $path, $replace);
      }
      else {
        if ($uri = file_unmanaged_move($file->uri, $path, $replace)) {
          $file->uri = $uri;
          return TRUE;
        }
        return FALSE;
      }
    }
    else {

      // If folder sync is disabled just the file entity is updated.
      file_save($file);
    }
  }
  return TRUE;
}

/**
 * Implements hook_taxonomy_term_presave().
 *
 * @see media_browser_plus_taxonomy_term_update()
 */
function media_browser_plus_taxonomy_term_presave($term) {

  // Avoid running this code when we are auto-creating the root folder term.
  if (empty($term->autocreate)) {

    // Figure out if this is a folder term and if so store the current file path
    // for further processing in media_browser_plus_taxonomy_term_update().
    $vocabulary = taxonomy_vocabulary_machine_name_load('media_folders');
    if (!empty($vocabulary) && $term->vid == $vocabulary->vid) {
      $parent = NULL;
      $root_folder = media_browser_plus_get_media_root_folder();

      // Check if parent handling is necessary.
      if (isset($term->parent)) {

        // Ensure we're dealing with an array.
        if (!is_array($term->parent)) {
          $term->parent = array(
            $term->parent,
          );
        }

        // A folder term can just have one parent.
        if (count($term->parent) > 1) {
          $term->parent = array(
            reset($term->parent),
          );
        }

        // Fetch the used parent.
        $parent = reset($term->parent);
      }

      // A subfolder term is always child of the root folder. Condition ensures
      // we don't interfere while installing the module.
      if ($root_folder && (empty($term->tid) || $term->tid != $root_folder->tid) && empty($parent)) {
        $term->parent = array(
          $root_folder->tid,
        );
      }

      // Actions if this is an existing term.
      if (!empty($term->tid)) {

        // Store current path the check later if the folder was moved.
        $term->media_browser_plus_original_path = media_browser_plus_construct_dir_path($term->original);
      }
    }
  }
}

/**
 * Implements hook_taxonomy_term_insert().
 */
function media_browser_plus_taxonomy_term_insert($term) {

  // Avoid running this code when we are auto-creating the root folder term.
  if (empty($term->autocreate)) {
    if ($term->vocabulary_machine_name == 'media_folders') {

      // Prepare path for new folder terms.
      $dir = media_browser_plus_construct_dir_path($term);
      $error = FALSE;
      foreach (media_get_local_stream_wrappers() as $scheme => $scheme_info) {
        $path = file_stream_wrapper_uri_normalize($scheme . '://' . $dir);
        if (!file_prepare_directory($path, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
          $error = TRUE;
        }
      }
      if (!$error) {
        drupal_set_message(t('Folder %term_name created successfully', array(
          '%term_name' => $term->name,
        )));
      }
      else {
        drupal_set_message(t('Folder %term_name created successfully as term but failed to create as physical folder.Please do it manually', array(
          '%term_name' => $term->name,
        )), 'warning');
      }

      // Clear view cache for media browser plus folders.
      media_browser_plus_clear_views_cache('media_browser_plus_folders');
    }
  }
}

/**
 * Implements hook_taxonomy_term_update().
 *
 * @see media_browser_plus_taxonomy_term_presave()
 */
function media_browser_plus_taxonomy_term_update($term) {
  if ($term->vocabulary_machine_name == 'media_folders') {

    // Check if the folder term was moved. Only folder terms have this property.
    if (!empty($term->media_browser_plus_original_path)) {
      $destination = media_browser_plus_construct_dir_path($term);
      if ($term->media_browser_plus_original_path != $destination) {
        module_load_include('inc', 'media_browser_plus', '/includes/media_browser_plus.folders');

        // Prepare batch to move folder and files.
        $batch = array(
          'title' => t('Updating file locations'),
          'operations' => media_browser_plus_move_subfolder($term, $term->media_browser_plus_original_path, $destination),
          'file' => drupal_get_path('module', 'media_browser_plus') . '/includes/media_browser_plus.folders.inc',
        );

        // If necessary start the batch to update the folder structure.
        if (!empty($batch['operations'])) {
          batch_set($batch);
        }
      }
    }

    // Clear view cache for media browser plus folders.
    media_browser_plus_clear_views_cache('media_browser_plus_folders');
  }
}

/**
 * Implements hook_form_FORM_ID_alter() for taxonomy_form_term().
 *
 * @see media_browser_plus_form_taxonomy_term_confirm_delete_submit()
 */
function media_browser_plus_form_taxonomy_form_term_alter(&$form, &$form_state) {

  // Just modify if this is is / contains the confirm delete form.
  if (isset($form_state['confirm_delete'])) {
    array_unshift($form['#submit'], 'media_browser_plus_form_taxonomy_term_confirm_delete_submit');
  }
}

/**
 * Submit handler for preparing for term deletion.
 *
 * Because hook_taxonomy_term_delete() is invoked after all data are removed
 * from the db we fill the static cache so that it's still possible to build
 * the necessary paths to clean the filesystem.
 *
 * @see media_browser_plus_taxonomy_term_delete()
 */
function media_browser_plus_form_taxonomy_term_confirm_delete_submit(&$form, &$form_state) {

  // Fill the static caches needed to clean the filesystem.
  taxonomy_get_parents($form_state['values']['tid']);
  taxonomy_get_parents_all($form_state['values']['tid']);
}

/**
 * Implements hook_taxonomy_term_delete().
 *
 * @see media_browser_plus_form_taxonomy_term_confirm_delete_submit()
 */
function media_browser_plus_taxonomy_term_delete($term) {
  static $term_hierarchy_filter;

  // Figure out if this is a folder term and if so handle the related files.
  $vocabulary = taxonomy_vocabulary_machine_name_load('media_folders');
  if (!empty($vocabulary) && $term->vid == $vocabulary->vid) {

    // Skip if this term is already handled here by its parent term.
    if (isset($term_hierarchy_filter[$term->tid])) {
      unset($term_hierarchy_filter[$term->tid]);
      return;
    }

    // Create an array of all the folders to handle.
    $folders = array(
      '0:' . $term->tid => $term,
    );

    // Fetch all sub-folders.
    $tree = taxonomy_get_tree($term->vid, $term->tid);
    foreach ($tree as $subterm) {
      $folders[$subterm->depth + 1 . ':' . $subterm->tid] = $subterm;
      $term_hierarchy_filter[$subterm->tid] = $subterm->tid;
    }

    // Ensure the order for processing is right.
    krsort($folders);
    $all_files_deleted = TRUE;
    $used_files = array();
    foreach ($folders as $folder) {

      // Fetch all files from the folder.
      $file_query = new EntityFieldQuery();
      $files = $file_query
        ->entityCondition('entity_type', 'file')
        ->fieldCondition('field_folder', 'tid', $folder->tid)
        ->execute();

      // If there are files, delete them if possible.
      if (!empty($files['file'])) {
        $files = file_load_multiple(array_keys($files['file']));
        foreach ($files as $file) {
          if (($file_usage = file_delete($file)) !== TRUE) {
            $all_files_deleted = FALSE;
            if (is_array($file_usage)) {
              $used_files[$file->fid] = $file_usage;
            }
          }
        }
      }

      // Also delete the folder when it's empty.
      if ($all_files_deleted && ($dir = media_browser_plus_construct_dir_path($folder))) {
        foreach (media_get_local_stream_wrappers() as $scheme => $scheme_info) {
          $folder_path = file_stream_wrapper_uri_normalize($scheme . '://' . $dir);
          if (!@drupal_rmdir($folder_path)) {
            drupal_set_message(t('Unable to delete the folder (!path) on the disk', array(
              '!path' => $folder_path,
            )), 'error');
          }
        }
      }
    }
    if (!$all_files_deleted) {
      $list = array();
      foreach ($used_files as $fid => $usage) {
        $file = file_load($fid);
        $list['items'][] = l($file->filename, 'file/' . $fid . '/usage');
      }
      drupal_set_message(t("Some of the files in the folder are used and can't be deleted:") . theme('item_list', $list), 'error');
    }

    // Clear view cache for media browser plus folders.
    media_browser_plus_clear_views_cache('media_browser_plus_folders');
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * This is necessary since altering the hierarchy or weight of terms in the
 * overview won't trigger any term hooks *blargh* :|
 */
function media_browser_plus_form_taxonomy_overview_terms_alter(&$form, &$form_state, $vocabulary) {
  if (!isset($form_state['confirm_reset_alphabetical'])) {
    if ($form['#vocabulary']->machine_name == 'media_folders') {
      $form['#validate'][] = 'media_browser_plus_form_taxonomy_overview_terms_validate';
      $form['#submit'][] = 'media_browser_plus_form_taxonomy_overview_terms_submit';
    }
  }
}

/**
 * Validation handler for the taxonomy term overview list.
 *
 * This is necessary since altering the hierarchy or weight of terms in the
 * overview won't trigger any term hooks *blargh* :|
 */
function media_browser_plus_form_taxonomy_overview_terms_validate(&$form, &$form_state) {
  $vocabulary = $form['#vocabulary'];
  $tree = taxonomy_get_tree($vocabulary->vid);
  foreach ($tree as $term) {
    $form_state['#mbp_original_paths'][$term->tid] = media_browser_plus_construct_dir_path($term);
  }
}

/**
 * Submit handler for the taxonomy term overview list.
 *
 * This is necessary since altering the hierarchy or weight of terms in the
 * overview won't trigger any term hooks *blargh* :|
 */
function media_browser_plus_form_taxonomy_overview_terms_submit(&$form, &$form_state) {
  module_load_include('inc', 'media_browser_plus', 'includes/media_browser_plus.folders');
  $vocabulary = $form['#vocabulary'];
  taxonomy_terms_static_reset();
  $root_folder = media_browser_plus_get_media_root_folder();
  $tree = taxonomy_get_tree($vocabulary->vid);

  // Prepare batch.
  $batch = array(
    'title' => t('Updating Media'),
    'operations' => array(),
    'finished' => 'media_browser_plus_update_folder_hierarchy_batch_complete',
    'file' => drupal_get_path('module', 'media_browser_plus') . '/includes/media_browser_plus.folders.inc',
  );
  foreach ($tree as $term) {

    // Deal only with subfolders.
    if ($term->tid != $root_folder->tid) {

      // A subfolder term is always child of the root folder.
      if (empty($term->parents[0])) {

        // The presave hook will take care of fixing this.
        taxonomy_term_save($term);
      }
      $path = media_browser_plus_construct_dir_path($term);
      if ($form_state['#mbp_original_paths'][$term->tid] != $path) {
        $batch['operations'] = array_merge($batch['operations'], media_browser_plus_move_subfolder($term, $form_state['#mbp_original_paths'][$term->tid], $path));
      }
    }
  }

  // If necessary start the batch to update the structure.
  if (!empty($batch['operations'])) {
    batch_set($batch);
  }

  // Clear view cache for media browser plus folders.
  media_browser_plus_clear_views_cache('media_browser_plus_folders');
}

/**
 * Batch process finish callback for updating the folder hierarchy.
 */
function media_browser_plus_update_folder_hierarchy_batch_complete($success, $results, $operations) {
  if ($success) {
    drupal_set_message(t('Successfully updated all folders'));
  }
  else {
    drupal_set_message(t('Error while updating folder structure'), 'error');
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function media_browser_plus_form_file_entity_add_upload_multiple_alter(&$form, &$form_state) {

  // Abuse the taxonomy term field widget to get the available options.
  $field['settings']['allowed_values'][] = array(
    'vocabulary' => 'media_folders',
    'parent' => 0,
  );
  $form['field_folder'] = array(
    '#type' => 'select',
    '#title' => t('Folder'),
    '#description' => t('Defines the folder where the uploaded files will be saved'),
    '#options' => taxonomy_allowed_values($field),
  );

  // Add own submit handler to set the selected folder in the file objects.
  $form['#submit'][] = 'media_browser_plus_form_file_entity_add_upload_multiple_submit';
}

/**
 * Submit handler to set the folder selected in the multiple upload form.
 */
function media_browser_plus_form_file_entity_add_upload_multiple_submit(&$form, &$form_state) {

  // Iterate over all saved files and add the folder setting.
  if (isset($form_state['values']['field_folder'])) {
    $folder = $form_state['values']['field_folder'];
    foreach ($form_state['files'] as $file) {

      // Only set folder if not set or different to what will be set.
      if (empty($file->field_folder[LANGUAGE_NONE][0]['tid']) || $file->field_folder[LANGUAGE_NONE][0]['tid'] != $folder) {
        $file->field_folder[LANGUAGE_NONE][0]['tid'] = $folder;
        file_save($file);
      }
    }
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function media_browser_plus_form_file_entity_add_upload_alter(&$form, &$form_state) {

  // This isn't really necessary but ensures the usage consistency over all
  // upload forms.
  switch ($form['#step']) {

    // Add folder selection to the upload form.
    case 1:

      // By re-using the field form structure we can inject the value into the
      // next step.
      // @todo Check if we can replace this somehow by the real field widget.
      $form['field_folder'] = array(
        '#type' => 'container',
        '#tree' => TRUE,
      );

      // Abuse the taxonomy term field widget to get the available options.
      $field['settings']['allowed_values'][] = array(
        'vocabulary' => 'media_folders',
        'parent' => 0,
      );
      $field_folder_info = field_info_field('field_folder');
      $form['field_folder'][LANGUAGE_NONE] = array(
        '#type' => 'select',
        '#title' => $field_folder_info['label'],
        '#description' => t('Defines the folder where the uploaded files will be saved'),
        '#options' => taxonomy_allowed_values($field),
      );
      break;

    // Nothing to do for now.
    case 2:
    case 3:
      break;

    // Set media folder default value.
    case 4:
      if (!empty($form_state['storage']['field_folder'][LANGUAGE_NONE]) && isset($form['field_folder'])) {
        $form['field_folder'][LANGUAGE_NONE]['#default_value'] = $form_state['storage']['field_folder'][LANGUAGE_NONE];
      }
      break;
  }
}

/**
 * Clears the cache for a specific view.
 *
 * This method is copied from the cache_actions module.
 *
 * @param string $view_name
 *   The name of the view to clear the cache.
 */
function media_browser_plus_clear_views_cache($view_name) {
  $view = views_get_view($view_name);
  if (is_object($view)) {

    // Go through all displays and clear the cache.
    foreach ($view->display as $display) {

      // Set display handler.
      $view
        ->set_display($display->id);

      // If we don't have our own cache plugin, then we need to copy the cache
      // settings from default.
      if (!isset($display->display_options['cache']) && isset($view->display['default'])) {
        $display->display_options['cache'] = $view->display['default']->display_options['cache'];
      }
      $cache_plugin = views_get_plugin('cache', $display->display_options['cache']['type']);

      // If we have a cache plugin, then initiate it and flush the cache.
      if (isset($cache_plugin)) {
        $cache_plugin
          ->init($view, $display);
        $cache_plugin
          ->cache_flush();
      }
    }
  }
}

/**
 * Implements hook_uuid_entity_features_export_render_alter().
 */
function media_browser_plus_uuid_entity_features_export_render_alter($entity_type, &$export, &$entity) {
  $root_folder = media_browser_plus_get_media_root_folder();

  // Take all reference fields in account.
  $fields = uuid_features_get_field_items_iterator($export, $entity_type, 'taxonomy_term_reference');
  foreach ($fields as $field_name => $field) {
    if (isset($export->{$field_name})) {
      foreach ($field as $language => $items) {
        foreach ($items as $delta => $item) {
          if ($export->{$field_name}[$language][$delta]['tid'] == $root_folder->tid || $export->{$field_name}[$language][$delta]['tid'] == $root_folder->uuid) {
            $export->{$field_name}[$language][$delta]['tid'] = 'mbp_uuid_features_root_folder';
            $export->{$field_name}[$language][$delta]['uuid'] = 'mbp_uuid_features_root_folder';
            $export->mbp_uuid_features_root_folder = TRUE;
          }
        }
      }
    }
  }
  $fields = uuid_features_get_field_items_iterator($export, $entity_type, 'entityreference');
  foreach ($fields as $field_name => $field) {
    if (isset($export->{$field_name})) {
      foreach ($field as $language => $items) {
        foreach ($items as $delta => $item) {
          if (in_array($root_folder->tid, $export->{$field_name}[$language][$delta]) || in_array($root_folder->uuid, $export->{$field_name}[$language][$delta])) {
            $export->{$field_name}[$language][$delta]['target_id'] = 'mbp_uuid_features_root_folder';
            $export->{$field_name}[$language][$delta]['uuid'] = 'mbp_uuid_features_root_folder';
            $export->mbp_uuid_features_root_folder = TRUE;
          }
        }
      }
    }
  }

  // Ensure exported folders also have the generic media root set.
  if ($entity_type == 'taxonomy_term' && $export->vocabulary_machine_name == 'media_folders' && !empty($export->parent)) {
    $root_parents = array_keys($export->parent, $root_folder->uuid);
    foreach ($root_parents as $key) {
      $export->parent[$key] = 'mbp_uuid_features_root_folder';
    }
    if ($export->uuid == $root_folder->uuid) {
      $export->uuid = 'mbp_uuid_features_root_folder';
    }
  }
}

/**
 * Implements hook_uuid_entity_features_rebuild_alter().
 */
function media_browser_plus_uuid_entity_features_rebuild_alter($entity_type, &$entity) {

  // If this file is marked as child of the root folder get the current root
  // folder and set it.
  if (!empty($entity->mbp_uuid_features_root_folder)) {
    unset($entity->mbp_uuid_features_root_folder);
    $root_folder = media_browser_plus_get_media_root_folder();

    // Take all reference fields in account.
    $fields = uuid_features_get_field_items_iterator($entity, $entity_type, 'taxonomy_term_reference');
    foreach ($fields as $field_name => $field) {
      if (isset($entity->{$field_name})) {
        foreach ($field as $language => $items) {
          foreach ($items as $delta => $item) {
            if (in_array('mbp_uuid_features_root_folder', $entity->{$field_name}[$language][$delta])) {
              $entity->{$field_name}[$language][$delta]['tid'] = $root_folder->tid;
            }
          }
        }
      }
    }
    $fields = uuid_features_get_field_items_iterator($entity, $entity_type, 'entityreference');
    foreach ($fields as $field_name => $field) {
      if (isset($entity->{$field_name})) {
        foreach ($field as $language => $items) {
          foreach ($items as $delta => $item) {
            if (in_array('mbp_uuid_features_root_folder', $entity->{$field_name}[$language][$delta])) {
              $entity->{$field_name}[$language][$delta]['target_id'] = $root_folder->tid;
            }
          }
        }
      }
    }
  }

  // Ensure imported folders also have the generic media root set.
  if ($entity_type == 'taxonomy_term' && $entity->vocabulary_machine_name == 'media_folders') {
    $root_parents = array_keys($entity->parent, 'mbp_uuid_features_root_folder');
    foreach ($root_parents as $key) {
      $entity->parent[$key] = $root_folder->tid;
    }
    if ($entity->uuid == 'mbp_uuid_features_root_folder') {
      $entity->uuid = $root_folder->uuid;
      $entity->tid = $root_folder->tid;
    }
  }
}

Functions

Namesort descending Description
media_browser_plus_access Manages access for media browser plus actions.
media_browser_plus_action_info Implements hook_action_info().
media_browser_plus_clear_views_cache Clears the cache for a specific view.
media_browser_plus_construct_dir_path Construct the path of a media_folder term without scheme.
media_browser_plus_download_action Callback for the action.
media_browser_plus_download_action_cleanup Delete temporary download archive.
media_browser_plus_download_action_form Configuration form shown to the user before the action gets executed.
media_browser_plus_download_multiple_files Download multiple files.
media_browser_plus_field_attach_create_bundle Implements hook_field_attach_create_bundle().
media_browser_plus_file_entity_access_wrapper Wrapper around file_entity_access() to deal with multiple files.
media_browser_plus_form_file_entity_add_upload_alter Implements hook_form_FORM_ID_alter().
media_browser_plus_form_file_entity_add_upload_multiple_alter Implements hook_form_FORM_ID_alter().
media_browser_plus_form_file_entity_add_upload_multiple_submit Submit handler to set the folder selected in the multiple upload form.
media_browser_plus_form_taxonomy_form_term_alter Implements hook_form_FORM_ID_alter() for taxonomy_form_term().
media_browser_plus_form_taxonomy_overview_terms_alter Implements hook_form_FORM_ID_alter().
media_browser_plus_form_taxonomy_overview_terms_submit Submit handler for the taxonomy term overview list.
media_browser_plus_form_taxonomy_overview_terms_validate Validation handler for the taxonomy term overview list.
media_browser_plus_form_taxonomy_term_confirm_delete_submit Submit handler for preparing for term deletion.
media_browser_plus_get_media_root_folder Loads and (if $autocreate is set) creates the default media folder object.
media_browser_plus_library Implements hook_library().
media_browser_plus_menu Implements hook_menu().
media_browser_plus_menu_alter Implements hook_menu_alter().
media_browser_plus_menu_local_tasks_alter Implements hook_menu_local_tasks_alter().
media_browser_plus_move_file Moves and saves a file.
media_browser_plus_move_file_callback Move the file to another folder.
media_browser_plus_taxonomy_term_delete Implements hook_taxonomy_term_delete().
media_browser_plus_taxonomy_term_insert Implements hook_taxonomy_term_insert().
media_browser_plus_taxonomy_term_presave Implements hook_taxonomy_term_presave().
media_browser_plus_taxonomy_term_update Implements hook_taxonomy_term_update().
media_browser_plus_update_folder_hierarchy_batch_complete Batch process finish callback for updating the folder hierarchy.
media_browser_plus_uuid_entity_features_export_render_alter Implements hook_uuid_entity_features_export_render_alter().
media_browser_plus_uuid_entity_features_rebuild_alter Implements hook_uuid_entity_features_rebuild_alter().
media_browser_plus_views_api Implements hook_views_api().