diff --git a/bean.install b/bean.install index ae629b2..d8144ad 100644 --- a/bean.install +++ b/bean.install @@ -16,6 +16,14 @@ function bean_schema() { 'type' => 'serial', 'not null' => TRUE, 'description' => 'Primary Key: Unique bean item ID.', + 'unsigned' => TRUE, + ), + 'vid' => array( + 'type' => 'int', + 'not null' => TRUE, + 'description' => 'Revision ID', + 'default' => 0, + 'unsigned' => TRUE, ), 'delta' => array( 'description' => "The bean's {block}.delta.", @@ -58,19 +66,134 @@ function bean_schema() { 'serialize' => TRUE, 'description' => 'A serialized array of additional data related to this bean.', ), + 'created' => array( + 'description' => 'The Unix timestamp when the entity was created.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'changed' => array( + 'description' => 'The Unix timestamp when the entity was most recently saved.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), ), 'foreign keys' => array( 'type' => array( 'table' => 'bean_type', 'columns' => array('type' => 'type'), ), + 'bean_revision' => array( + 'table' => 'bean_revision', + 'columns' => array('vid' => 'vid'), + ) ), 'primary key' => array('bid'), 'unique keys' => array( + 'vid' => array('vid'), 'delta' => array('delta'), ), ); + $schema['bean_revision'] = array( + 'description' => 'Stores bean items.', + 'fields' => array( + 'bid' => array( + 'description' => 'The {bean} this version belongs to.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'vid' => array( + 'description' => 'The primary identifier for this version.', + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'uid' => array( + 'description' => 'The author of the revision.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'delta' => array( + 'description' => "The bean's {block}.delta.", + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + ), + 'label' => array( + 'description' => 'The Displays in the Admin page.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'title' => array( + 'description' => 'The human-readable name of this bean.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'type' => array( + 'description' => 'The {bean_type}.type of this bean.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + ), + 'view_mode' => array( + 'description' => 'The View mode to use as the bean.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => 'default', + ), + 'data' => array( + 'type' => 'text', + 'not null' => FALSE, + 'size' => 'big', + 'serialize' => TRUE, + 'description' => 'A serialized array of additional data related to this bean.', + ), + 'log' => array( + 'description' => t('A log message associated with the revision.'), + 'type' => 'text', + 'size' => 'big', + ), + 'created' => array( + 'description' => 'The Unix timestamp when the entity was created.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'changed' => array( + 'description' => 'The Unix timestamp when the entity was most recently saved.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'foreign keys' => array( + 'type' => array( + 'table' => 'bean_type', + 'columns' => array('type' => 'type'), + ), + 'version_bean' => array( + 'table' => 'bean', + 'columns' => array('bid' => 'bid'), + ) + ), + 'primary key' => array('vid'), + 'indexes' => array( + 'bid' => array('bid', 'vid'), + ), + ); + return $schema; } @@ -176,3 +299,21 @@ function bean_update_7006() { registry_rebuild(); return t('Registry and Menu have been rebuilt'); } + +/** + * Add a Bean Revision Table + */ +function bean_update_7007(&$return) { + $schema = bean_schema(); + $bean = $schema['bean']; + $bean_revision = $schema['bean_revision']; + + db_drop_primary_key('bean'); + db_change_field('bean', 'bid', 'bean', $bean['bid'], array('primary key' => array('bid'))); + db_add_field('bean', 'vid', $bean['vid']); + db_add_unique_key('bean', 'vid', array('vid')); + + db_create_table('bean_revision', $bean_revision); + + return t('Bean Revision table has been added'); +} diff --git a/bean.module b/bean.module index 8e5f30a..5785582 100644 --- a/bean.module +++ b/bean.module @@ -15,12 +15,15 @@ function bean_entity_info() { 'entity class' => 'Bean', 'controller class' => 'BeanEntityAPIController', 'base table' => 'bean', + 'revision table' => 'bean_revision', 'fieldable' => TRUE, 'entity keys' => array( 'id' => 'bid', 'bundle' => 'type', 'label' => 'title', 'name' => 'delta', + 'set active revision' => 'revision', + 'revision' => 'vid', ), 'bundles' => array(), 'bundle keys' => array( @@ -154,7 +157,7 @@ function bean_menu() { $items['block/%bean_delta/delete'] = array( 'title' => 'Delete', 'page callback' => 'drupal_get_form', - 'page arguments' => array('bean_delete_confirm', 1), + 'page arguments' => array('bean_delete_revision_confirm', 1), 'access callback' => 'bean_access', 'access arguments' => array('delete', 1), 'weight' => 1, @@ -163,6 +166,61 @@ function bean_menu() { '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' => 'Revisions', + 'page callback' => 'bean_view', + 'page arguments' => array(1), + 'access callback' => 'bean_access', + 'access arguments' => array('view', 1), + 'file' => 'includes/bean.pages.inc', + 'load arguments' => array(1, 3), + ); + + $items['block/%bean_delta/revisions/%/view'] = array( + 'title' => 'Revisions', + 'page callback' => 'bean_view', + 'page arguments' => array(1), + 'access callback' => 'bean_access', + 'access arguments' => array('view', 1), + 'file' => 'includes/bean.pages.inc', + 'load arguments' => array(1, 3), + 'weight' => -10, + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'context' => MENU_CONTEXT_NONE, + ); + + $items['block/%bean_delta/revisions/%/edit'] = array( + 'page callback' => 'bean_edit', + 'page arguments' => array(1), + 'access callback' => 'bean_access', + 'access arguments' => array('edit', 1), + 'file' => 'includes/bean.pages.inc', + 'load arguments' => array(1, 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_confirm', 1), + 'access callback' => 'bean_access', + 'access arguments' => array('delete', 1), + 'file' => 'includes/bean.pages.inc', + 'load arguments' => array(1, 3), + 'weight' => 1, + 'type' => MENU_LOCAL_TASK, + 'context' => MENU_CONTEXT_NONE, + ); $items['admin/content/blocks'] = array( 'title' => 'Blocks', @@ -212,7 +270,8 @@ function bean_admin_paths() { 'block/*/edit' => TRUE, 'block/*/delete' => TRUE, 'block/*/revisions' => TRUE, - 'block/*/revisions/*/revert' => TRUE, + 'block/*/revisions/*/edit' => TRUE, + 'block/*/revisions/*/set-active' => TRUE, 'block/*/revisions/*/delete' => TRUE, 'block/add' => TRUE, 'block/add/*' => TRUE, @@ -421,23 +480,38 @@ function bean_load($bid, $reset = FALSE) { } /** + * 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 + * @return Bean * A fully-loaded $bean object or FALSE if it cannot be loaded. */ -function bean_load_delta($delta, $reset = FALSE) { +function bean_load_delta($delta, $reset = FALSE, $revision = NULL) { $result = db_select('bean', 'b') ->fields('b', array('bid')) ->condition('delta', $delta) ->execute(); if ($bid = $result->fetchField()) { - $beans = bean_load_multiple(array($bid), array(), $reset); - return reset($beans); + if ($revision) { + return bean_load_revision($bid, $revision, $reset); + } + else { + return bean_load($bid, $reset); + } } return FALSE; } @@ -445,8 +519,8 @@ function bean_load_delta($delta, $reset = FALSE) { /** * Menu callback to load a bean by the delta */ -function bean_delta_load($delta) { - return bean_load_delta($delta); +function bean_delta_load($delta, $revision = NULL) { + return bean_load_delta($delta, FALSE, $revision); } /** @@ -493,6 +567,12 @@ function bean_permission() { 'title' => t('View Bean page'), 'description' => t('Visit !url', array('!url' => "block/")), ), + 'administer bean settings' => array( + 'title' => t('Administer Bean Settings'), + ), + 'view bean revisions' => array( + 'title' => t('Administer Bean Settings'), + ), ); // Add a Permission for each entity type. diff --git a/includes/bean.core.inc b/includes/bean.core.inc index ab072fe..d3a2ad5 100644 --- a/includes/bean.core.inc +++ b/includes/bean.core.inc @@ -144,6 +144,13 @@ class Bean extends Entity { public $data; public $delta; public $view_mode; + public $bid; + public $vid; + public $created; + public $changed; + public $log; + public $uid; + public $currentRevision; protected $plugin; /** @@ -314,41 +321,65 @@ class Bean extends Entity { * Override the save to add clearing of caches */ public function save() { - // Set the delta if it's not set already if (empty($this->delta)) { - $max_length = 32; + $this->checkDelta(); + } - // Base it on the label and make sure it isn't too long for the database. - if (module_exists('transliteration')) { - $delta = drupal_clean_css_identifier(transliteration_get($this->label)); - } - else { - $delta = drupal_clean_css_identifier(strtolower($this->label));; - } + $this->setUid(); - $this->delta = substr($delta, 0, $max_length); - - // Check if delta is unique. - if (bean_load_delta($this->delta)) { - $i = 0; - $separator = '-'; - $original_delta = $this->delta; - do { - $unique_suffix = $separator . $i++; - $this->delta = substr($original_delta, 0, $max_length - drupal_strlen($unique_suffix)) . $unique_suffix; - } while (bean_load_delta($this->delta)); - } + if (empty($this->created)) { + $this->created = REQUEST_TIME; } + $this->changed = REQUEST_TIME; + $this->plugin->submit($this); $return = parent::save(); block_flush_caches(); cache_clear_all('bean:' . $this->delta . ':', 'cache_block', TRUE); + return $return; } /** + * Get the user + */ + protected function setUid() { + global $user; + + $this->uid = empty($this->uid) ? $user->uid : $this->uid; + } + + /** + * Set the delta function + */ + protected function checkDelta() { + $max_length = 32; + + // Base it on the label and make sure it isn't too long for the database. + if (module_exists('transliteration')) { + $delta = drupal_clean_css_identifier(transliteration_get($this->label)); + } + else { + $delta = drupal_clean_css_identifier(strtolower($this->label));; + } + + $this->delta = substr($delta, 0, $max_length); + + // Check if delta is unique. + if (bean_load_delta($this->delta)) { + $i = 0; + $separator = '-'; + $original_delta = $this->delta; + do { + $unique_suffix = $separator . $i++; + $this->delta = substr($original_delta, 0, $max_length - drupal_strlen($unique_suffix)) . $unique_suffix; + } while (bean_load_delta($this->delta)); + } + } + + /** * Override the delete to remove the fields and blocks */ public function delete() { @@ -423,31 +454,49 @@ class BeanEntityAPIController extends EntityAPIController { } } + // Support the entitycache module if activated. + if (!empty($this->entityInfo['entity cache']) && !$revision_id && $ids && !$conditions) { + $cached_entities = EntityCacheControllerHelper::entityCacheGet($this, $ids, $conditions); + // If any entities were loaded, remove them from the ids still to load. + $ids = array_diff($ids, array_keys($cached_entities)); + $entities += $cached_entities; + + // Add loaded entities to the static cache if we are not loading a + // revision. + if ($this->cache && !empty($cached_entities) && !$revision_id) { + $this->cacheSet($cached_entities); + } + } + // Load any remaining entities from the database. This is the case if $ids // is set to FALSE (so we load all entities), if there are any ids left to // load or if loading a revision. if (!($this->cacheComplete && $ids === FALSE && !$conditions) && ($ids === FALSE || $ids || $revision_id)) { $queried_entities = array(); foreach ($this->query($ids, $conditions, $revision_id) as $record) { - // This is what was overridden. - $this->setPlugin($record); // Skip entities already retrieved from cache. if (isset($entities[$record->{$this->idKey}])) { continue; } - // Take care of serialized columns. - $schema = drupal_get_schema($this->entityInfo['base table']); - - foreach ($schema['fields'] as $field => $info) { - if (!empty($info['serialize']) && isset($record->$field)) { - $record->$field = unserialize($record->$field); - // Support automatic merging of 'data' fields into the entity. - if (!empty($info['merge']) && is_array($record->$field)) { - foreach ($record->$field as $key => $value) { - $record->$key = $value; + /************ Start Override *********************/ + $this->setPlugin($record); + /************ End Override ***********************/ + + // For DB-based entities take care of serialized columns. + if (!empty($this->entityInfo['base table'])) { + $schema = drupal_get_schema($this->entityInfo['base table']); + + foreach ($schema['fields'] as $field => $info) { + if (!empty($info['serialize']) && isset($record->$field)) { + $record->$field = unserialize($record->$field); + // Support automatic merging of 'data' fields into the entity. + if (!empty($info['merge']) && is_array($record->$field)) { + foreach ($record->$field as $key => $value) { + $record->$key = $value; + } + unset($record->$field); } - unset($record->$field); } } } @@ -464,6 +513,28 @@ class BeanEntityAPIController extends EntityAPIController { $entities += $queried_entities; } + // If we load specific revision we should check whether it is current. + if ($revision_id) { + $record = current($entities); + if (isset($record->{$this->revisionKey})) { + $query = $this->buildQuery(array($record->{$this->idKey}), array($this->revisionKey => $record->{$this->revisionKey})); + $current_revision = $query->execute()->fetchAll(); + $record->currentRevision = !empty($current_revision); + } + } + // If we loaded multiple entities they are all current revisions. + else { + foreach ($entities as &$record) { + $record->currentRevision = TRUE; + } + } + + // Entitycache module support: Add entities to the entity cache if we are + // not loading a revision. + if (!empty($this->entityInfo['entity cache']) && !empty($queried_entities) && !$revision_id) { + EntityCacheControllerHelper::entityCacheSet($this, $queried_entities); + } + if ($this->cache) { // Add entities to the cache if we are not loading a revision. if (!empty($queried_entities) && !$revision_id) { diff --git a/includes/bean.info.inc b/includes/bean.info.inc index 3aec965..25abb22 100644 --- a/includes/bean.info.inc +++ b/includes/bean.info.inc @@ -42,7 +42,6 @@ class BeanMetadataController extends EntityDefaultMetadataController { 'label' => t('Block Delta'), 'required' => TRUE, 'schema field' => 'delta', - 'description' => t('The bean delta.'), ); $properties['type'] = array( @@ -56,4 +55,4 @@ class BeanMetadataController extends EntityDefaultMetadataController { return $info; } -} \ No newline at end of file +} diff --git a/includes/bean.pages.inc b/includes/bean.pages.inc index 1d10ff5..16f575c 100644 --- a/includes/bean.pages.inc +++ b/includes/bean.pages.inc @@ -200,6 +200,37 @@ function bean_form($form, &$form_state, Bean $bean, $type = NULL) { '#weight' => -9, ); + $form['revision'] = array( + '#weight' => 10, + ); + + if (empty($bean->bid)) { + $form['revision']['#access'] = FALSE; + } + + ctools_include('dependent'); + ctools_add_js('dependent'); + + $form['revision']['revision'] = array( + '#type' => 'checkbox', + '#title' => t('Create new revision'), + '#default_value' => 1, + '#id' => 'edit-revision', + ); + + if (!bean_access('create', $bean) || !$bean->currentRevision) { + $form['revision']['revision']['#disabled'] = TRUE; + $form['revision']['revision']['#value'] = TRUE; + } + + $form['revision']['log'] = array( + '#type' => 'textarea', + '#title' => t('Log message'), + '#description' => t('Provide an explanation of the changes you are making. This will help other authors understand your motivations.'), + '#dependency' => array('edit-revision' => array(1)), + '#default_value' => '', + ); + // The view mode. if (user_access('edit bean view mode')) { $bean_info = $bean->entityInfo(); @@ -239,7 +270,7 @@ function bean_form($form, &$form_state, Bean $bean, $type = NULL) { '#weight' => 200, '#submit' => array('bean_form_submit'), ); - if (!empty($bean->bid) && user_access('delete any ' . $bean->type . ' bean')) { + if (!empty($bean->bid) && bean_access('delete', $bean)) { $form['actions']['delete'] = array( '#type' => 'submit', '#value' => t('Delete'), @@ -266,6 +297,11 @@ function bean_form_validate($form, &$form_state) { $bean->label = $form_state['values']['label']; $bean->title = $form_state['values']['title']; $bean->view_mode = $form_state['values']['view_mode']; + $bean->revision = $form_state['values']['revision']; + + if ($bean->revision) { + $bean->log = $form_state['values']['log']; + } field_attach_form_validate('bean', $bean, $form, $form_state); $form_state['values']['bean'] = $bean;