<?php

/**
 * @file
 * Implements the basic functionality required to create and display an entity.
 *
 * This example does not use the
 * @link http://drupal.org/project/entity Entity API module @endlink, which is
 * used by many entity implementations and is recommended by many.
 *
 * An example of use of creating and managing entities using the Entity API
 * module is provided in the
 * @link http://drupal.org/project/model Model Entity module @endlink.
 *
 * @todo: Reference the ronald_istos article series
 * @todo: Reference the Drupal module development book
 * @todo: Add a single field
 */

/**
 * @defgroup entity_example Example: Entity
 * @ingroup examples
 * @{
 * Example creating a core Entity API entity.
 *
 * Note that this example does not use or demonstrate the contrib Entity API,
 * which you can find here: http://drupal.org/project/entity
 */

/**
 * Implements hook_entity_info().
 *
 * This is the fundamental description of the entity.
 *
 * It provides a single entity with a single bundle and without revision
 * support.
 */
function entity_example_entity_info() {
  $info['entity_example_basic'] = array(
    // A human readable label to identify our entity.
    'label' => t('Example Basic Entity'),

    // The controller for our Entity, extending the Drupal core controller.
    'controller class' => 'EntityExampleBasicController',

    // The table for this entity defined in hook_schema()
    'base table' => 'entity_example_basic',

    // Returns the uri elements of an entity.
    'uri callback' => 'entity_example_basic_uri',

    // IF fieldable == FALSE, we can't attach fields.
    'fieldable' => TRUE,

    // entity_keys tells the controller what database fields are used for key
    // functions. It is not required if we don't have bundles or revisions.
    // Here we do not support a revision, so that entity key is omitted.
    'entity keys' => array(
      // The 'id' (basic_id here) is the unique id.
      'id' => 'basic_id' ,
      // Bundle will be determined by the 'bundle_type' field.
      'bundle' => 'bundle_type',
    ),
    'bundle keys' => array(
      'bundle' => 'bundle_type',
    ),

    // FALSE disables caching. Caching functionality is handled by Drupal core.
    'static cache' => TRUE,

    // Bundles are alternative groups of fields or configuration
    // associated with a base entity type.
    'bundles' => array(
      'first_example_bundle' => array(
        'label' => 'First example bundle',
        // 'admin' key is used by the Field UI to provide field and
        // display UI pages.
        'admin' => array(
          'path' => 'admin/structure/entity_example_basic/manage',
          'access arguments' => array('administer entity_example_basic entities'),
        ),
      ),
    ),
    // View modes allow entities to be displayed differently based on context.
    // As a demonstration we'll support "Tweaky", but we could have and support
    // multiple display modes.
    'view modes' => array(
      'tweaky' => array(
        'label' => t('Tweaky'),
        'custom settings' => FALSE,
      ),
    ),
  );

  return $info;
}

/**
 * Fetch a basic object.
 *
 * This function ends up being a shim between the menu system and
 * entity_example_basic_load_multiple().
 *
 * This function gets its name from the menu system's wildcard
 * naming conventions. For example, /path/%wildcard would end
 * up calling wildcard_load(%wildcard value). In our case defining
 * the path: examples/entity_example/basic/%entity_example_basic in
 * hook_menu() tells Drupal to call entity_example_basic_load().
 *
 * @param int $basic_id
 *   Integer specifying the basic entity id.
 * @param bool $reset
 *   A boolean indicating that the internal cache should be reset.
 *
 * @return object
 *   A fully-loaded $basic object or FALSE if it cannot be loaded.
 *
 * @see entity_example_basic_load_multiple()
 * @see entity_example_menu()
 */
function entity_example_basic_load($basic_id = NULL, $reset = FALSE) {
  $basic_ids = (isset($basic_id) ? array($basic_id) : array());
  $basic = entity_example_basic_load_multiple($basic_ids, array(), $reset);
  return $basic ? reset($basic) : FALSE;
}

/**
 * Loads multiple basic entities.
 *
 * We only need to pass this request along to entity_load(), which
 * will in turn call the load() method of our entity controller class.
 */
function entity_example_basic_load_multiple($basic_ids = FALSE, $conditions = array(), $reset = FALSE) {
  return entity_load('entity_example_basic', $basic_ids, $conditions, $reset);
}

/**
 * Implements the uri callback.
 */
function entity_example_basic_uri($basic) {
  return array(
    'path' => 'examples/entity_example/basic/' . $basic->basic_id,
  );
}

/**
 * Implements hook_menu().
 */
