You are here

fieldable_panels_pane.inc in Fieldable Panels Panes (FPP) 7

CTools content type to render a fielded panel pane.

File

plugins/content_types/fieldable_panels_pane.inc
View source
<?php

/**
 * @file
 * CTools content type to render a fielded panel pane.
 */

/**
 * Small hook implementation of plugin.
 *
 * We have to use this because the form here can be loaded via form caching and
 * if this .inc file is loaded before the plugin is requested, the $plugin =
 * array() notation doesn't work.
 */
function fieldable_panels_panes_fieldable_panels_pane_ctools_content_types() {
  return array(
    'title' => t('Fielded custom content'),
    'no title override' => TRUE,
    'description' => t('Create custom panels pane with fields'),
    'category' => t('Fielded panes'),
    'all contexts' => TRUE,
    'defaults' => array(
      'view_mode' => 'full',
    ),
    // Callback to load the FPP.
    'content type' => 'fieldable_panels_panes_fieldable_panels_pane_content_type',
    // Functionality for editing an FPP.
    'edit text' => t('Edit'),
    // Form API callback.
    'edit form' => 'fieldable_panels_panes_fieldable_panels_pane_content_type_edit_form',
    // Access callback.
    'check editable' => 'fieldable_panels_pane_content_type_edit_form_access',
  );
}

// --------------------------------------------------------------------------
// Callbacks, many of them automatically named, for rendering content.

/**
 * Return an individual FPP.
 */
function fieldable_panels_panes_fieldable_panels_pane_content_type($subtype_id, $plugin) {
  $content_type = array();

  // If an ID was passed in, try loading a corresponding FPP.
  if (strpos($subtype_id, ':') !== FALSE) {

    // If a specific FPP was requested, load it. Use the 'force' method to
    // avoid inadvertently triggering an infinite loop.
    $entity = fieldable_panels_panes_load_from_subtype_force($subtype_id);

    // The FPP could be loaded, so generate its content type definition.
    if (!empty($entity)) {
      $content_type = _fieldable_panels_panes_custom_content_type($entity);
    }
    else {
      return $content_type;
    }
  }

  // If nothing was loaded yet.
  if (empty($content_type)) {
    $type = 'fieldable_panels_pane';
    $subtypes = ctools_content_get_subtypes($type);
    if (isset($subtypes[$subtype_id])) {
      $content_type = $subtypes[$subtype_id];
    }

    // If there's only 1 and we somehow have the wrong subtype ID, do not
    // care. Return the proper subtype anyway.
    if (empty($content_type) && !empty($plugin['single'])) {
      $content_type = current($subtypes);
    }
  }

  // Trigger hook_fieldable_panels_pane_content_type_alter().
  drupal_alter('fieldable_panels_pane_content_type', $content_type, $subtype_id, $plugin);
  return $content_type;
}

/**
 * Callback to return the custom content types with the specified $subtype_name.
 */
function fieldable_panels_panes_fieldable_panels_pane_content_type_content_type($subtype_name) {
  $types = _fieldable_panels_panes_default_content_type();
  if (isset($types[$subtype_name])) {
    return $types[$subtype_name];
  }
  else {
    $entity = fieldable_panels_panes_load_from_subtype($subtype_name);
    if (!empty($entity)) {
      return _fieldable_panels_panes_custom_content_type($entity);
    }
  }
}

/**
 * Callback to return all custom content types available.
 */
function fieldable_panels_panes_fieldable_panels_pane_content_type_content_types() {
  $types = _fieldable_panels_panes_default_content_type();

  // The current function is called as part of the standard panel pane rendering
  // pipeline. To prevent a full list of FPP entities from being generated every
  // time that a panel is rendered, generate the list only when a request is
  // made from an administrative URL or Panel Nodes pages.
  // @see https://www.drupal.org/node/2374577
  if (path_is_admin(current_path()) || preg_match('#(panels|panel_content$)#i', current_path())) {
    $fpp_info = entity_get_info('fieldable_panels_pane');
    foreach ($fpp_info['bundles'] as $bundle_name => $bundle) {
      $ids = db_query('SELECT fpid FROM {fieldable_panels_panes} WHERE reusable = 1 AND bundle = :bundle', array(
        ':bundle' => $bundle_name,
      ))
        ->fetchCol();
      if (!empty($ids)) {
        $types = array_merge($types, fieldable_panels_panes_build_content_type_info($bundle_name, $ids));
      }
    }
  }
  return $types;
}

