<?php

/**
 * @file
 * Block Entity
 */

/**
 * Implements hook_entity_info().
 */
function bean_entity_info() {
  $return = array(
    'bean' => array(
      'label' => t('Block'),
      'entity class' => 'Bean',
      'controller class' => 'BeanEntityAPIController',
      // This class provides integration with the Inline entity form module.
      'inline entity form' => array(
        'controller' => 'BeanInlineEntityFormController',
      ),
      'base table' => 'bean',
      'revision table' => 'bean_revision',
      'fieldable' => TRUE,
      'entity keys' => array(
        'id' => 'bid',
        'bundle' => 'type',
        'label' => 'title',
        'name' => 'delta',
        'revision' => 'vid',
        'default revision' => 'default_revision',
      ),
      'bundles' => array(),
      'bundle keys' => array(
        'bundle' => 'type',
      ),
      'label callback' => 'entity_class_label',
      'uri callback' => 'entity_class_uri',
      'access callback' => 'bean_access',
      'view callback' => 'bean_view_callback',
      'module' => 'bean',
      'metadata controller class' => 'BeanMetadataController',
      'view modes' => array(
        'default' => array(
          'label' => t('Default'),
          'custom settings' => FALSE,
        ),
      ),
      'field replacement' => array(
        'title' => array(
          'field' => array(
            'type' => 'text',
            'cardinality' => 1,
            'translatable' => TRUE,
          ),
          'instance' => array(
            'label' => t('Title'),
            'description' => t('The Title of the block.'),
            'required' => FALSE,
            'settings' => array(
              'text_processing' => 0,
            ),
            'widget' => array(
              'weight' => -5,
            ),
          ),
        ),
      ),
      'translation' => array(
        'entity_translation' => array(
          'class' => 'EntityTranslationBeanHandler',
          'base path' => 'block/%bean_delta',
          'path wildcard' => '%bean_delta',
          'path schemes' => array(
            'default' => array(),
          ),
        ),
      ),
    ),
  );

  foreach (bean_get_types() as $type) {
    if (!empty($type)) {
      $return['bean']['bundles'][$type->type] = array(
        'label' => $type->getLabel(),
        'description' => $type->getDescription(),
        'admin' => array(
          'path' => 'admin/structure/block-types/manage/%bean_type',
          'real path' => 'admin/structure/block-types/manage/' . $type->buildURL(),
          'bundle argument' => 4,
          'access arguments' => array('administer bean types'),
        ),
      );
    }
  }

  return $return;
}

/**
 * Implements hook_menu().
 */