function entity_example_menu() {
  $items['examples/entity_example'] = array(
    'title' => 'Entity Example',
    'page callback' => 'entity_example_info_page',
    'access arguments' => array('view any entity_example_basic entity'),
  );

  // This provides a place for Field API to hang its own
  // interface and has to be the same as what was defined
  // in basic_entity_info() above.
  $items['admin/structure/entity_example_basic/manage'] = array(
    'title' => 'Administer entity_example_basic entity type',
    'page callback' => 'entity_example_basic_list_entities',
    'access arguments' => array('administer entity_example_basic entities'),
  );

  // Add example entities.
  $items['admin/structure/entity_example_basic/manage/add'] = array(
    'title' => 'Add an Entity Example Basic Entity',
    'page callback' => 'entity_example_basic_add',
    'access arguments' => array('create entity_example_basic entities'),
    'type' => MENU_LOCAL_ACTION,
  );

  // List of all entity_example_basic entities.
  $items['admin/structure/entity_example_basic/manage/list'] = array(
    'title' => 'List',
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );

  // The page to view our entities - needs to follow what
  // is defined in basic_uri and will use load_basic to retrieve
  // the necessary entity info.
  $items['examples/entity_example/basic/%entity_example_basic'] = array(
    'title callback' => 'entity_example_basic_title',
    'title arguments' => array(3),
    'page callback' => 'entity_example_basic_view',
    'page arguments' => array(3),
    'access arguments' => array('view any entity_example_basic entity'),
  );

  // 'View' tab for an individual entity page.
  $items['examples/entity_example/basic/%entity_example_basic/view'] = array(
    'title' => 'View',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );

  // 'Edit' tab for an individual entity page.
  $items['examples/entity_example/basic/%entity_example_basic/edit'] = array(
    'title' => 'Edit',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('entity_example_basic_form', 3),
    'access arguments' => array('edit any entity_example_basic entity'),
    'type' => MENU_LOCAL_TASK,
  );

  // Add example entities.
  $items['examples/entity_example/basic/add'] = array(
    'title' => 'Add an Entity Example Basic Entity',
    'page callback' => 'entity_example_basic_add',
    'access arguments' => array('create entity_example_basic entities'),
  );

  return $items;
}

/**
 * Basic information for the page.
 */
function entity_example_info_page() {
  $content['preface'] = array(
    '#type' => 'item',
    '#markup' => t('The entity example provides a simple example entity.'),
  );
  if (user_access('administer entity_example_basic entities')) {
    $content['preface']['#markup'] = t('You can administer these and add fields and change the view !link.',
      array('!link' => l(t('here'), 'admin/structure/entity_example_basic/manage'))
    );
  }
  $content['table'] = entity_example_basic_list_entities();

  return $content;
}

/**
 * Implements hook_permission().
 */
function entity_example_permission() {
  $permissions = array(
    'administer entity_example_basic entities' => array(
      'title' => t('Administer entity_example_basic entities'),
    ),
    'view any entity_example_basic entity' => array(
      'title' => t('View any Entity Example Basic entity'),
    ),
    'edit any entity_example_basic entity' => array(
      'title' => t('Edit any Entity Example Basic entity'),
    ),
    'create entity_example_basic entities' => array(
      'title' => t('Create Entity Example Basic Entities'),
    ),
  );
  return $permissions;
}

/**
 * Returns a render array with all entity_example_basic entities.
 *
 * In this basic example we know that there won't be many entities,
 * so we'll just load them all for display. See pager_example.module
 * to implement a pager. Most implementations would probably do this
 * with the contrib Entity API module, or a view using views module,
 * but we avoid using non-core features in the Examples project.
 *
 * @see pager_example.module
 */
function entity_example_basic_list_entities() {
  $content = array();
  // Load all of our entities.
  $entities = entity_example_basic_load_multiple();
  if (!empty($entities)) {
    foreach ($entities as $entity) {
      // Create tabular rows for our entities.
      $rows[] = array(
        'data' => array(
          'id' => $entity->basic_id,
          'item_description' => l($entity->item_description, 'examples/entity_example/basic/' . $entity->basic_id),
          'bundle' => $entity->bundle_type,
        ),
      );
    }
    // Put our entities into a themed table. See theme_table() for details.
    $content['entity_table'] = array(
      '#theme' => 'table',
      '#rows' => $rows,
      '#header' => array(t('ID'), t('Item Description'), t('Bundle')),
    );
  }
  else {
    // There were no entities. Tell the user.
    $content[] = array(
      '#type' => 'item',
      '#markup' => t('No entity_example_basic entities currently exist.'),
    );
  }
  return $content;
}

/**
 * Callback for a page title when this entity is displayed.
 */
function entity_example_basic_title($entity) {
  return t('Entity Example Basic (item_description=@item_description)', array('@item_description' => $entity->item_description));
}

/**
 * Menu callback to display an entity.
 *
 * As we load the entity for display, we're responsible for invoking a number
 * of hooks in their proper order.
 *
 * @see hook_entity_prepare_view()
 * @see hook_entity_view()
 * @see hook_entity_view_alter()
 */