/**
 * Returns a list of all reusable FPP entities of a given bundle.
 *
 * @param string $bundle
 *   Fieldable panel pane bundle machine name.
 * @param array $ids
 *   An array of fieldable panel pane entity ids.
 *
 * @return array
 *   An array of content type information for each fpp entity.
 */
function fieldable_panels_panes_build_content_type_info($bundle, array $ids) {
  $types = array();
  $cid = "fieldable_panels_panes_{$bundle}_content_type";
  if (($cache = cache_get($cid, 'cache_panels')) && !empty($cache->data)) {
    $types = $cache->data;
  }
  else {
    $entities = fieldable_panels_panes_load_multiple($ids);
    if (!empty($entities)) {
      foreach ($entities as $entity_id => $entity) {
        $subtype = _fieldable_panels_panes_custom_content_type($entity);
        $types[$subtype['name']] = $subtype;

        // Re-key $entities such that $types and $entities are keyed
        // identically.
        $entities[$subtype['name']] = $entity;
        unset($entities[$entity_id]);
      }
    }

    // Trigger hook_fieldable_panels_panes_content_types_alter().
    drupal_alter('fieldable_panels_panes_content_types', $types, $bundle, $entities);
    cache_set($cid, $types, 'cache_panels');
  }
  return $types;
}

/**
 * Callback to render our content type.
 *
 * Note: don't add the 'array' type hint for $panel_args or $context as they
 * might be NULL.
 */
function fieldable_panels_panes_fieldable_panels_pane_content_type_render($subtype, $conf, $panel_args = array(), $context = array()) {
  $entity = fieldable_panels_panes_load_from_subtype($subtype);
  if (!empty($entity) && !empty($entity->fpid) && fieldable_panels_panes_access('view', $entity)) {
    $view_mode = isset($conf['view_mode']) ? $conf['view_mode'] : 'full';
    $settings = field_bundle_settings('fieldable_panels_pane', $entity->bundle);

    // Add pane config to the entity object before hook_view().
    $entity->conf = $conf;
    $block = new stdClass();
    $block->title = '';
    $block->content = fieldable_panels_panes_view($entity, $view_mode);

    // Was there a title defined?
    if (!empty($entity->title)) {

      // Is this view mode configured?
      $is_view_mode_set = isset($settings['extra_fields']['display']['title'][$view_mode]['visible']);

      // Should the default view mode show the title?
      $show_default_title = !empty($settings['extra_fields']['display']['title']['default']['visible']);

      // Is the title configured for this view mode?
      $show_view_mode_title = $is_view_mode_set && $settings['extra_fields']['display']['title'][$view_mode]['visible'];

      // Combine all of the above logic.
      if (empty($settings['extra_fields']['display']) || !$is_view_mode_set && $show_default_title || $show_view_mode_title) {
        $block->title = filter_xss_admin($entity->title);
      }
    }
    return $block;
  }
}

/**
 * Gets the admin title from the entity.
 */
function _fieldable_panels_panes_admin_title_from_entity($entity) {
  if (!empty($entity) && is_object($entity)) {
    $title = array();

    // Start with the entity bundle label.
    $info = entity_get_info('fieldable_panels_pane');
    if (!empty($info['bundles'][$entity->bundle]['label'])) {
      $title[] = $info['bundles'][$entity->bundle]['label'];
    }

    // Add the admin title, if it isn't the same as what was returned above.
    if (!empty($entity->admin_title)) {
      if (!in_array(trim($entity->admin_title), $title)) {
        $title[] = trim($entity->admin_title);
      }
    }
    elseif (!empty($entity->title)) {
      if (!in_array(trim($entity->title), $title)) {
        $title[] = trim($entity->title);
      }
    }

    // Make sure no XSS strings go through the title.
    $title = filter_xss(implode(': ', $title));

    // Indicate when the FPP is reusable.
    if (!empty($entity->reusable)) {
      $title .= ' (' . t('reusable') . ')';
    }
  }
  else {
    $title = t('Deleted/removed entity pane');
  }
  return $title;
}

/**
 * Callback to provide the administrative title of the custom content.
 */
function fieldable_panels_panes_fieldable_panels_pane_content_type_admin_title($subtype, $conf) {
  $entity = fieldable_panels_panes_load_from_subtype($subtype);
  return _fieldable_panels_panes_admin_title_from_entity($entity);
}