function bean_menu() {
  $items = array();

  $items['block/add'] = array(
    'title' => 'Add Block',
    'page callback' => 'bean_add_page',
    'access arguments' => array('add'),
    'access callback' => 'bean_add_page_access',
    'file' => 'includes/bean.pages.inc',
  );

  foreach (bean_get_types() as $type) {
    if (!empty($type)) {
      $items['block/add/' . $type->buildURL()] = array(
        'title' => $type->getLabel(),
        'title callback' => 'check_plain',
        'page callback' => 'bean_add',
        'page arguments' => array($type->type),
        'access arguments' => array('create any ' . $type->type . ' bean'),
        'file' => 'includes/bean.pages.inc',
      );
    }
  }

  $items['block/%bean_delta'] = array(
    'title' => 'Block',
    'page callback' => 'bean_view_page',
    'page arguments' => array(1),
    'access arguments' => array('view bean page'),
    'file' => 'includes/bean.pages.inc',
  );

  $items['block/%bean_delta/view'] = array(
    'title' => 'View',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'context' => MENU_CONTEXT_NONE,
    'weight' => -20,
  );


  $items['block/%bean_delta/edit'] = array(
    'title' => 'Edit Block',
    'type' => MENU_LOCAL_TASK,
    'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
    'page callback' => 'bean_edit',
    'page arguments' => array(1),
    'access arguments' => array('update', 'bean', 1),
    'access callback' => 'entity_access',
    'file' => 'includes/bean.pages.inc',
  );

  $items['block/%bean_delta/delete'] = array(
    'title' => 'Delete Block',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('bean_delete_confirm', 1),
    'access callback' => 'entity_access',
    'access arguments' => array('delete', 'bean', 1),
    'weight' => 1,
    'type' => MENU_LOCAL_TASK,
    'context' => MENU_CONTEXT_NONE,
    'file' => 'includes/bean.pages.inc',
  );

  $items['block/%bean_delta/revisions'] = array(
    'title' => 'Revisions',
    'page callback' => 'bean_revisions_page',
    'page arguments' => array(1),
    'access arguments' => array('view bean revisions'),
    'file' => 'includes/bean.pages.inc',
    'type' => MENU_LOCAL_TASK,
  );

  $items['block/%bean_delta/revisions/%'] = array(
    'title callback' => 'bean_revision_title',
    'title arguments' => array(1),
    'page callback' => 'bean_view',
    'page arguments' => array(1),
    'access callback' => 'entity_access',
    'access arguments' => array('view', 'bean', 1),
    'file' => 'includes/bean.pages.inc',
    'load arguments' => array(3),
  );

  $items['block/%bean_delta/revisions/%/view'] = array(
    'title' => 'View',
    'page callback' => 'bean_view',
    'page arguments' => array(1),
    'access callback' => 'entity_access',
    'access arguments' => array('view', 'bean', 1),
    'file' => 'includes/bean.pages.inc',
    'load arguments' => array(3),
    'weight' => -10,
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'context' => MENU_CONTEXT_NONE,
  );

  $items['block/%bean_delta/revisions/%/edit'] = array(
    'title' => 'Edit',
    'page callback' => 'bean_edit',
    'page arguments' => array(1),
    'access callback' => 'entity_access',
    'access arguments' => array('update', 'bean', 1),
    'file' => 'includes/bean.pages.inc',
    'load arguments' => array(3),
    'type' => MENU_LOCAL_TASK,
    'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
  );

  $items['block/%bean_delta/revisions/%/set-active'] = array(
    'title' => 'Set active',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('bean_set_default_confirm', 1),
    'access callback' => 'entity_access',
    'access arguments' => array('update', 'bean', 1),
    'file' => 'includes/bean.pages.inc',
    'load arguments' => array(3),
    'type' => MENU_LOCAL_TASK,
    'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
  );

  $items['block/%bean_delta/revisions/%/delete'] = array(
    'title' => 'Delete',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('bean_delete_revision_confirm', 1),
    'access callback' => 'entity_access',
    'access arguments' => array('delete', 'bean', 1),
    'file' => 'includes/bean.pages.inc',
    'load arguments' => array(3),
    'weight' => 1,
    'type' => MENU_LOCAL_TASK,
    'context' => MENU_CONTEXT_NONE,
  );

  $items['admin/content/blocks'] = array(
    'title' => 'Blocks',
    'description' => 'Manage blocks used on your site.',
    'page callback' => 'bean_list',
    'access arguments' => array('access bean overview'),
    'file' => 'includes/bean.pages.inc',
    'type' => MENU_LOCAL_TASK | MENU_NORMAL_ITEM,
  );

  if (module_exists('devel')) {
    $devel_path = drupal_get_path('module', 'devel');
    $items['block/%bean_delta/devel'] = array(
      'title' => 'Devel',
      'page callback' => 'devel_load_object',
      'page arguments' => array('bean', 1),
      'access arguments' => array('administer beans'),
      'type' => MENU_LOCAL_TASK,
      'file' => 'devel.pages.inc',
      'file path' => $devel_path,
      'weight' => 100,
    );
    $items['block/%bean_delta/devel/load'] = array(
      'title' => 'Load',
      'type' => MENU_DEFAULT_LOCAL_TASK,
    );
    $items['block/%bean_delta/devel/render'] = array(
      'title' => 'Render',
      'page callback' => 'devel_render_object',
      'page arguments' => array('bean', 1),
      'access arguments' => array('administer beans'),
      'file' => 'devel.pages.inc',
      'file path' => $devel_path,
      'type' => MENU_LOCAL_TASK,
      'weight' => 100,
    );
  }

  return $items;
}

/**
 * Title callback
 */
function bean_revision_title($bean) {
  return $bean->label();
}