function entity_example_basic_view($entity, $view_mode = 'tweaky') {
  // Our entity type, for convenience.
  $entity_type = 'entity_example_basic';
  // Start setting up the content.
  $entity->content = array(
    '#view_mode' => $view_mode,
  );
  // Build fields content - this is where the Field API really comes in to play.
  // The task has very little code here because it all gets taken care of by
  // field module.
  // field_attach_prepare_view() lets the fields load any data they need
  // before viewing.
  field_attach_prepare_view($entity_type, array($entity->basic_id => $entity),
    $view_mode);
  // We call entity_prepare_view() so it can invoke hook_entity_prepare_view()
  // for us.
  entity_prepare_view($entity_type, array($entity->basic_id => $entity));
  // Now field_attach_view() generates the content for the fields.
  $entity->content += field_attach_view($entity_type, $entity, $view_mode);

  // OK, Field API done, now we can set up some of our own data.
  $entity->content['created'] = array(
    '#type' => 'item',
    '#title' => t('Created date'),
    '#markup' => format_date($entity->created),
  );
  $entity->content['item_description'] = array(
    '#type' => 'item',
    '#title' => t('Item Description'),
    '#markup' => $entity->item_description,
  );

  // Now to invoke some hooks. We need the language code for
  // hook_entity_view(), so let's get that.
  global $language;
  $langcode = $language->language;
  // And now invoke hook_entity_view().
  module_invoke_all('entity_view', $entity, $entity_type, $view_mode,
    $langcode);
  // Now invoke hook_entity_view_alter().
  drupal_alter(array('entity_example_basic_view', 'entity_view'),
    $entity->content, $entity_type);

  // And finally return the content.
  return $entity->content;
}

/**
 * Implements hook_field_extra_fields().
 *
 * This exposes the "extra fields" (usually properties that can be configured
 * as if they were fields) of the entity as pseudo-fields
 * so that they get handled by the Entity and Field core functionality.
 * Node titles get treated in a similar manner.
 */
function entity_example_field_extra_fields() {
  $form_elements['item_description'] = array(
    'label' => t('Item Description'),
    'description' => t('Item Description (an extra form field)'),
    'weight' => -5,
  );
  $display_elements['created'] = array(
    'label' => t('Creation date'),
    'description' => t('Creation date (an extra display field)'),
    'weight' => 0,
  );
  $display_elements['item_description'] = array(
    'label' => t('Item Description'),
    'description' => t('Just like title, but trying to point out that it is a separate property'),
    'weight' => 0,
  );

  // Since we have only one bundle type, we'll just provide the extra_fields
  // for it here.
  $extra_fields['entity_example_basic']['first_example_bundle']['form'] = $form_elements;
  $extra_fields['entity_example_basic']['first_example_bundle']['display'] = $display_elements;

  return $extra_fields;
}

/**
 * Provides a wrapper on the edit form to add a new entity.
 */
function entity_example_basic_add() {
  // Create a basic entity structure to be used and passed to the validation
  // and submission functions.
  $entity = entity_get_controller('entity_example_basic')->create();
  return drupal_get_form('entity_example_basic_form', $entity);
}

/**
 * Form function to create an entity_example_basic entity.
 *
 * The pattern is:
 * - Set up the form for the data that is specific to your
 *   entity: the columns of your base table.
 * - Call on the Field API to pull in the form elements
 *   for fields attached to the entity.
 */
function entity_example_basic_form($form, &$form_state, $entity) {
  $form['item_description'] = array(
    '#type' => 'textfield',
    '#title' => t('Item Description'),
    '#required' => TRUE,
    '#default_value' => $entity->item_description,
  );

  $form['basic_entity'] = array(
    '#type' => 'value',
    '#value' => $entity,
  );

  field_attach_form('entity_example_basic', $entity, $form, $form_state);

  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save'),
    '#weight' => 100,
  );
  $form['delete'] = array(
    '#type' => 'submit',
    '#value' => t('Delete'),
    '#submit' => array('entity_example_basic_edit_delete'),
    '#weight' => 200,
  );

  return $form;
}


/**
 * Validation handler for entity_example_basic_add_form form.
 *
 * We pass things straight through to the Field API to handle validation
 * of the attached fields.
 */
function entity_example_basic_form_validate($form, &$form_state) {
  field_attach_form_validate('entity_example_basic', $form_state['values']['basic_entity'], $form, $form_state);
}


/**
 * Form submit handler: Submits basic_add_form information.
 */
function entity_example_basic_form_submit($form, &$form_state) {
  $entity = $form_state['values']['basic_entity'];
  $entity->item_description = $form_state['values']['item_description'];
  field_attach_submit('entity_example_basic', $entity, $form, $form_state);
  $entity = entity_example_basic_save($entity);
  $form_state['redirect'] = 'examples/entity_example/basic/' . $entity->basic_id;
}