/**
 * Callback to provide administrative information for a fieldable panels pane.
 */
function fieldable_panels_panes_fieldable_panels_pane_content_type_admin_info($subtype, $conf) {
  return fieldable_panels_panes_fieldable_panels_pane_content_type_render($subtype, $conf);
}

/**
 * Form used to edit our content type.
 */
function fieldable_panels_panes_fieldable_panels_pane_content_type_edit_form($form, &$form_state) {
  $conf =& $form_state['conf'];
  if (!isset($form_state['entity'])) {
    $form_state['entity'] = fieldable_panels_panes_load_from_subtype($form_state['subtype_name']);
  }
  $entity = $form_state['entity'];

  // It's possible that we have a reference to an entity that is no longer
  // valid. If so, bail, because otherwise field API will whitescreen.
  if (empty($entity)) {
    $form['error'] = array(
      '#markup' => t('The pane entity referenced does not appear to be valid. It was probably deleted and you should remove this pane.'),
    );
    return $form;
  }
  ctools_form_include_file($form_state, $form_state['plugin']['path'] . '/' . $form_state['plugin']['file']);
  $entity_info = entity_get_info('fieldable_panels_pane');

  // Show all of the available view modes.
  foreach ($entity_info['view modes'] as $mode => $option) {
    $view_mode_options[$mode] = $option['label'];
  }
  $form['view_mode'] = array(
    '#title' => t('View mode'),
    '#type' => 'select',
    '#description' => t('Select a view mode for this pane.'),
    '#options' => $view_mode_options,
    '#default_value' => $conf['view_mode'],
  );

  // If we're adding a reusable type, the only thing we want on the form is
  // the view mode, so skip the rest.
  if ($form_state['op'] == 'add' && !empty($form_state['subtype']['entity_id'])) {
    $form_state['no update entity'] = TRUE;
    return $form;
  }
  $form = fieldable_panels_panes_entity_edit_form($form, $form_state);
  $form['reusable']['warning'] = array(
    '#markup' => '<div class="description">' . t('Note: Editing any value on a reusable pane will change the value everywhere this pane is used.') . '</div>',
  );
  return $form;
}

/**
 * Validate submission of our content type edit form.
 */
function fieldable_panels_panes_fieldable_panels_pane_content_type_edit_form_validate($form, &$form_state) {
  if (!empty($form_state['no update entity'])) {
    return;
  }
  if ($form_state['entity']) {
    fieldable_panels_panes_entity_edit_form_validate($form, $form_state);
  }
}

/**
 * Submit our content type edit form.
 */
function fieldable_panels_panes_fieldable_panels_pane_content_type_edit_form_submit($form, &$form_state) {
  $form_state['conf']['view_mode'] = $form_state['values']['view_mode'];
  if (!empty($form_state['no update entity'])) {
    return;
  }
  $entity = $form_state['entity'];
  if (!$entity) {
    return;
  }
  fieldable_panels_panes_entity_edit_form_submit($form, $form_state);

  // Determine how to handle revision locking.
  $revision_context_aware = fieldable_panels_panes_revision_is_lockable($entity);

  // If this is a new entity entity, or revision locking is enabled, look for a
  // specific ID to use.
  if (!empty($entity->is_new) || $revision_context_aware) {

    // If UUID is available, use it.
    if (module_exists('uuid') && isset($entity->uuid)) {
      if ($revision_context_aware) {
        $subtype = 'vuuid:' . $entity->vuuid;
      }
      else {
        $subtype = 'uuid:' . $entity->uuid;
      }
    }
    else {
      if ($revision_context_aware) {
        $subtype = 'vid:' . $entity->vid;
      }
      else {
        $subtype = 'fpid:' . $entity->fpid;
      }
    }
  }
  else {
    $subtype = 'current:' . $entity->fpid;
  }

  // @todo: This won't work if $form_state does not contain 'pane' which could
  // theoretically happen in a non-Panels use case. Not that anybody uses this
  // outside of Panels.
  $form_state['pane']->subtype = $subtype;
}

/**
 * Callback for the 'edit' permission.
 */