/**
 * Implements hook_admin_paths().
 */
function bean_admin_paths() {
  if (variable_get('node_admin_theme')) {
    $paths = array(
      'block/*/edit' => TRUE,
      'block/*/delete' => TRUE,
      'block/*/revisions' => TRUE,
      'block/*/revisions/*/edit' => TRUE,
      'block/*/revisions/*/set-active' => TRUE,
      'block/*/revisions/*/delete' => TRUE,
      'block/add' => TRUE,
      'block/add/*' => TRUE,
    );
    return $paths;
  }
}

/**
 * Title of the bean
 */
function bean_page_title($bean) {
  return $bean->label;
}

/**
 * Menu Argument Loader
 */
function bean_type_load($type) {
  return bean_load_plugin_class(strtr($type, array('-' => '_')));
}

/**
 * Menu Argument Loader for panelizer tab.
 */
function bean_type_panelizer_load($type) {
  $type = bean_load_plugin_class(strtr($type, array('-' => '_')));
  return $type->type;
}

/**
 * Gets an array of all bean types, keyed by the type name.
 *
 * @return BeanType
 *   Depending whether $type isset, an array of bean types or a single one.
 */
function bean_get_types() {
  $bean_types = &drupal_static(__FUNCTION__);

  if (empty($bean_types)) {
    $bean_types = bean_load_plugin_class_all();
  }

  return $bean_types;
}

/**
 * Fetch the widget plugin info
 */
function bean_fetch_plugin_info($plugin = NULL) {
  $plugins = &drupal_static(__FUNCTION__);
  ctools_include('plugins');

  if (empty($plugins)) {
    if (($cache = cache_get('bean_plugins')) && !empty($cache->data)) {
      $plugins = $cache->data;
    }
    else {
      $plugins = ctools_get_plugins('bean', 'types');

      // Only use modules with the same version.
      $allowed_modules = array_keys(ctools_plugin_api_info('bean', 'types', bean_min_version(), bean_current_version()));
      foreach ($plugins as $key => $plugin_value) {
        if (!in_array($plugin_value['module'], $allowed_modules) || ($plugin_value['abstract'])) {
          unset($plugins[$key]);
        }
      }
      cache_set('bean_plugins', $plugins);
    }
  }

  if (empty($plugin)) {
    return $plugins;
  }
  else {
    // Make sure the plugin is in the cache.
    if (!isset($plugins[$plugin])) {
      $plugin_info = ctools_get_plugins('bean', 'types', $plugin);
      if (empty($allowed_modules)) {
        $allowed_modules = array_keys(ctools_plugin_api_info('bean', 'types', bean_current_version(), bean_min_version()));
      }
      if (in_array($plugin_info['module'], $allowed_modules)) {
        $plugins[$plugin] = $plugin_info;

        cache_set('bean_plugins', $plugins);
      }
    }

    // If we still don't have the plugin then return NULL.
    if (empty($plugins[$plugin])) {
      return NULL;
    }
    return $plugins[$plugin];
  }
}

/**
 * Reset the static variables and caches.
 */
function bean_reset($skip_menu_rebuild = FALSE, $rehash_blocks= FALSE) {
  // Clear ctools cache.
  ctools_include('plugins');
  ctools_get_plugins_reset();
  cache_clear_all('plugins:bean:types', 'cache');
  cache_clear_all('ctools_plugin_files', 'cache', TRUE);

  // Invoke modules to clear their caches.
  foreach (module_implements('bean_cache_clear') as $module) {
    module_invoke($module, 'bean_cache_clear');
  }

  // Rebuild ctools plugin types and the menu.
  ctools_plugin_get_plugin_type_info(TRUE);
  if (!$skip_menu_rebuild) {
    menu_rebuild();
  }

  // Rehashes blocks for active themes. This will populate the blocks table and allow
  // modules like blockreference to work properly.
  if ($rehash_blocks && module_exists('block')) {
    block_flush_caches();
  }
}

/**
 * Implements hook_bean_cache_clear().
 */