/**
 * Form deletion handler.
 *
 * @todo: 'Are you sure?' message.
 */
function entity_example_basic_edit_delete($form, &$form_state) {
  $entity = $form_state['values']['basic_entity'];
  entity_example_basic_delete($entity);
  drupal_set_message(t('The entity %item_description (ID %id) has been deleted',
    array('%item_description' => $entity->item_description, '%id' => $entity->basic_id))
  );
  $form_state['redirect'] = 'examples/entity_example';
}

/**
 * We save the entity by calling the controller.
 */
function entity_example_basic_save(&$entity) {
  return entity_get_controller('entity_example_basic')->save($entity);
}

/**
 * Use the controller to delete the entity.
 */
function entity_example_basic_delete($entity) {
  entity_get_controller('entity_example_basic')->delete($entity);
}

/**
 * EntityExampleBasicControllerInterface definition.
 *
 * We create an interface here because anyone could come along and
 * use hook_entity_info_alter() to change our controller class.
 * We want to let them know what methods our class needs in order
 * to function with the rest of the module, so here's a handy list.
 *
 * @see hook_entity_info_alter()
 */
interface EntityExampleBasicControllerInterface
  extends DrupalEntityControllerInterface {

  /**
   * Create an entity.
   */
  public function create();

  /**
   * Save an entity.
   *
   * @param object $entity
   *   The entity to save.
   */
  public function save($entity);

  /**
   * Delete an entity.
   *
   * @param object $entity
   *   The entity to delete.
   */
  public function delete($entity);

}

/**
 * EntityExampleBasicController extends DrupalDefaultEntityController.
 *
 * Our subclass of DrupalDefaultEntityController lets us add a few
 * important create, update, and delete methods.
 */
class EntityExampleBasicController
  extends DrupalDefaultEntityController
  implements EntityExampleBasicControllerInterface {

  /**
   * Create and return a new entity_example_basic entity.
   */
  public function create() {
    $entity = new stdClass();
    $entity->type = 'entity_example_basic';
    $entity->basic_id = 0;
    $entity->bundle_type = 'first_example_bundle';
    $entity->item_description = '';
    return $entity;
  }

  /**
   * Saves the custom fields using drupal_write_record().
   */
  public function save($entity) {
    // If our entity has no basic_id, then we need to give it a
    // time of creation.
    if (empty($entity->basic_id)) {
      $entity->created = time();
    }
    // Invoke hook_entity_presave().
    module_invoke_all('entity_presave', $entity, 'entity_example_basic');
    // The 'primary_keys' argument determines whether this will be an insert
    // or an update. So if the entity already has an ID, we'll specify
    // basic_id as the key.
    $primary_keys = $entity->basic_id ? 'basic_id' : array();
    // Write out the entity record.
    drupal_write_record('entity_example_basic', $entity, $primary_keys);
    // We're going to invoke either hook_entity_update() or
    // hook_entity_insert(), depending on whether or not this is a
    // new entity. We'll just store the name of hook_entity_insert()
    // and change it if we need to.
    $invocation = 'entity_insert';
    // Now we need to either insert or update the fields which are
    // attached to this entity. We use the same primary_keys logic
    // to determine whether to update or insert, and which hook we
    // need to invoke.
    if (empty($primary_keys)) {
      field_attach_insert('entity_example_basic', $entity);
    }
    else {
      field_attach_update('entity_example_basic', $entity);
      $invocation = 'entity_update';
    }
    // Invoke either hook_entity_update() or hook_entity_insert().
    module_invoke_all($invocation, $entity, 'entity_example_basic');
    return $entity;
  }

  /**
   * Delete a single entity.
   *
   * Really a convenience function for deleteMultiple().
   */
  public function delete($entity) {
    $this->deleteMultiple(array($entity));
  }

  /**
   * Delete one or more entity_example_basic entities.
   *
   * Deletion is unfortunately not supported in the base
   * DrupalDefaultEntityController class.
   *
   * @param array $entities
   *   An array of entity IDs or a single numeric ID.
   */
  public function deleteMultiple($entities) {
    $basic_ids = array();
    if (!empty($entities)) {
      $transaction = db_transaction();
      try {
        foreach ($entities as $entity) {
          // Invoke hook_entity_delete().
          module_invoke_all('entity_delete', $entity, 'entity_example_basic');
          field_attach_delete('entity_example_basic', $entity);
          $basic_ids[] = $entity->basic_id;
        }
        db_delete('entity_example_basic')
          ->condition('basic_id', $basic_ids, 'IN')
          ->execute();
      }
      catch (Exception $e) {
        $transaction->rollback();
        watchdog_exception('entity_example', $e);
        throw $e;
      }
    }
  }
}

/**
 * @} End of "defgroup entity_example".
 */