function fieldable_panels_pane_content_type_edit_form_access($content_type, $subtype, $view_mode = 'full') {
  $return = fieldable_panels_panes_check_access_update($subtype);

  // Trigger hook_fieldable_panels_pane_content_type_edit_form_access_alter().
  drupal_alter('fieldable_panels_pane_content_type_edit_form_access', $return, $content_type, $subtype, $view_mode);
  return $return;
}

// --------------------------------------------------------------------------
// Internal methods used by the above callbacks.

/**
 * Provide the default content types.
 *
 * These are all visible in the UI as the content types that allow a user
 * to add new panel pane entities that will then be stored in the database.
 */
function _fieldable_panels_panes_default_content_type() {
  $types = array();
  $entity_info = entity_get_info('fieldable_panels_pane');
  foreach ($entity_info['bundles'] as $bundle => $info) {
    $types[$bundle] = array(
      'name' => $bundle,
      'title' => $info['label'],
      'category' => !empty($info['pane category']) ? $info['pane category'] : t('Fielded panes'),
      'top level' => !empty($info['pane top level']) ? $info['pane top level'] : FALSE,
      'icon' => !empty($info['pane icon']) ? $info['pane icon'] : NULL,
      'description' => !empty($info['description']) ? t($info['description']) : t('Create a new custom entity.'),
      'all contexts' => TRUE,
      'bundle' => $bundle,
      'create content access' => 'fieldable_panels_panes_content_type_create_access',
    );
  }
  return $types;
}

/**
 * Return an info array for a specific custom content type.
 */
function _fieldable_panels_panes_custom_content_type($entity) {
  $info = array(
    'title' => _fieldable_panels_panes_admin_title_from_entity($entity),
    'description' => check_plain($entity->admin_description),
    'category' => $entity->category ? check_plain($entity->category) : t('Miscellaneous'),
    'all contexts' => TRUE,
    'icon' => 'icon_block_custom.png',
  );

  // Determine how to handle revision locking.
  $revision_context_aware = fieldable_panels_panes_revision_is_lockable($entity);

  // If UUID is available, use it.
  if (module_exists('uuid') && isset($entity->uuid)) {
    if ($revision_context_aware) {
      $info['name'] = 'vuuid:' . $entity->vuuid;
    }
    else {
      $info['name'] = 'uuid:' . $entity->uuid;
    }
  }
  else {
    if ($revision_context_aware) {
      $info['name'] = 'vid:' . $entity->vid;
    }
    else {
      $info['name'] = 'fpid:' . $entity->fpid;
    }
  }
  $info['entity_id'] = $info['name'];
  $info['bundle'] = $entity->bundle;
  return $info;
}

/**
 * Access callback for creating a new content type.
 */
function fieldable_panels_panes_content_type_create_access($type, $subtype) {
  return fieldable_panels_panes_access('create', $subtype['name']);
}

Functions

Namesort descending Description
fieldable_panels_panes_build_content_type_info Returns a list of all reusable FPP entities of a given bundle.
fieldable_panels_panes_content_type_create_access Access callback for creating a new content type.
fieldable_panels_panes_fieldable_panels_pane_content_type Return an individual FPP.
fieldable_panels_panes_fieldable_panels_pane_content_type_admin_info Callback to provide administrative information for a fieldable panels pane.
fieldable_panels_panes_fieldable_panels_pane_content_type_admin_title Callback to provide the administrative title of the custom content.
fieldable_panels_panes_fieldable_panels_pane_content_type_content_type Callback to return the custom content types with the specified $subtype_name.
fieldable_panels_panes_fieldable_panels_pane_content_type_content_types Callback to return all custom content types available.
fieldable_panels_panes_fieldable_panels_pane_content_type_edit_form Form used to edit our content type.
fieldable_panels_panes_fieldable_panels_pane_content_type_edit_form_submit Submit our content type edit form.
fieldable_panels_panes_fieldable_panels_pane_content_type_edit_form_validate Validate submission of our content type edit form.
fieldable_panels_panes_fieldable_panels_pane_content_type_render Callback to render our content type.
fieldable_panels_panes_fieldable_panels_pane_ctools_content_types Small hook implementation of plugin.
fieldable_panels_pane_content_type_edit_form_access Callback for the 'edit' permission.
_fieldable_panels_panes_admin_title_from_entity Gets the admin title from the entity.
_fieldable_panels_panes_custom_content_type Return an info array for a specific custom content type.
_fieldable_panels_panes_default_content_type Provide the default content types.