function bean_bean_cache_clear() {
  // Clear static variables.
  drupal_static_reset('bean_access');
  drupal_static_reset('bean_get_types');
  drupal_static_reset('bean_get_all_beans');
  drupal_static_reset('bean_fetch_plugin_info');
  drupal_static_reset('bean_load_plugin_class');
  drupal_static_reset('bean_block_info');

  // Flush caches.
  cache_clear_all('bean_plugins', 'cache');

  if (module_exists('block')) {
    cache_clear_all(NULL, 'cache_block');
  }
  cache_clear_all(NULL, 'cache_page');
}

/**
 * Clear out cache_block table
 * @param  object $bean the bean object
 */
function bean_cache_clear_block($bean = null) {
  if (isset($bean->delta)) {
    cache_clear_all('bean:' . $bean->delta . ':', 'cache_block', TRUE);
  }
  else {
    cache_clear_all('bean:', 'cache_block', TRUE);
  }
}

/**
 * Load a widget class
 *
 * @param $plugin_key string
 *  the key fo the plugin
 * @return BeanTypePluginInterface | Boolean
 *  An instance of the bean class or FALSE if not loaded
 */
function bean_load_plugin_class($plugin_key = NULL) {
  $cache = &drupal_static(__FUNCTION__);

  if (!isset($cache[$plugin_key])) {
    ctools_include('plugins');
    $class = ctools_plugin_load_class('bean', 'types', $plugin_key, 'handler');
    if ($class && bean_check_plugin_class($class)) {
      $cache[$plugin_key] = new $class(bean_fetch_plugin_info($plugin_key));
    }
  }

  return isset($cache[$plugin_key]) ? $cache[$plugin_key] : FALSE;
}

/**
 * Check the plugin type class
 *
 * @param $class string
 *  The name of the bean type class
 * @return Boolean
 */
function bean_check_plugin_class($class) {
  $ref_class = new ReflectionClass($class);
  if ($ref_class->isInstantiable() && in_array('BeanTypePluginInterface', $ref_class->getInterfaceNames())) {
    return TRUE;
  }

  return FALSE;
}

/**
 * Load all widget classes
 */
function bean_load_plugin_class_all() {
  $return = array();

  foreach (bean_fetch_plugin_info() as $plugin) {
    if (!empty($plugin['name']) && $plugin['name'] !== 'bean' && $plugin_class = bean_load_plugin_class($plugin['name'])) {
      $return[$plugin['name']] = $plugin_class;
    }
  }

  return $return;
}

/**
 * Load all beans
 */
function bean_get_all_beans() {
  $beans = &drupal_static(__FUNCTION__);

  if (!isset($beans)) {
    $query = new EntityFieldQuery();
    $result = $query
      ->entityCondition('entity_type', 'bean')
      ->execute();
    if (isset($result['bean'])) {
      $beans = bean_load_multiple(array_keys($result['bean']));
    }
    else {
      $beans = array();
    }
  }

  return $beans;
}

/**
 * Fetch a bean object.
 *
 * @param $bid
 *   Integer specifying the bean id.
 * @param $reset
 *   A boolean indicating that the internal cache should be reset.
 * @return
 *   A fully-loaded $bean object or FALSE if it cannot be loaded.
 *
 * @see bean_load_multiple()
 */
function bean_load($bid, $reset = FALSE) {
  $beans = bean_load_multiple(array($bid), array(), $reset);
  return reset($beans);
}

/**
 * Fetch a bean revision
 *
 * A new function to not break the bean_load API
 */
function bean_load_revision($bid, $vid, $reset = FALSE) {
  $conditions = array('vid' => $vid);
  $beans = bean_load_multiple(array($bid), $conditions, $reset);
  return reset($beans);
}

/**
 * Fetch a bean object by its delta.
 *
 * @param $delta
 *   String specifying the bean delta.
 * @param $reset
 *   A boolean indicating that the internal cache should be reset.
 * @return Bean
 *   A fully-loaded $bean object or FALSE if it cannot be loaded.
 */
function bean_load_delta($delta, $reset = FALSE, $revision = NULL) {
  static $deltas = array();
  if (!$reset && !$revision && isset($deltas[$delta])) {
    return $deltas[$delta];
  }

  $result = db_select('bean', 'b')
    ->fields('b', array('bid'))
    ->condition('delta', $delta)
    ->execute();
  if ($bid = $result->fetchField()) {
    if ($revision) {
      return bean_load_revision($bid, $revision, $reset);
    }
    else {
      $deltas[$delta] = bean_load($bid, $reset);
      return $deltas[$delta];
    }
  }
  return FALSE;
}

/**
 * Menu callback to load a bean by the delta
 */
function bean_delta_load($delta, $revision = NULL) {
  return bean_load_delta($delta, FALSE, $revision);
}

/**
 * Load multiple beans based on certain conditions.
 *
 * @param $bids
 *   An array of bean IDs.
 * @param $conditions
 *   An array of conditions to match against the {bean} table.
 * @param $reset
 *   A boolean indicating that the internal cache should be reset.
 * @return
 *   An array of bean objects, indexed by pid.
 *
 * @see entity_load()
 * @see bean_load()
 */
function bean_load_multiple($bids = array(), $conditions = array(), $reset = FALSE) {
  return entity_load('bean', $bids, $conditions, $reset);
}

/**
 * Delete a bean revision
 */
function bean_delete_revision($vid) {
  return entity_get_controller('bean')->deleteRevision($vid);
}

/**
 * Implements hook_permissions().
 */
function bean_permission() {
  $perms = array(
    'administer bean types' => array(
      'title' => t('Administer block types'),
      'description' => t('Create and delete fields on beans, and set their permissions.'),
    ),
    'administer beans' => array(
      'title' => t('Administer beans'),
      'description' => t('Delete, add, edit and view all beans.'),
    ),
    'access bean overview' => array(
      'title' => t('Access the Bean overview page'),
      'description' => t('Visit !url', array('!url' => "admin/content/blocks")),
    ),
    'edit bean view mode' => array(
      'title' => t('Change the View Mode of the Bean'),
      'description' => t('Ability to change the view mode on the bean form'),
    ),
    'view bean page' => array(
      'title' => t('View Bean page'),
      'description' => t('Visit !url', array('!url' => 'block/< delta >')),
    ),
    'administer bean settings' => array(
      'title' => t('Administer Bean Settings'),
    ),
    'view bean revisions' => array(
      'title' => t('View Bean revisions'),
    ),
  );

  // Add a Permission for each entity type.
  bean_reset(TRUE);
  foreach (bean_get_types() as $bean_type) {
    $bean_type_name = check_plain($bean_type->type);
    $perms += array(
      "create any $bean_type_name bean" => array(
        'title' => t('%type_name: Add Bean', array('%type_name' => $bean_type->getLabel())),
      ),
      "edit any $bean_type_name bean" => array(
        'title' => t('%type_name: Edit Bean', array('%type_name' => $bean_type->getLabel())),
      ),
      "view any $bean_type_name bean" => array(
        'title' => t('%type_name: View Bean', array('%type_name' => $bean_type->getLabel())),
      ),
      "delete any $bean_type_name bean" => array(
        'title' => t('%type_name: Delete Bean', array('%type_name' => $bean_type->getLabel())),
      ),
    );
  }

  return $perms;
}

/**
 * Access callback for the entity API.
 */
function bean_type_access($op, $type = NULL, $account = NULL) {
  return user_access('administer bean types', $account);
}

/**
 * Determines whether the given user has access to a bean.
 *
 * @param $op
 *   The operation being performed. One of 'view', 'update', 'create', 'delete'
 *   or just 'edit' (being the same as 'create' or 'update').
 * @param $bean
 *   Optionally a bean or a bean type to check access for. If nothing is
 *   given, access for all beans is determined.
 * @param $account
 *   The user to check for. Leave it to NULL to check for the current user.
 * @return boolean
 *   Whether access is allowed or not.
 */
function bean_access($op, $bean = NULL, $account = NULL) {
  $rights = &drupal_static(__FUNCTION__, array());

  // Only real permissions are view, delete, create and edit
  switch ($op) {
    case 'view':
    case 'delete':
    case 'create':
      $op = $op;
      break;
    case 'add':
      $op = 'create';
      break;
    default:
      $op = 'edit';
  }

  // If no user object is supplied, the access check is for the current user.
  if (empty($account)) {
    $account = $GLOBALS['user'];
  }

  $cid = is_object($bean) ? $bean->bid : $bean;

  // If we've already checked access for this node, user and op, return from cache.
  if (isset($rights[$account->uid][$cid][$op])) {
    return $rights[$account->uid][$cid][$op];
  }

  if (user_access('administer beans', $account)) {
    return TRUE;
  }

  // We grant access to the bean if both of the following conditions are met:
  // - No modules say to deny access.
  // - At least one module says to grant access.
  // If no module specified either allow or deny, we fall back to the default.
  $access = module_invoke_all('bean_access', $bean, $op, $account);
  if (in_array(FALSE, $access, TRUE)) {
    $rights[$account->uid][$cid][$op] = FALSE;
    return FALSE;
  }
  elseif (in_array(TRUE, $access, TRUE)) {
    $rights[$account->uid][$cid][$op] = TRUE;
    return TRUE;
  }

  if (isset($bean) && isset($bean->type)) {
    if (user_access("$op any {$bean->type} bean", $account)) {
      $rights[$account->uid][$cid][$op] = TRUE;
      return TRUE;
    }
  }
  elseif (isset($bean) && is_string($bean)) {
    if (user_access("$op any {$bean} bean", $account)) {
      $rights[$account->uid][$cid][$op] = TRUE;
      return TRUE;
    }
  }
  else {
    // Here we are looking for access to any of the types.
    foreach (bean_get_types() as $bean_type) {
      $perm = $op . ' any ' . $bean_type->type . ' bean';
      if (user_access($perm, $account)) {
        $rights[$account->uid][$cid][$op] = TRUE;
        return TRUE;
      }
    }
  }

  return FALSE;
}

/**
 * Access callback for the general Bean add page (block/add).
 */
function bean_add_page_access() {
  if (user_access('administer bean types') || user_access('administer beans')) {
    return TRUE;
  }
  foreach (bean_get_types() as $bean_type) {
    if (entity_access('create', 'bean', $bean_type->type)) {
      return TRUE;
    }
  }
  return FALSE;
}

/**
 * Inserts or updates a bean object into the database.
 *
 * @param $bean
 *   The bean object to be inserted.
 *
 * @return
 *   Failure to write a record will return FALSE. Otherwise SAVED_NEW or
 *   SAVED_UPDATED is returned depending on the operation performed.
 */
function bean_save(Bean $bean) {
  return $bean->save();
}

/**
 * Deletes an existing bean.
 *
 * @param $bean
 *   The message object to be bean.
 */
function bean_delete(Bean $bean) {
  return $bean->delete();
}

/**
 * Helper to easily create a bean.
 *
 * @param $values array
 *   Array with the following keys:
 *   - "arguments" - Array with arguments that should be replaced on run time in
 *     the message type.
 *
 * @return Bean
 */
function bean_create($values) {
  return entity_create('bean', $values);
}

/**
 * View the Bean
 */
function bean_view(Bean $bean, $view_mode = 'default', $langcode = NULL) {
  if (!isset($langcode)) {
    $langcode = $GLOBALS['language_content']->language;
  }

  return $bean->view($view_mode, $langcode);
}

/**
 * View callback
 */
function bean_view_callback($entities, $view_mode, $langcode, $entity_type) {
  $render = array();
  foreach($entities as $key => $entity) {
    $id = entity_id($entity_type, $entity);
    $render[$entity_type][$id] = bean_view($entity, $view_mode, $langcode);
  }
  return $render;
}

/**
 * Implements hook_block_info().
 */
function bean_block_info() {
  $blocks = &drupal_static(__FUNCTION__, array());

  if (!empty($blocks)) {
    return $blocks;
  }

  $blocks = array();
  $beans = bean_get_all_beans();
  foreach ($beans as $bean) {
    $blocks[$bean->delta] = array(
      'info' => $bean->adminTitle(),
      'cache' => $bean->getInfo('cache_level'),
    );
  }

  return $blocks;
}

/**
 * Implements hook_block_view().
 */
function bean_block_view($delta = '') {
  $return = array();
  $bean = bean_load_delta($delta);

  if (entity_access('view', 'bean', $bean) && $bean) {
    $content = $bean->view($bean->view_mode);
    if (!is_array($content)) {
      $content = array(
        '#markup' => $content,
      );
    }

    $return = array(
      'content' => $content,
      'subject' => isset($bean->title) ? filter_xss($bean->title, $allowed_tags = array('em', 'strong', 'i', 'b')) : '',
    );
  }

  if (!empty($return['content']) && module_exists('contextual')) {
    $return['content']['#contextual_links']['bean'] = array(
      'block', array($bean->Identifier(), 'edit')
    );
  }

  return $return;
}

/**
 * Define the name of the api hook
 */
function bean_types_hook_name() {
  return 'bean_types_api_info';
}

/**
 * The current version of the API
 */
function bean_current_version() {
  return 5;
}

/**
 * The minimal version
 */
function bean_min_version() {
  return 4;
}

/**
 * Implements hook_bean_types_api_info().
 */
function bean_bean_types_api_info() {
  return array(
    'api' => bean_current_version(),
  );
}

/**
 * Implements hook_ctools_plugin_type().
 */
function bean_ctools_plugin_type() {
  return array(
    'types' => array(
      'cache' => TRUE,
      'use hooks' => TRUE,
      'classes' => array('handler'),
      'info file' => TRUE,
      'alterable' => TRUE,
      'defaults' => array(
        'abstract' => FALSE,
        'label' => t('Block'),
        'description' => '',
        'cache_level' => DRUPAL_CACHE_PER_ROLE,
        // Editable in the block type UI
        // THESE should have 'bean_custom' as the class
        'editable' => FALSE,
        'view_mode' => 'default',
        'handler' => array(
          'class' => 'BeanDefault',
          'parent' => 'bean',
          'file' => 'BeanDefault.class.php',
          'path' => drupal_get_path('module', 'bean') . '/plugins',
        ),
      ),
    ),
  );
}

/**
 * Implements hook_bean_types().
 */
function bean_bean_types() {
  $plugins = array();

  $plugins['bean'] = array(
    'handler' => array(
      'class' => 'BeanPlugin',
      'file' => 'BeanPlugin.class.php',
      'path' => drupal_get_path('module', 'bean') . '/plugins',
    ),
  );

  $plugins['bean_default'] = array(
    'abstract' => TRUE,
    'handler' => array(
      'class' => 'BeanDefault',
      'parent' => 'bean',
      'file' => 'BeanDefault.class.php',
      'path' => drupal_get_path('module', 'bean') . '/plugins',
    ),
  );

  return $plugins;
}

/**
 * Implements hook_theme().
 */
function bean_theme() {
  return array(
    'bean' => array(
      'render element' => 'entity',
      'template' => 'bean',
    ),
    'bean_add_list' => array(
      'variables' => array('content' => NULL),
    ),
  );
}

/**
 * Implements hook_menu_local_tasks_alter().
 */
function bean_menu_local_tasks_alter(&$data, $router_item, $root_path) {
  // Add action link to 'block/add' on 'admin/content/blocks' page.
  if ($root_path == 'admin/content/blocks' && bean_get_types()) {
    $item = menu_get_item('block/add');
    if ($item['access']) {
      $data['actions']['output'][] = array(
        '#theme' => 'menu_local_action',
        '#link' => $item,
      );
    }
  }
}

/**
 * Implements hook_workbench_create_alter().
 */
function bean_workbench_create_alter(&$output) {
  $items = array();
  $i = 0;
  foreach (bean_get_types() as $bean_type) {
    if (entity_access('create', 'bean', $bean_type->type)) {
      $items[str_replace(' ', '', $bean_type->getLabel()) . '_' . $i] = array(
        'title' => $bean_type->getLabel(),
        'href' => 'block/add/' . $bean_type->buildURL(),
        'description' => $bean_type->getDescription(),
        'localized_options' => array(),
        'page_arguments' => serialize(array('bean')),
      );
      $i++;
    }
  }

  if (!empty($items)) {
    ksort($items);

    $output['bean_types'] = array(
      '#title' => t('Create Blocks'),
      '#markup' => theme('node_add_list', array('content' => $items)),
      '#theme' => 'workbench_element',
    );
  }
}

/**
 * Implements hook_modules_enabled()
 */
function bean_modules_enabled($modules) {
  bean_reset(TRUE);
}

/**
 * Implements hook_field_extra_fields().
 */
function bean_field_extra_fields() {
  $extra = array();
  foreach (bean_get_types() as $type) {
    $extra['bean'][$type->type]['display']['title'] = array(
      'label' => t('Title'),
      'description' => t('Bean module element'),
      'weight' => -9,
    );
    $extra['bean'][$type->type]['form']['label'] = array(
      'label' => t('Label'),
      'description' => t('Bean module element'),
      'weight' => -10,
    );
    $extra['bean'][$type->type]['form']['title'] = array(
      'label' => t('Title'),
      'description' => t('Bean module element'),
      'weight' => -9,
    );
    $extra['bean'][$type->type]['form']['revision'] = array(
      'label' => t('Revision settings'),
      'description' => t('Bean module element'),
      'weight' => 10,
    );
    $extra['bean'][$type->type]['form']['view_mode'] = array(
      'label' => t('View mode'),
      'description' => t('Bean module element'),
      'weight' => 10,
     );
  }
  return $extra;
}

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

/**
 * Implements hook_flush_caches().
 */
function bean_flush_caches() {
  bean_reset(TRUE);
  return array();
}

/**
 * Implements hook_admin_menu_output_build().
 *
 * Places the "Add block" tree into the toolbar from Admin Menu module
 *
 * @see admin_menu_admin_menu_output_build()
 */
function bean_admin_menu_output_build(&$content) {
  if (!isset($content['menu'])) {
    return;
  }

  // Retrieve the "Add block" link tree.
  $link = db_query("SELECT * FROM {menu_links} WHERE router_path = 'block/add' AND module = 'system'")->fetchAssoc();
  $conditions = array();
  for ($i = 1; $i < MENU_MAX_DEPTH; $i++) {
    if (!empty($link["p$i"])) {
      $conditions["p$i"] = $link["p$i"];
    }
  }
  $tree = menu_build_tree($link['menu_name'], array(
    'conditions' => $conditions,
    'min_depth' => $link['depth'],
  ));
  $links = admin_menu_links_menu($tree);
  if (!empty($links)) {
    // If the user has access to the top-level "Content" category, insert the
    // "Add content" link tree there.
    if (isset($content['menu']['admin/content'])) {
      $content['menu']['admin/content'] += $links;
    }
    // Otherwise insert "Add blocks" as top-level category.
    else {
      $key = key($links);
      $links[$key]['#weight'] = -100;
      $content['menu'] += $links;
    }
  }
}

/**
 * CTools callback for Bean blocks.
 *
 * @see _ctools_block_content_type_content_type() in block.inc.
 */
function bean_ctools_block_info($module, $delta, &$info) {
  // Use the core block icon.
  $info['icon'] = 'icon_contrib_block.png';

  // Load the full bean.
  $bean = bean_load_delta($delta);

  // Identify the bean type.
  $bean_types = bean_get_types();

  // By default store all beans in the Blocks category.
  $info['category'] = t('Blocks');

  // If the bean type loaded correctly (should always happen),
  if (isset($bean_types[$bean->type])) {
    // Optionally group the blocks by their bean type..
    if (variable_get('bean_ctools_separate', TRUE)) {
      $info['category'] = t('Blocks') . ': ' . $bean_types[$bean->type]->getLabel();
    }
    // Optionally prefix the block with the bean type's label.
    if (variable_get('bean_ctools_prefix', FALSE)) {
      $info['title'] = $bean_types[$bean->type]->getLabel() . ': ' . $info['title'];
    }
  }
}

/**
 * Implements hook_ctools_plugin_directory().
 *
 * Inform ctools about our panelizer plugin.
 */
function bean_ctools_plugin_directory($module, $plugin) {
  if (in_array($module, array('panelizer'))) {
    return 'plugins/' . $plugin;
  }
}
