diff --git a/core/lib/Drupal/Core/Entity/EntityNG.php b/core/lib/Drupal/Core/Entity/EntityNG.php index e16c6fb..0480d10 100644 --- a/core/lib/Drupal/Core/Entity/EntityNG.php +++ b/core/lib/Drupal/Core/Entity/EntityNG.php @@ -10,6 +10,7 @@ use Drupal\Core\TypedData\ContextAwareInterface; use Drupal\Core\TypedData\TypedDataInterface; use Drupal\Component\Uuid\Uuid; +use Drupal\Core\Language\Language; use ArrayIterator; use InvalidArgumentException; diff --git a/core/modules/block/block.install b/core/modules/block/block.install index c16e50a..da69b7d 100644 --- a/core/modules/block/block.install +++ b/core/modules/block/block.install @@ -4,6 +4,7 @@ * @file * Install, update and uninstall functions for the block module. */ +use Drupal\Component\Uuid\Uuid; /** * Implements hook_schema(). @@ -42,7 +43,7 @@ function block_update_dependencies() { ); // Migrate users.data after User module prepared the tables. $dependencies['block'][8005] = array( - 'user' => 8011, + 'user' => 8016, ); return $dependencies; } @@ -194,6 +195,255 @@ function block_update_8006() { } /** + * Migrate {block_custom} to {custom_block}. + * + * Note this table now resides in custom_block_schema() but for 7.x to 8.x + * upgrades, changes must be made from block module as custom_block module is + * only enabled during upgrade process. + */ +function block_update_8007() { + // Add the {custom_block} table. + db_create_table('custom_block', array( + 'description' => 'Stores contents of custom-made blocks.', + 'fields' => array( + 'id' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => "The block's {custom_block}.id.", + ), + 'uuid' => array( + 'description' => 'Unique Key: Universally unique identifier for this entity.', + 'type' => 'varchar', + 'length' => 128, + 'not null' => FALSE, + ), + 'info' => array( + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '', + 'description' => 'Block description.', + ), + 'machine_name' => array( + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '', + 'description' => 'Machine name of block.', + ), + // Defaults to NULL in order to avoid a brief period of potential + // deadlocks on the index. + 'revision_id' => array( + 'description' => 'The current {block_custom_revision}.revision_id version identifier.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => FALSE, + 'default' => NULL, + ), + 'type' => array( + 'description' => 'The type of this custom block.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + ), + 'langcode' => array( + 'description' => 'The {language}.langcode of this node.', + 'type' => 'varchar', + 'length' => 12, + 'not null' => TRUE, + 'default' => '', + ), + ), + 'primary key' => array('id'), + 'indexes' => array( + 'block_custom_type' => array(array('type', 4)), + ), + 'unique keys' => array( + 'revision_id' => array('revision_id'), + 'uuid' => array('uuid'), + 'info' => array('info'), + ), + 'foreign keys' => array( + 'custom_block_revision' => array( + 'table' => 'custom_block_revision', + 'columns' => array('revision_id' => 'revision_id'), + ), + ), + )); + // Add the {custom_block_revision} table. + db_create_table('custom_block_revision', array( + 'description' => 'Stores contents of custom-made blocks.', + 'fields' => array( + 'id' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + 'description' => "The block's {custom_block}.id.", + ), + // Defaults to NULL in order to avoid a brief period of potential + // deadlocks on the index. + 'revision_id' => array( + 'description' => 'The current version identifier.', + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'log' => array( + 'description' => 'The log entry explaining the changes in this version.', + 'type' => 'text', + 'not null' => TRUE, + 'size' => 'big', + ), + 'info' => array( + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '', + 'description' => 'Block description.', + ), + ), + 'primary key' => array('revision_id'), + )); + // Populate the {custom_block} and {custom_block_revision} table. + $results = db_select('block_custom', 'bc')->fields('bc')->execute(); + + $uuid = new Uuid(); + $execute = FALSE; + $block_insert = db_insert('custom_block')->fields(array( + 'id', + 'uuid', + 'machine_name', + 'info', + 'revision_id', + 'langcode', + 'type' + )); + $revision_insert = db_insert('custom_block_revision')->fields(array( + 'id', + 'revision_id', + 'log', + 'info' + )); + foreach ($results as $block) { + $custom_block = array( + 'id' => $block->bid, + 'uuid' => $uuid->generate(), + 'machine_name' => preg_replace('^([a-z|0-9|\_]', '', $block->info), + 'info' => $block->info, + 'revision_id' => $block->bid, + 'langcode' => LANGUAGE_NOT_SPECIFIED, + 'type' => 'basic' + ); + $revision = array( + 'id' => $block->bid, + 'revision_id' => $block->bid, + 'info' => $block->info, + 'log' => 'Initial value from 7.x to 8.x upgrade' + ); + $block_insert->values($custom_block); + $revision_insert->values($revision); + // We have something to execute. + $execute = TRUE; + } + if ($execute) { + $block_insert->execute(); + $revision_insert->execute(); + } +} + +/** + * Migrate {block_custom}.body and {block_custom}.format to block_body field. + */ +function block_update_8008() { + $sandbox['#finished'] = 0; + + if (!isset($sandbox['total'])) { + // Initial invocation. + + // First, create the body field. + $body_field = array( + 'field_name' => 'block_body', + 'type' => 'text_with_summary', + 'entity_types' => array('custom_block'), + 'module' => 'text', + 'cardinality' => 1, + ); + _update_7000_field_create_field($body_field); + + $instance = array( + 'field_name' => 'block_body', + 'entity_type' => 'custom_block', + 'bundle' => 'basic', + 'label' => 'Block body', + 'widget' => array('type' => 'text_textarea_with_summary'), + 'settings' => array('display_summary' => FALSE), + ); + _update_7000_field_create_instance($body_field, $instance); + + // Initialize state for future calls. + $sandbox['last'] = 0; + $sandbox['count'] = 0; + + $query = db_select('block_custom', 'bc'); + $sandbox['total'] = $query->countQuery()->execute()->fetchField(); + + $sandbox['body_field_id'] = $body_field['id']; + } + else { + // Subsequent invocations. + + $found = FALSE; + if ($sandbox['total']) { + // Operate on each block in turn. + $batch_size = 200; + $query = db_select('block_custom', 'bc'); + $query + ->fields('bc', array('bid', 'body', 'format')) + ->condition('bc.bid', $sandbox['last'], '>') + ->orderBy('bc.bid', 'ASC') + ->range(0, $batch_size); + $blocks = $query->execute(); + + // Load the block, set up 'body' and save the field data. + foreach ($blocks as $block) { + $found = TRUE; + + $data = array( + LANGUAGE_NOT_SPECIFIED => array( + array( + 'format' => $block->format, + 'value' => $block->body + ) + ) + ); + // This is a core update and no contrib modules are enabled yet, so + // we can assume default field storage for a faster update. + _update_7000_field_sql_storage_write('custom_block', 'basic', $block->bid, $block->bid, 'block_body', $data); + + $sandbox['last'] = $block->bid; + $sandbox['count'] += 1; + } + + $sandbox['#finished'] = min(0.99, $sandbox['count'] / $sandbox['total']); + } + + if (!$found) { + // All blocks are processed. + + // Remove the now-obsolete body info from block_custom. + db_drop_field('block_custom', 'body'); + db_drop_field('block_custom', 'format'); + + // We're done. + $sandbox['#finished'] = 1; + } + } +} + +/** * @} End of "addtogroup updates-7.x-to-8.x". * The next series of updates should start at 9000. */ diff --git a/core/modules/block/custom_block/config/custom_block.type.basic.yml b/core/modules/block/custom_block/config/custom_block.type.basic.yml new file mode 100644 index 0000000..e2bb99e --- /dev/null +++ b/core/modules/block/custom_block/config/custom_block.type.basic.yml @@ -0,0 +1,3 @@ +id: basic +label: Basic block +revision: '0' diff --git a/core/modules/block/custom_block/custom_block.admin.inc b/core/modules/block/custom_block/custom_block.admin.inc new file mode 100644 index 0000000..afffca2 --- /dev/null +++ b/core/modules/block/custom_block/custom_block.admin.inc @@ -0,0 +1,99 @@ +render(); +} + +/** + * Page callback: Presents the custom block type creation form. + * + * @return array + * A form array as expected by drupal_render(). + * + * @see custom_block_menu() + */ +function custom_block_type_add() { + $block_type = entity_create('custom_block_type', array()); + return entity_get_form($block_type); +} + +/** + * Page callback: Presents the custom block type edit form. + * + * @param \Drupal\custom_block\Plugin\Core\Entity\CustomBlockType $block_type + * The custom block type to edit. + * + * @return array + * A form array as expected by drupal_render(). + * + * @see custom_block_menu() + */ +function custom_block_type_edit(CustomBlockType $block_type) { + drupal_set_title(t('Edit %label custom block type', array('%label' => $block_type->label())), PASS_THROUGH); + return entity_get_form($block_type); +} + +/** + * Page callback: Form constructor for the custom block type deletion form. + * + * @param \Drupal\custom_block\Plugin\Core\Entity\CustomBlockType $block_type + * The custom block type to be deleted. + * + * @see custom_block_menu() + * @see custom_block_type_delete_form_submit() + * + * @ingroup forms + */ +function custom_block_type_delete_form($form, &$form_state, CustomBlockType $block_type) { + $form_state['custom_block_type'] = $block_type; + $form['id'] = array( + '#type' => 'value', + '#value' => $block_type->id(), + ); + + $message = t('Are you sure you want to delete %label?', array('%label' => $block_type->label())); + + $blocks = entity_query('custom_block')->condition('type', $block_type->id())->execute(); + if (!empty($blocks)) { + drupal_set_title($message, PASS_THROUGH); + $caption = '

' . format_plural(count($blocks), '%label is used by 1 custom block on your site. You can not remove this block type until you have removed all of the %label blocks.', '%label is used by @count custom blocks on your site. You may not remove %label until you have removed all of the %label custom blocks.', array('%label' => $block_type->label())) . '

'; + $form['description'] = array('#markup' => $caption); + return $form; + } + + return confirm_form( + $form, + $message, + 'admin/structure/custom-blocks', + t('This action cannot be undone.'), + t('Delete') + ); +} + +/** + * Form submission handler for custom_block_type_delete_form(). + */ +function custom_block_type_delete_form_submit($form, &$form_state) { + $block_type = $form_state['custom_block_type']; + $block_type->delete(); + + drupal_set_message(t('Custom block type %label has been deleted.', array('%label' => $block_type->label()))); + watchdog('custom_block', 'Custom block type %label has been deleted.', array('%label' => $block_type->label()), WATCHDOG_NOTICE); + + $form_state['redirect'] = 'admin/structure/custom-blocks'; +} diff --git a/core/modules/block/custom_block/custom_block.info b/core/modules/block/custom_block/custom_block.info index f38e540..295858e 100644 --- a/core/modules/block/custom_block/custom_block.info +++ b/core/modules/block/custom_block/custom_block.info @@ -4,3 +4,4 @@ package = Core version = VERSION core = 8.x dependencies[] = block +dependencies[] = text diff --git a/core/modules/block/custom_block/custom_block.install b/core/modules/block/custom_block/custom_block.install index 4a0e4b0..3581b58 100644 --- a/core/modules/block/custom_block/custom_block.install +++ b/core/modules/block/custom_block/custom_block.install @@ -10,21 +10,20 @@ */ function custom_block_schema() { $schema = array(); - $schema['block_custom'] = array( + $schema['custom_block'] = array( 'description' => 'Stores contents of custom-made blocks.', 'fields' => array( - 'bid' => array( + 'id' => array( 'type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE, - 'description' => "The block's {block}.bid.", + 'description' => "The block's {custom_block}.id.", ), - 'body' => array( - 'type' => 'text', + 'uuid' => array( + 'description' => 'Unique Key: Universally unique identifier for this entity.', + 'type' => 'varchar', + 'length' => 128, 'not null' => FALSE, - 'size' => 'big', - 'description' => 'Block contents.', - 'translatable' => TRUE, ), 'info' => array( 'type' => 'varchar', @@ -33,17 +32,87 @@ function custom_block_schema() { 'default' => '', 'description' => 'Block description.', ), - 'format' => array( + 'machine_name' => array( 'type' => 'varchar', - 'length' => 255, + 'length' => 128, + 'not null' => TRUE, + 'default' => '', + 'description' => 'Machine name of block.', + ), + // Defaults to NULL in order to avoid a brief period of potential + // deadlocks on the index. + 'revision_id' => array( + 'description' => 'The current {block_custom_revision}.revision_id version identifier.', + 'type' => 'int', + 'unsigned' => TRUE, 'not null' => FALSE, - 'description' => 'The {filter_format}.format of the block body.', + 'default' => NULL, + ), + 'type' => array( + 'description' => 'The type of this custom block.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + ), + 'langcode' => array( + 'description' => 'The {language}.langcode of this node.', + 'type' => 'varchar', + 'length' => 12, + 'not null' => TRUE, + 'default' => '', ), ), + 'primary key' => array('id'), + 'indexes' => array( + 'block_custom_type' => array(array('type', 4)), + ), 'unique keys' => array( + 'revision_id' => array('revision_id'), + 'uuid' => array('uuid'), 'info' => array('info'), ), - 'primary key' => array('bid'), + 'foreign keys' => array( + 'custom_block_revision' => array( + 'table' => 'custom_block_revision', + 'columns' => array('revision_id' => 'revision_id'), + ), + ), + ); + + $schema['custom_block_revision'] = array( + 'description' => 'Stores contents of custom-made blocks.', + 'fields' => array( + 'id' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + 'description' => "The block's {custom_block}.id.", + ), + // Defaults to NULL in order to avoid a brief period of potential + // deadlocks on the index. + 'revision_id' => array( + 'description' => 'The current version identifier.', + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'log' => array( + 'description' => 'The log entry explaining the changes in this version.', + 'type' => 'text', + 'not null' => TRUE, + 'size' => 'big', + ), + 'info' => array( + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '', + 'description' => 'Block description.', + ), + ), + 'primary key' => array('revision_id'), ); return $schema; } diff --git a/core/modules/block/custom_block/custom_block.js b/core/modules/block/custom_block/custom_block.js new file mode 100644 index 0000000..fb0905a --- /dev/null +++ b/core/modules/block/custom_block/custom_block.js @@ -0,0 +1,46 @@ +/** + * @file + * Defines Javascript behaviors for the custom_block module. + */ + +(function ($) { + +"use strict"; + +Drupal.behaviors.customBlockDetailsSummaries = { + attach: function (context) { + var $context = $(context); + $context.find('.custom-block-form-revision-information').drupalSetSummary(function (context) { + var $context = $(context); + var revisionCheckbox = $context.find('.form-item-revision input'); + + // Return 'New revision' if the 'Create new revision' checkbox is checked, + // or if the checkbox doesn't exist, but the revision log does. For users + // without the "Administer content" permission the checkbox won't appear, + // but the revision log will if the content type is set to auto-revision. + if (revisionCheckbox.is(':checked') || (!revisionCheckbox.length && $context.find('.form-item-log textarea').length)) { + return Drupal.t('New revision'); + } + + return Drupal.t('No revision'); + }); + + $context.find('fieldset.custom-block-translation-options').drupalSetSummary(function (context) { + var $context = $(context); + var translate; + var $checkbox = $context.find('.form-item-translation-translate input'); + + if ($checkbox.size()) { + translate = $checkbox.is(':checked') ? Drupal.t('Needs to be updated') : Drupal.t('Does not need to be updated'); + } + else { + $checkbox = $context.find('.form-item-translation-retranslate input'); + translate = $checkbox.is(':checked') ? Drupal.t('Flag other translations as outdated') : Drupal.t('Do not flag other translations as outdated'); + } + + return translate; + }); + } +}; + +})(jQuery); diff --git a/core/modules/block/custom_block/custom_block.module b/core/modules/block/custom_block/custom_block.module index 8c7fdea..a0cccb2 100644 --- a/core/modules/block/custom_block/custom_block.module +++ b/core/modules/block/custom_block/custom_block.module @@ -5,6 +5,10 @@ * Allows the creaation of custom blocks through the user interface. */ +use Drupal\custom_block\Plugin\Core\Entity\CustomBlockType; +use Drupal\custom_block\Plugin\Core\Entity\CustomBlock; +use Symfony\Component\HttpFoundation\RedirectResponse; + /** * Implements hook_menu(). */ @@ -19,15 +23,91 @@ function custom_block_menu() { $items['admin/structure/block/list/' . $plugin_id . '/add/custom_blocks'] = array( 'title' => 'Add custom block', 'description' => 'Create a block with custom content and settings.', - 'page callback' => 'block_admin_add', - 'page arguments' => array('custom_block:custom_block', $theme), - 'access callback' => TRUE, + 'page callback' => 'custom_block_add_block_redirect', + 'page arguments' => array($theme), + 'access arguments' => array('administer blocks'), 'type' => MENU_LOCAL_ACTION, - 'file' => 'block.admin.inc', - 'file path' => drupal_get_path('module', 'block'), ); } } + + $items['admin/structure/custom-blocks'] = array( + 'title' => 'Custom block types', + 'description' => 'Manage custom block types.', + 'page callback' => 'custom_block_type_list', + 'access arguments' => array('administer blocks'), + 'file' => 'custom_block.admin.inc', + ); + $items['admin/structure/custom-blocks/add'] = array( + 'title' => 'Add custom block type', + 'page callback' => 'custom_block_type_add', + 'access arguments' => array('administer blocks'), + 'type' => MENU_LOCAL_ACTION, + 'weight' => 1, + 'file' => 'custom_block.admin.inc', + ); + $items['admin/structure/custom-blocks/manage/%custom_block_type'] = array( + 'title' => 'Edit custom block type', + 'title callback' => 'entity_page_label', + 'title arguments' => array(4), + 'page callback' => 'custom_block_type_edit', + 'page arguments' => array(4), + 'access arguments' => array('administer blocks'), + 'file' => 'custom_block.admin.inc', + ); + $items['admin/structure/custom-blocks/manage/%custom_block_type/edit'] = array( + 'title' => 'Edit', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => -10, + ); + $items['admin/structure/custom-blocks/manage/%custom_block_type/delete'] = array( + 'title' => 'Delete', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('custom_block_type_delete_form', 4), + 'access arguments' => array('administer blocks'), + 'type' => MENU_LOCAL_TASK, + 'weight' => 10, + 'file' => 'custom_block.admin.inc', + ); + + $items['block/add'] = array( + 'title' => 'Add custom block', + 'page callback' => 'custom_block_add_page', + 'access arguments' => array('administer blocks'), + 'file' => 'custom_block.pages.inc', + ); + + $items['block/add/%custom_block_type'] = array( + 'title callback' => 'entity_page_label', + 'title arguments' => array(2), + 'page callback' => 'custom_block_add', + 'page arguments' => array(2), + 'access arguments' => array('administer blocks'), + 'description' => 'Add custom block', + 'file' => 'custom_block.pages.inc', + ); + $items['block/%custom_block_machine_name/edit'] = array( + 'title' => 'Edit', + 'page callback' => 'custom_block_edit', + 'page arguments' => array(1), + 'access callback' => 'entity_page_access', + 'access arguments' => array(1, 'update'), + 'weight' => 0, + 'type' => MENU_LOCAL_TASK, + 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE, + 'file' => 'custom_block.pages.inc', + ); + $items['block/%custom_block_machine_name/delete'] = array( + 'title' => 'Delete', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('custom_block_delete_form', 1), + 'access callback' => 'entity_page_access', + 'access arguments' => array(1, 'delete'), + 'weight' => 1, + 'type' => MENU_LOCAL_TASK, + 'context' => MENU_CONTEXT_INLINE, + 'file' => 'custom_block.pages.inc', + ); return $items; } @@ -53,3 +133,203 @@ function theme_custom_block_block($variables) { return check_markup($body, $format); } + +/** + * Loads a custom block type. + * + * @param int $id + * The ID of the custom block type to load. + * + * @return Drupal\custom_block\Plugin\Core\Entity\CustomBlockType|false + * A CustomBlockType object or FALSE if the requested $id does not exist. + */ +function custom_block_type_load($id) { + return entity_load('custom_block_type', $id); +} + +/** + * Loads a custom block. + * + * @param int $id + * The id of the custom block. + * + * @return Drupal\custom_block\Plugin\Core\Entity\CustomBlock|false + * A CustomBlock object or FALSE if the requested $id does not exist. + */ +function custom_block_load($id) { + return entity_load('custom_block', $id); +} + +/** + * Loads a custom block. + * + * @param string $machine_name + * The machine name of the custom block. + * + * @return Drupal\custom_block\Plugin\Core\Entity\CustomBlock|false + * A CustomBlock object or FALSE if the requested $id does not exist. + */ +function custom_block_machine_name_load($machine_name) { + $results = entity_load_multiple_by_properties('custom_block', array( + 'machine_name' => $machine_name + )); + return reset($results); +} + +/** + * Entity URI callback. + * + * @param Drupal\custom_block\Plugin\Core\Entity\CustomBlockType $block_type + * A custom block type entity. + * + * @return array + * An array with 'path' as the key and the path to the custom block type as + * the value. + */ +function custom_block_custom_block_type_uri(CustomBlockType $block_type) { + return array( + 'path' => 'admin/structure/custom-blocks/manage/' . $block_type->id(), + ); +} + +/** + * Entity URI callback. + * + * @param Drupal\custom_block\Plugin\Core\Entity\CustomBlock $block + * A custom block entity. + * + * @return array + * An array with 'path' as the key and the path to the custom block as the + * value. + */ +function custom_block_custom_block_uri(CustomBlock $block) { + return array( + 'path' => 'block/' . $block->machine_name->value, + ); +} + +/** + * Implements hook_entity_info_alter(). + */ +function custom_block_entity_info_alter(&$types) { + // Add a translation handler for fields if the language module is enabled. + if (module_exists('language')) { + $types['custom_block']['translation']['custom_block'] = TRUE; + } +} + +/** + * Implements hook_entity_bundle_info(). + */ +function custom_block_entity_bundle_info() { + $bundles = array(); + foreach (config_get_storage_names_with_prefix('custom_block.type.') as $config_name) { + $config = config($config_name); + $bundles['custom_block'][$config->get('id')] = array( + 'label' => $config->get('label'), + 'admin' => array( + 'path' => 'admin/structure/custom-blocks/manage/%', + 'real path' => 'admin/structure/custom-blocks/manage/' . $config->get('id'), + 'bundle argument' => 4, + ), + ); + } + return $bundles; +} + +/** + * Implements hook_entity_view_mode_info(). + */ +function custom_block_entity_view_mode_info() { + $view_modes['custom_block']['full'] = array( + 'label' => t('Full'), + ); + return $view_modes; +} + +/** + * Adds the default body field to a custom block type. + * + * @param string $block_type_id + * Id of the block type. + * @param string $label + * (optional) The label for the body instance. Defaults to 'Block body' + * + * @return array() + * Body field instance. + */ +function custom_block_add_body_field($block_type_id, $label = 'Block body') { + // Add or remove the body field, as needed. + $field = field_info_field('block_body'); + $instance = field_info_instance('custom_block', 'block_body', $block_type_id); + if (empty($field)) { + $field = array( + 'field_name' => 'block_body', + 'type' => 'text_with_summary', + 'entity_types' => array('custom_block'), + ); + $field = field_create_field($field); + } + if (empty($instance)) { + $instance = array( + 'field_name' => 'block_body', + 'entity_type' => 'custom_block', + 'bundle' => $block_type_id, + 'label' => $label, + 'widget' => array('type' => 'text_textarea_with_summary'), + 'settings' => array('display_summary' => FALSE), + ); + $instance = field_create_instance($instance); + + // Assign display settings for 'default' view mode. + entity_get_display('custom_block', $block_type_id, 'default') + ->setComponent('block_body', array( + 'label' => 'hidden', + 'type' => 'text_default', + )) + ->save(); + } + + return $instance; +} + +/** + * Implements hook_form_FORM_ID_alter() for block_plugin_ui(). + */ +function custom_block_form_block_plugin_ui_alter(&$form, $form_state) { + foreach ($form['left']['plugin_library']['#rows'] as $plugin_id => &$row) { + @list($base, $derivative) = explode(':', $plugin_id); + if ($base !== 'custom_block') { + continue; + } + $row['1']['data']['#links']['edit'] = array( + 'title' => t('Edit'), + 'href' => 'block/' . $derivative . '/edit' + ); + } +} + +/** + * Implements hook_admin_paths(). + */ +function custom_block_admin_paths() { + $paths = array( + 'block/add' => TRUE, + 'block/add/*' => TRUE, + 'block/*/edit' => TRUE, + 'block/*/delete' => TRUE, + 'admin/structure/custom-blocks/*' => TRUE, + ); + return $paths; +} + +/** + * Page callback: Redirect user to the block add page. + * + * @param string $theme + * The theme name for which the newly created custom block will be configured. + */ +function custom_block_add_block_redirect($theme) { + $url = url('block/add', array('query' => array('theme' => $theme))); + return new RedirectResponse($url); +} diff --git a/core/modules/block/custom_block/custom_block.pages.inc b/core/modules/block/custom_block/custom_block.pages.inc new file mode 100644 index 0000000..31976e3 --- /dev/null +++ b/core/modules/block/custom_block/custom_block.pages.inc @@ -0,0 +1,138 @@ + array('theme' => $_GET['theme']) + ); + } + $types = config_get_storage_names_with_prefix('custom_block.type.'); + if (count($types) == 1) { + $config = config(reset($types)); + drupal_goto('block/add/' . $config->get('id'), $options); + } + // Multiple custom block types exist, present a list of links. + $links = array(); + + foreach ($types as $config_name) { + $config = config($config_name); + $links[] = l($config->get('label'), 'block/add/' . $config->get('id'), $options); + } + return array( + '#theme' => array( + 'item_list', + 'item_list__custom_block_add' + ), + '#items' => $links, + '#title' => t('Choose a block type to continue.') + ); +} + +/** + * Page callback: Presents the custom block creation form. + * + * @param Drupal\custom_block\Plugin\Core\Entity\CustomBlockType $block_type + * The custom block type to add. + * + * @return array + * A form array as expected by drupal_render(). + * + * @see custom_block_menu() + */ +function custom_block_add(CustomBlockType $block_type) { + drupal_set_title(t('Add %type custom block', array( + '%type' => $block_type->label() + )), PASS_THROUGH); + $block = entity_create('custom_block', array( + 'type' => $block_type->id() + )); + $options = array(); + if (isset($_GET['theme']) && in_array($_GET['theme'], array_keys(list_themes()))) { + // We have navigated to this page from the block library and will keep track + // of the theme for redirecting the user to the configuration page for the + // newly created block in the given theme. + $block->setTheme($_GET['theme']); + } + return entity_get_form($block); +} + +/** + * Page callback: Presents the custom block edit form. + * + * @param Drupal\custom_block\Plugin\Core\Entity\CustomBlock $block + * The custom block to edit. + * + * @return array + * A form array as expected by drupal_render(). + * + * @see custom_block_menu() + */ +function custom_block_edit(CustomBlock $block) { + drupal_set_title(t('Edit custom block %label', array('%label' => $block->label())), PASS_THROUGH); + return entity_get_form($block); +} + +/** + * Page callback: Form constructor for the custom block deletion form. + * + * @param Drupal\custom_block\Plugin\Core\Entity\CustomBlock $block + * The custom block to be deleted. + * + * @see custom_block_menu() + * @see custom_block_delete_form_submit() + * + * @ingroup forms + */ +function custom_block_delete_form($form, &$form_state, CustomBlock $block) { + $form_state['custom_block'] = $block; + $form['id'] = array( + '#type' => 'value', + '#value' => $block->id(), + ); + + return confirm_form( + $form, + t('Are you sure you want to delete %label?', array('%label' => $block->label())), + 'admin/structure/block', + t('This action cannot be undone.'), + t('Delete') + ); +} + +/** + * Form submission handler for custom_block_delete_form(). + */ +function custom_block_delete_form_submit($form, &$form_state) { + // @todo Delete all configured instances of the block. + $block = $form_state['custom_block']; + $block->delete(); + + drupal_set_message(t('Custom block %label has been deleted.', array('%label' => $block->label()))); + watchdog('custom_block', 'Custom block %label has been deleted.', array('%label' => $block->label()), WATCHDOG_NOTICE); + + $form_state['redirect'] = 'admin/structure/block'; +} diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockAccessController.php b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockAccessController.php new file mode 100644 index 0000000..e9c1e31 --- /dev/null +++ b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockAccessController.php @@ -0,0 +1,47 @@ +type->value); + // If this is a new custom block, fill in the default values. + if (isset($block->id->value)) { + $block->set('log', NULL); + } + // Always use the default revision setting. + $block->setNewRevision($block_type->revision); + + module_invoke_all('custom_block_prepare', $block); + } + + /** + * Overrides Drupal\Core\Entity\EntityFormController::form(). + */ + public function form(array $form, array &$form_state, EntityInterface $block) { + + // Override the default CSS class name, since the user-defined custom block + // type name in 'TYPE-block-form' potentially clashes with third-party class + // names. + $form['#attributes']['class'][0] = drupal_html_class('block-' . $block->type->value . '-form'); + + // Basic block information. + // These elements are just values so they are not even sent to the client. + foreach (array('revision_id', 'id') as $key) { + $form[$key] = array( + '#type' => 'value', + '#value' => $block->$key->value, + ); + } + + $form['info'] = array( + '#type' => 'textfield', + '#title' => t('Block description'), + '#required' => TRUE, + '#default_value' => $block->info->value, + '#weight' => -5, + '#description' => t('A brief description of your block. Used on the Blocks administration page.', array('@overview' => url('admin/structure/block'))), + ); + + $form['machine_name'] = array( + '#type' => 'machine_name', + '#default_value' => $block->machine_name->value, + '#machine_name' => array( + 'exists' => 'custom_block_load', + 'source' => array('info') + ), + '#weight' => -4, + '#disabled' => !$block->isNew(), + ); + + $language_configuration = module_invoke('language', 'get_default_configuration', 'custom_block', $block->type->value); + + $form['langcode'] = array( + '#title' => t('Language'), + '#type' => 'language_select', + '#default_value' => $block->langcode->value, + '#languages' => LANGUAGE_ALL, + '#access' => isset($language_configuration['language_show']) && !$language_configuration['language_show'], + ); + + $form['additional_settings'] = array( + '#type' => 'vertical_tabs', + '#weight' => 99, + ); + + // Add a log field if the "Create new revision" option is checked, or if the + // current user has the ability to check that option. + $form['revision_information'] = array( + '#type' => 'details', + '#title' => t('Revision information'), + '#collapsible' => TRUE, + // Collapsed by default when "Create new revision" is unchecked. + '#collapsed' => !$block->isNewRevision(), + '#group' => 'additional_settings', + '#attributes' => array( + 'class' => array('custom-block-form-revision-information'), + ), + '#attached' => array( + 'js' => array(drupal_get_path('module', 'custom_block') . '/custom_block.js'), + ), + '#weight' => 20, + '#access' => $block->isNewRevision() || user_access('administer blocks'), + ); + + $form['revision_information']['revision'] = array( + '#type' => 'checkbox', + '#title' => t('Create new revision'), + '#default_value' => $block->isNewRevision(), + '#access' => user_access('administer blocks'), + ); + + // Check the revision log checkbox when the log textarea is filled in. + // This must not happen if "Create new revision" is enabled by default, + // since the state would auto-disable the checkbox otherwise. + if (!$block->isNewRevision()) { + $form['revision_information']['revision']['#states'] = array( + 'checked' => array( + 'textarea[name="log"]' => array('empty' => FALSE), + ), + ); + } + + $form['revision_information']['log'] = array( + '#type' => 'textarea', + '#title' => t('Revision log message'), + '#rows' => 4, + '#default_value' => !empty($block->log->value) ? $block->log->value : '', + '#description' => t('Briefly describe the changes you have made.'), + ); + + return parent::form($form, $form_state, $block); + } + + /** + * Updates the custom block object by processing the submitted values. + * + * This function can be called by a "Next" button of a wizard to update the + * form state's entity with the current step's values before proceeding to the + * next step. + * + * Overrides Drupal\Core\Entity\EntityFormController::submit(). + */ + public function submit(array $form, array &$form_state) { + // Build the block object from the submitted values. + $block = parent::submit($form, $form_state); + + // Save as a new revision if requested to do so. + if (!empty($form_state['values']['revision'])) { + $block->setNewRevision(); + } + + foreach (module_implements('custom_block_submit') as $module) { + $function = $module . '_custom_block_submit'; + $function($block, $form, $form_state); + } + + return $block; + } + + /** + * Overrides Drupal\Core\Entity\EntityFormController::save(). + */ + public function save(array $form, array &$form_state) { + $block = $this->getEntity($form_state); + $insert = empty($block->id->value); + $block->save(); + $watchdog_args = array('@type' => $block->bundle(), '%info' => $block->label()); + $block_type = entity_load('custom_block_type', $block->type->value); + $t_args = array('@type' => $block_type->label(), '%info' => $block->label()); + + if ($insert) { + watchdog('content', '@type: added %info.', $watchdog_args, WATCHDOG_NOTICE); + drupal_set_message(t('@type %info has been created.', $t_args)); + } + else { + watchdog('content', '@type: updated %info.', $watchdog_args, WATCHDOG_NOTICE); + drupal_set_message(t('@type %info has been updated.', $t_args)); + } + + if ($block->id->value) { + $form_state['values']['id'] = $block->id->value; + $form_state['id'] = $block->id->value; + if ($insert) { + if ($theme = $block->getTheme()) { + $form_state['redirect'] = 'admin/structure/block/add/custom_block:' . $block->machine_name->value . '/' . $theme; + } + else { + $form_state['redirect'] = 'admin/structure/block/add/custom_block:' . $block->machine_name->value . '/' . variable_get('theme_default', 'stark'); + } + } + else { + $form_state['redirect'] = 'admin/structure/block'; + } + } + else { + // In the unlikely case something went wrong on save, the block will be + // rebuilt and block form redisplayed. + drupal_set_message(t('The block could not be saved.'), 'error'); + $form_state['rebuild'] = TRUE; + } + + // Clear the page and block caches. + cache_invalidate_tags(array('content' => TRUE)); + } + + /** + * Overrides Drupal\Core\Entity\EntityFormController::delete(). + */ + public function delete(array $form, array &$form_state) { + $destination = array(); + if (isset($_GET['destination'])) { + $destination = drupal_get_destination(); + unset($_GET['destination']); + } + $block = $this->buildEntity($form, $form_state); + $form_state['redirect'] = array('block/' . $block->machine_name->value . '/delete', array('query' => $destination)); + } + +} diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockRenderController.php b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockRenderController.php new file mode 100644 index 0000000..4a36e49 --- /dev/null +++ b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockRenderController.php @@ -0,0 +1,30 @@ +id->value) && $view_mode == 'full') { + $build['#contextual_links']['custom_block'] = array('block', array($entity->machine_name->value)); + } + } + +} diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockStorageController.php b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockStorageController.php new file mode 100644 index 0000000..6a3bf05 --- /dev/null +++ b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockStorageController.php @@ -0,0 +1,124 @@ +isNewRevision()) { + // When inserting either a new custom block or a new custom_block + // revision, $entity->log must be set because {block_custom_revision}.log + // is a text column and therefore cannot have a default value. However, + // it might not be set at this point (for example, if the user submitting + // the form does not have permission to create revisions), so we ensure + // that it is at least an empty string in that case. + // @todo: Make the {block_custom_revision}.log column nullable so that we + // can remove this check. + if (!isset($record->log)) { + $record->log = ''; + } + } + elseif (!isset($record->log) || $record->log === '') { + // If we are updating an existing custom_block without adding a new + // revision, we need to make sure $entity->log is unset whenever it is + // empty. As long as $entity->log is unset, drupal_write_record() will not + // attempt to update the existing database column when re-saving the + // revision; therefore, this code allows us to avoid clobbering an + // existing log entry with an empty one. + unset($record->log); + } + + } + + /** + * Overrides Drupal\Core\Entity\DatabaseStorageController::attachLoad(). + */ + protected function attachLoad(&$blocks, $load_revision = FALSE) { + // Create an array of block types for passing as a load argument. + // Note that blocks at this point are still \StdClass objects returned from + // the database. + foreach ($blocks as $id => $entity) { + $types[$entity->type] = $entity->type; + } + + // Besides the list of blocks, pass one additional argument to + // hook_custom_block_load(), containing a list of block types that were + // loaded. + $this->hookLoadArguments = array($types); + parent::attachLoad($blocks, $load_revision); + } + + /** + * Overrides Drupal\Core\Entity\DatabaseStorageController::postSave(). + */ + function postSave(EntityInterface $block, $update) { + // Invalidate the block cache to update custom block-based derivatives. + drupal_container()->get('plugin.manager.block')->clearCachedDefinitions(); + } + + /** + * Implements \Drupal\Core\Entity\DataBaseStorageControllerNG::basePropertyDefinitions(). + */ + public function baseFieldDefinitions() { + $properties['id'] = array( + 'label' => t('ID'), + 'description' => t('The custom block ID.'), + 'type' => 'integer_field', + 'read-only' => TRUE, + ); + $properties['uuid'] = array( + 'label' => t('UUID'), + 'description' => t('The custom block UUID.'), + 'type' => 'string_field', + ); + $properties['revision_id'] = array( + 'label' => t('Revision ID'), + 'description' => t('The revision ID.'), + 'type' => 'integer_field', + ); + $properties['langcode'] = array( + 'label' => t('Language code'), + 'description' => t('The comment language code.'), + 'type' => 'language_field', + ); + $properties['info'] = array( + 'label' => t('Subject'), + 'description' => t('The custom block name.'), + 'type' => 'string_field', + ); + $properties['machine_name'] = array( + 'label' => t('Machine name'), + 'description' => t('The custom block machine name.'), + 'type' => 'string_field', + ); + $properties['type'] = array( + 'label' => t('Block type'), + 'description' => t("The block type."), + 'type' => 'string_field', + ); + $properties['log'] = array( + 'label' => t('Revision log message'), + 'description' => t("The revision log message."), + 'type' => 'string_field', + ); + return $properties; + } + +} diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTranslationController.php b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTranslationController.php new file mode 100644 index 0000000..860b9ef --- /dev/null +++ b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTranslationController.php @@ -0,0 +1,59 @@ + 'additional_settings', + '#weight' => 100, + '#attributes' => array( + 'class' => array('custom-block-translation-options'), + ), + ); + } + } + + /** + * Overrides EntityTranslationController::entityFormTitle(). + */ + protected function entityFormTitle(EntityInterface $entity) { + $block_type = entity_load('custom_block_type', $entity->type->value); + return t('Edit @type @title', array('@type' => $block_type->label(), '@title' => $entity->label())); + } + + /** + * Overrides EntityTranslationController::entityFormTitle(). + * + * Custom Block menu paths are keyed by machine name, not id. + */ + protected function getPathInstance($path, $entity_id) { + $entity = entity_load('custom_block', $entity_id); + $wildcard = '%custom_block_machine_name'; + return str_replace($wildcard, $entity->machine_name->value, $path); + } +} diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeFormController.php b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeFormController.php new file mode 100644 index 0000000..eefd485 --- /dev/null +++ b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeFormController.php @@ -0,0 +1,107 @@ + 'textfield', + '#title' => t('Label'), + '#maxlength' => 255, + '#default_value' => $block_type->label(), + '#description' => t("Provide a label for this block type to help identify it in the administration pages."), + '#required' => TRUE, + ); + $form['id'] = array( + '#type' => 'machine_name', + '#default_value' => $block_type->id(), + '#machine_name' => array( + 'exists' => 'custom_block_type_load', + ), + '#disabled' => !$block_type->isNew(), + ); + + $form['revision'] = array( + '#type' => 'checkbox', + '#title' => t('Create new revision'), + '#default_value' => $block_type->revision, + '#description' => t('Create a new revision by default for this block type.') + ); + + if (module_exists('translation_entity')) { + $form['language'] = array( + '#type' => 'details', + '#title' => t('Language settings'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#group' => 'additional_settings', + ); + + $language_configuration = language_get_default_configuration('custom_block', $block_type->id()); + $form['language']['language_configuration'] = array( + '#type' => 'language_configuration', + '#entity_information' => array( + 'entity_type' => 'custom_block', + 'bundle' => $block_type->id(), + ), + '#default_value' => $language_configuration, + ); + + $form['#submit'][] = 'language_configuration_element_submit'; + } + + $form['actions'] = array('#type' => 'actions'); + $form['actions']['submit'] = array( + '#type' => 'submit', + '#value' => t('Save'), + ); + + return $form; + } + + /** + * Overrides Drupal\Core\Entity\EntityFormController::save(). + */ + public function save(array $form, array &$form_state) { + $block_type = $this->getEntity($form_state); + $status = $block_type->save(); + + $uri = $block_type->uri(); + if ($status == SAVED_UPDATED) { + drupal_set_message(t('Custom block type %label has been updated.', array('%label' => $block_type->label()))); + watchdog('custom_block', 'Custom block type %label has been updated.', array('%label' => $block_type->label()), WATCHDOG_NOTICE, l(t('Edit'), $uri['path'] . '/edit')); + } + else { + drupal_set_message(t('Custom block type %label has been added.', array('%label' => $block_type->label()))); + watchdog('custom_block', 'Custom block type %label has been added.', array('%label' => $block_type->label()), WATCHDOG_NOTICE, l(t('Edit'), $uri['path'] . '/edit')); + } + + $form_state['redirect'] = 'admin/structure/custom-blocks'; + } + + /** + * Overrides Drupal\Core\Entity\EntityFormController::delete(). + */ + public function delete(array $form, array &$form_state) { + $block_type = $this->getEntity($form_state); + $form_state['redirect'] = 'admin/structure/custom-blocks/manage/' . $block_type->id() . '/delete'; + } + +} diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeListController.php b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeListController.php new file mode 100644 index 0000000..8f4e333 --- /dev/null +++ b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeListController.php @@ -0,0 +1,60 @@ +uri(); + $operations['manage-fields'] = array( + 'title' => t('Manage fields'), + 'href' => $uri['path'] . '/fields', + 'options' => $uri['options'], + 'weight' => 11, + ); + $operations['manage-display'] = array( + 'title' => t('Manage display'), + 'href' => $uri['path'] . '/display', + 'options' => $uri['options'], + 'weight' => 12, + ); + } + return $operations; + } + + /** + * Overrides Drupal\Core\Entity\EntityListController::buildHeader(). + */ + public function buildHeader() { + $row['type'] = t('Block type'); + $row['operations'] = t('Operations'); + return $row; + } + + /** + * Overrides Drupal\Core\Entity\EntityListController::buildRow(). + */ + public function buildRow(EntityInterface $entity) { + parent::buildRow($entity); + $uri = $entity->uri(); + $row['type'] = l($entity->label(), $uri['path'], $uri['options']); + $row['operations']['data'] = $this->buildOperations($entity); + return $row; + } + +} diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeStorageController.php b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeStorageController.php new file mode 100644 index 0000000..1c8cbc2 --- /dev/null +++ b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeStorageController.php @@ -0,0 +1,44 @@ +id()); + custom_block_add_body_field($entity->id()); + } + elseif ($entity->original->id() != $entity->id()) { + field_attach_rename_bundle('custom_block', $entity->original->id(), $entity->id()); + } + } + + /** + * Overrides \Drupal\Core\Config\Entity\ConfigStorageController::postDelete(). + */ + protected function postDelete($entities) { + parent::postDelete($entities); + + foreach ($entities as $entity) { + field_attach_delete_bundle('custom_block', $entity->id()); + } + } + +} diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Core/Entity/CustomBlock.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Core/Entity/CustomBlock.php new file mode 100644 index 0000000..5688366 --- /dev/null +++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Core/Entity/CustomBlock.php @@ -0,0 +1,202 @@ +id->value; + } + + /** + * Implements Drupal\Core\Entity\EntityInterface::bundle(). + */ + public function bundle() { + return $this->type->value; + } + + /** + * Overrides Drupal\Core\Entity\Entity::createDuplicate(). + */ + public function createDuplicate() { + $duplicate = parent::createDuplicate(); + $duplicate->revision_id->value = NULL; + $duplicate->id->value = NULL; + $duplicate->machine_name->value = NULL; + return $duplicate; + } + + /** + * Overrides Drupal\Core\Entity\Entity::getRevisionId(). + */ + public function getRevisionId() { + return $this->revision_id->value; + } + + /** + * Sets the theme value. + * + * When creating a new custom block from the block library, the user is + * redirected to the configure form for that block in the given theme. The + * theme is stored against the block when the custom block add form is shown. + * + * @param string $theme + * The theme name. + */ + public function setTheme($theme) { + $this->theme = $theme; + } + + /** + * Gets the theme value. + * + * When creating a new custom block from the block library, the user is + * redirected to the configure form for that block in the given theme. The + * theme is stored against the block when the custom block add form is shown. + * + * @return string + * The theme name. + */ + public function getTheme() { + return $this->theme; + } + + /** + * Initialize the object. Invoked upon construction and wake up. + */ + protected function init() { + parent::init(); + // We unset all defined properties, so magic getters apply. + unset($this->id); + unset($this->info); + unset($this->revision_id); + unset($this->log); + unset($this->machine_name); + unset($this->uuid); + unset($this->type); + unset($this->new); + } +} diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Core/Entity/CustomBlockType.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Core/Entity/CustomBlockType.php new file mode 100644 index 0000000..86582db --- /dev/null +++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Core/Entity/CustomBlockType.php @@ -0,0 +1,65 @@ +derivatives[$result->bid] = $base_plugin_definition; - $this->derivatives[$result->bid]['settings'] = array( - 'info' => $result->info, - 'body' => $result->body, - 'format' => $result->format, + $custom_blocks = entity_load_multiple('custom_block', entity_query('custom_block')->execute()); + foreach ($custom_blocks as $custom_block) { + $this->derivatives[$custom_block->machine_name->value] = $base_plugin_definition; + $this->derivatives[$custom_block->machine_name->value]['settings'] = array( + 'info' => $custom_block->info->value, ) + $base_plugin_definition['settings']; - $this->derivatives[$result->bid]['subject'] = $result->info; + $this->derivatives[$custom_block->machine_name->value]['subject'] = $custom_block->info->value; } - $this->derivatives['custom_block'] = $base_plugin_definition; return $this->derivatives; } } diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/block/block/CustomBlock.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/block/block/CustomBlock.php index 84cd757..a4cfa66 100644 --- a/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/block/block/CustomBlock.php +++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/block/block/CustomBlock.php @@ -1,6 +1,6 @@ configuration = parent::getConfig(); $this->configuration['status'] = $definition['settings']['status']; $this->configuration['info'] = $definition['settings']['info']; - $this->configuration['body'] = $definition['settings']['body']; - $this->configuration['format'] = $definition['settings']['format']; + $this->configuration['view_mode'] = $definition['settings']['view_mode']; return $this->configuration; } @@ -47,57 +45,26 @@ public function getConfig() { * Adds body and description fields to the block configuration form. */ public function blockForm($form, &$form_state) { - // @todo Disable this field when editing an existing block and provide a - // separate interface for administering custom blocks. - $form['info'] = array( - '#type' => 'textfield', - '#title' => t('Description'), - '#required' => TRUE, - '#default_value' => $this->configuration['info'], - '#description' => t('A brief description of your block. Used on the Blocks administration page. Changing this field will change the description for all copies of this block.', array('@overview' => url('admin/structure/block'))), - ); - // @todo Disable this field when editing an existing block and provide a - // separate interface for administering custom blocks. - $form['body'] = array( - '#type' => 'text_format', - '#title' => t('Body'), - '#default_value' => $this->configuration['body'], - '#format' => isset($this->configuration['format']) ? $this->configuration['format'] : filter_default_format(), - '#description' => t('The content of the block as shown to the user. Changing this field will change the block body everywhere it is used.'), - '#rows' => 15, - '#required' => TRUE, + $options = array(); + $view_modes = entity_get_view_modes('custom_block'); + foreach ($view_modes as $view_mode => $detail) { + $options[$view_mode] = $detail['label']; + } + $form['custom_block']['view_mode'] = array( + '#type' => 'select', + '#options' => $options, + '#title' => t('View mode'), + '#description' => t('Output the block in this view mode.'), + '#default_value' => $this->configuration['view_mode'] ); $form['title']['#description'] = t('The title of the block as shown to the user.'); return $form; } /** - * Overrides \Drupal\block\BlockBase::blockValidate(). - */ - public function blockValidate($form, &$form_state) { - list(, $bid) = explode(':', $form_state['entity']->get('plugin')); - $custom_block_exists = (bool) db_query_range('SELECT 1 FROM {block_custom} WHERE bid <> :bid AND info = :info', 0, 1, array( - ':bid' => $bid, - ':info' => $form_state['values']['info'], - ))->fetchField(); - if (empty($form_state['values']['info']) || $custom_block_exists) { - form_set_error('info', t('Ensure that each block description is unique.')); - } - } - - /** * Overrides \Drupal\block\BlockBase::blockSubmit(). */ public function blockSubmit($form, &$form_state) { - list(, $bid) = explode(':', $this->getPluginId()); - $block = array( - 'info' => $form_state['values']['info'], - 'body' => $form_state['values']['body']['value'], - 'format' => $form_state['values']['body']['format'], - 'bid' => is_numeric($bid) ? $bid : NULL, - ); - drupal_write_record('block_custom', $block, !is_null($block['bid']) ? array('bid') : array()); - $form_state['entity']->set('plugin', 'custom_block:' . $block['bid']); // Invalidate the block cache to update custom block-based derivatives. if (module_exists('block')) { drupal_container()->get('plugin.manager.block')->clearCachedDefinitions(); @@ -108,12 +75,18 @@ public function blockSubmit($form, &$form_state) { * Implements \Drupal\block\BlockBase::build(). */ public function build() { - // Populate the block with the user-defined block body. - return array( - '#theme' => 'custom_block_block', - '#body' => $this->configuration['body'], - '#format' => $this->configuration['format'], - ); + list(, $machine_name) = explode(':', $this->getPluginId()); + if ($block = custom_block_machine_name_load($machine_name)) { + return entity_view($block, $this->configuration['view_mode']); + } + else { + return array( + '#markup' => t('Block with name %name does not exist. Add custom block.', array( + '%name' => $machine_name, + '!url' => url('block/add') + )), + '#access' => user_access('administer blocks') + ); + } } - } diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockBuildContentTest.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockBuildContentTest.php new file mode 100644 index 0000000..cee6ce8 --- /dev/null +++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockBuildContentTest.php @@ -0,0 +1,41 @@ + 'Rebuild content', + 'description' => 'Test the rebuilding of content for full view modes.', + 'group' => 'Custom Block', + ); + } + + /** + * Ensures that content is rebuilt in calls to custom_block_build_content(). + */ + public function testCustomBlockRebuildContent() { + $block = $this->createCustomBlock(); + + // Set a property in the content array so we can test for its existence later on. + $block->content['test_content_property'] = array( + '#value' => $this->randomString(), + ); + $content = entity_view_multiple(array($block), 'full'); + + // If the property doesn't exist it means the block->content was rebuilt. + $this->assertFalse(isset($content['test_content_property']), 'Custom block content was emptied prior to being built.'); + } +} diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockCreationTest.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockCreationTest.php new file mode 100644 index 0000000..71ee742 --- /dev/null +++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockCreationTest.php @@ -0,0 +1,106 @@ + 'Custom Block creation', + 'description' => 'Create a block and test saving it.', + 'group' => 'Custom Block', + ); + } + + /** + * Sets the test up. + */ + public function setUp() { + parent::setUp(); + $this->drupalLogin($this->adminUser); + } + + /** + * Creates a "Basic page" block and verifies its consistency in the database. + */ + public function testCustomBlockCreation() { + // Create a block. + $edit = array(); + $langcode = LANGUAGE_NOT_SPECIFIED; + $edit['info'] = $this->randomName(8); + $edit['machine_name'] = drupal_strtolower($edit['info']); + $edit["block_body[$langcode][0][value]"] = $this->randomName(16); + $this->drupalPost('block/add/basic', $edit, t('Save')); + + // Check that the Basic block has been created. + $this->assertRaw(format_string('!block %name has been created.', array( + '!block' => 'Basic block', + '%name' => $edit["info"] + )), 'Basic block created.'); + + // Check that the block exists in the database. + $blocks = entity_load_multiple_by_properties('custom_block', array('info' => $edit['info'])); + $block = reset($blocks); + $this->assertTrue($block, 'Custom Block found in database.'); + } + + /** + * Verifies that a transaction rolls back the failed creation. + */ + public function testFailedBlockCreation() { + // Create a block. + try { + $this->createCustomBlock('fail_creation'); + $this->fail('Expected exception has not been thrown.'); + } + catch (Exception $e) { + $this->pass('Expected exception has been thrown.'); + } + + if (Database::getConnection()->supportsTransactions()) { + // Check that the block does not exist in the database. + $id = db_select('custom_block', 'b') + ->fields('b', array('id')) + ->condition('info', 'fail_creation') + ->execute() + ->fetchField(); + $this->assertFalse($id, 'Transactions supported, and block not found in database.'); + } + else { + // Check that the block exists in the database. + $id = db_select('custom_block', 'b') + ->fields('b', array('id')) + ->condition('info', 'fail_creation') + ->execute() + ->fetchField(); + $this->assertTrue($id, 'Transactions not supported, and block found in database.'); + + // Check that the failed rollback was logged. + $records = db_query("SELECT wid FROM {watchdog} WHERE message LIKE 'Explicit rollback failed%'")->fetchAll(); + $this->assertTrue(count($records) > 0, 'Transactions not supported, and rollback error logged to watchdog.'); + } + } +} diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockLoadHooksTest.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockLoadHooksTest.php new file mode 100644 index 0000000..717d659 --- /dev/null +++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockLoadHooksTest.php @@ -0,0 +1,70 @@ + 'Custom Block load hooks', + 'description' => 'Test the hooks invoked when a custom block is being loaded.', + 'group' => 'Custom Block', + ); + } + + /** + * Tests that hook_custom_block_load() is invoked correctly. + */ + public function testHookCustomBlockLoad() { + $other_bundle = $this->createCustomBlockType('other'); + // Create some sample articles and pages. + $custom_block1 = $this->createCustomBlock(); + $custom_block2 = $this->createCustomBlock(); + $custom_block3 = $this->createCustomBlock(); + $custom_block4 = $this->createCustomBlock(FALSE, $other_bundle->id()); + + // Check that when a set of custom blocks that only contains basic blocks is + // loaded, the properties added to the custom block by + // custom_block_test_load_custom_block() correctly reflect the expected + // values. + $custom_blocks = entity_load_multiple_by_properties('custom_block', array('type' => 'basic')); + $loaded_custom_block = end($custom_blocks); + $this->assertEqual($loaded_custom_block->custom_block_test_loaded_ids, array( + $custom_block1->id->value, + $custom_block2->id->value, + $custom_block3->id->value + ), 'hook_custom_block_load() received the correct list of custom_block IDs the first time it was called.'); + $this->assertEqual($loaded_custom_block->custom_block_test_loaded_types, array('basic'), 'hook_custom_block_load() received the correct list of custom block types the first time it was called.'); + + // Now, as part of the same page request, load a set of custom_blocks that contain + // both basic and other bundle, and make sure the parameters passed to + // custom_block_test_custom_block_load() are correctly updated. + $custom_blocks = entity_load_multiple('custom_block', entity_query('custom_block')->execute(), TRUE); + $loaded_custom_block = end($custom_blocks); + $this->assertEqual($loaded_custom_block->custom_block_test_loaded_ids, array( + $custom_block1->id->value, + $custom_block2->id->value, + $custom_block3->id->value, + $custom_block4->id->value + ), 'hook_custom_block_load() received the correct list of custom_block IDs the second time it was called.'); + $this->assertEqual($loaded_custom_block->custom_block_test_loaded_types, array('basic', 'other'), 'hook_custom_block_load() received the correct list of custom_block types the second time it was called.'); + } +} diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockRevisionsTest.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockRevisionsTest.php new file mode 100644 index 0000000..ac995ea --- /dev/null +++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockRevisionsTest.php @@ -0,0 +1,92 @@ + 'Custom Block revisions', + 'description' => 'Create a block with revisions.', + 'group' => 'Custom Block', + ); + } + + /** + * Sets the test up. + */ + public function setUp() { + parent::setUp(); + + // Create initial block. + $block = $this->createCustomBlock('initial'); + + $blocks = array(); + $logs = array(); + + // Get original block. + $blocks[] = $block->revision_id->value; + $logs[] = ''; + + // Create three revisions. + $revision_count = 3; + for ($i = 0; $i < $revision_count; $i++) { + $block->setNewRevision(TRUE); + $logs[] = $block->log->value = $this->randomName(32); + $block->save(); + $blocks[] = $block->revision_id->value; + } + + $this->blocks = $blocks; + $this->logs = $logs; + } + + /** + * Checks block revision related operations. + */ + public function testRevisions() { + $blocks = $this->blocks; + $logs = $this->logs; + + foreach ($blocks as $delta => $revision_id) { + // Confirm the correct revision text appears. + $loaded = entity_revision_load('custom_block', $revision_id); + // Verify log is the same. + $this->assertEqual($loaded->log->value, $logs[$delta], format_string('Correct log message found for revision !revision', array( + '!revision' => $loaded->revision_id->value + ))); + } + + // Confirm that this is the default revision. + $this->assertTrue($loaded->isDefaultRevision(), 'Third block revision is the default one.'); + + // Make a new revision and set it to not be default. + // This will create a new revision that is not "front facing". + // Save this as a non-default revision. + $loaded->setNewRevision(); + $loaded->isDefaultRevision = FALSE; + $loaded->block_body = $this->randomName(8); + $loaded->save(); + + $this->drupalGet('block/' . $loaded->id->value); + $this->assertNoText($loaded->block_body->value, 'Revision body text is not present on default version of block.'); + + // Verify that the non-default revision id is greater than the default + // revision id. + $default_revision = entity_load('custom_block', $loaded->id->value); + $this->assertTrue($loaded->revision_id->value > $default_revision->revision_id->value, 'Revision id is greater than default revision id.'); + } +} diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockSaveTest.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockSaveTest.php new file mode 100644 index 0000000..0cf1074 --- /dev/null +++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockSaveTest.php @@ -0,0 +1,110 @@ + 'Custom Block save', + 'description' => 'Test $custom_block->save() for saving content.', + 'group' => 'Custom Block', + ); + } + + /** + * Sets the test up. + */ + public function setUp() { + parent::setUp(); + $this->drupalLogin($this->adminUser); + } + + /** + * Checks whether custom block IDs are saved properly during an import. + */ + public function testImport() { + // Custom block ID must be a number that is not in the database. + $max_id = db_query('SELECT MAX(id) FROM {custom_block}')->fetchField(); + $test_id = $max_id + mt_rand(1000, 1000000); + $info = $this->randomName(8); + $block = array( + 'info' => $info, + 'machine_name' => $info, + 'block_body' => array(LANGUAGE_NOT_SPECIFIED => array(array('value' => $this->randomName(32)))), + 'type' => 'basic', + 'id' => $test_id + ); + $block = entity_create('custom_block', $block); + $block->enforceIsNew(TRUE); + $block->save(); + + // Verify that block_submit did not wipe the provided id. + $this->assertEqual($block->id->value, $test_id, 'Block imported using provide id'); + + // Test the import saved. + $block_by_id = custom_block_load($test_id); + $this->assertTrue($block_by_id, 'Custom block load by block ID.'); + } + + /** + * Tests determing changes in hook_block_presave(). + * + * Verifies the static block load cache is cleared upon save. + */ + public function testDeterminingChanges() { + // Initial creation. + $block = $this->createCustomBlock('test_changes'); + + // Update the block without applying changes. + $block->save(); + $this->assertEqual($block->label(), 'test_changes', 'No changes have been determined.'); + + // Apply changes. + $block->info->value = 'updated'; + $block->save(); + + // The hook implementations custom_block_test_custom_block_presave() and + // custom_block_test_custom_block_update() determine changes and change the + // title. + $this->assertEqual($block->label(), 'updated_presave_update', 'Changes have been determined.'); + + // Test the static block load cache to be cleared. + $block = custom_block_load($block->id->value); + $this->assertEqual($block->label(), 'updated_presave', 'Static cache has been cleared.'); + } + + /** + * Tests saving a block on block insert. + * + * This test ensures that a block has been fully saved when + * hook_custom_block_insert() is invoked, so that the block can be saved again + * in a hook implementation without errors. + * + * @see block_test_block_insert() + */ + public function testCustomBlockSaveOnInsert() { + // custom_block_test_custom_block_insert() tiggers a save on insert if the + // title equals 'new'. + $block = $this->createCustomBlock('new'); + $this->assertEqual($block->label(), 'CustomBlock ' . $block->id->value, 'Custom block saved on block insert.'); + } +} diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockTestBase.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockTestBase.php new file mode 100644 index 0000000..2310c9e --- /dev/null +++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockTestBase.php @@ -0,0 +1,97 @@ +adminUser = $this->drupalCreateUser($this->permissions); + } + + /** + * Creates a custom block. + * + * @param string $title + * (optional) Title of block. When no value is given uses a random name. + * Defaults to FALSE. + * @param string $bundle + * (optional) Bundle name. Defaults to 'basic'. + * + * @return Drupal\custom_block\Plugin\Core\Entity\CustomBlock + * Created custom block. + */ + protected function createCustomBlock($title = FALSE, $bundle = 'basic') { + $title = ($title ? : $this->randomName()); + if ($custom_block = entity_create('custom_block', array( + 'machine_name' => $title, + 'info' => $title, + 'type' => $bundle, + 'langcode' => 'en' + ))) { + $custom_block->save(); + } + return $custom_block; + } + + /** + * Creates a custom block type (bundle). + * + * @param string $label + * The block type label. + * + * @return Drupal\custom_block\Plugin\Core\Entity\CustomBlockType + * Created custom block type. + */ + protected function createCustomBlockType($label) { + $bundle = entity_create('custom_block_type', array( + 'id' => $label, + 'label' => $label, + 'revision' => FALSE + )); + $bundle->save(); + return $bundle; + } + +} diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockTranslationUITest.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockTranslationUITest.php new file mode 100644 index 0000000..bd29a39 --- /dev/null +++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockTranslationUITest.php @@ -0,0 +1,165 @@ + 'Custom Block translation UI', + 'description' => 'Tests the node translation UI.', + 'group' => 'Custom Block', + ); + } + + /** + * Overrides \Drupal\simpletest\WebTestBase::setUp(). + */ + public function setUp() { + $this->entityType = 'custom_block'; + $this->bundle = 'basic'; + $this->name = drupal_strtolower($this->randomName()); + $this->testLanguageSelector = FALSE; + parent::setUp(); + } + + /** + * Overrides \Drupal\translation_entity\Tests\EntityTranslationUITest::getTranslatorPermission(). + */ + public function getTranslatorPermissions() { + return array( + 'translate any entity', + 'edit original values', + 'access administration pages', + 'administer blocks', + 'administer custom_block fields' + ); + } + + /** + * Creates a custom block. + * + * @param string $title + * (optional) Title of block. When no value is given uses a random name. + * Defaults to FALSE. + * @param string $bundle + * (optional) Bundle name. When no value is given, defaults to + * $this->bundle. Defaults to FALSE. + * + * @return Drupal\custom_block\Plugin\Core\Entity\CustomBlock + * Created custom block. + */ + protected function createCustomBlock($title = FALSE, $bundle = FALSE) { + $title = ($title ? : $this->randomName()); + $bundle = ($bundle ? : $this->bundle); + $custom_block = entity_create('custom_block', array( + 'machine_name' => $title, + 'info' => $title, + 'type' => $bundle, + 'langcode' => 'en' + )); + $custom_block->save(); + return $custom_block; + } + + /** + * Tests field translation form. + */ + public function testFieldTranslationForm() { + $admin_user = $this->drupalCreateUser(array( + 'translate any entity', + 'access administration pages', + 'administer blocks', + 'administer custom_block fields' + )); + $this->drupalLogin($admin_user); + + $block = $this->createCustomBlock($this->name); + + // Visit translation page. + $this->drupalGet('block/' . $block->machine_name->value . '/translations'); + $this->assertRaw('Not translated'); + + // Delete the only translatable field. + field_delete_field('field_test_et_ui_test'); + + // Visit translation page. + $this->drupalGet('block/' . $block->machine_name->value . '/translations'); + $this->assertRaw('no translatable fields'); + } + + /** + * Overrides \Drupal\translation_entity\Tests\EntityTranslationUITest::getNewEntityValues(). + */ + protected function getNewEntityValues($langcode) { + return array('machine_name' => $this->name, 'info' => $this->name) + parent::getNewEntityValues($langcode); + } + + /** + * Test that no metadata is stored for a disabled bundle. + */ + public function testDisabledBundle() { + // Create a bundle that does not have translation enabled. + $disabled_bundle = $this->randomName(); + $bundle = entity_create('custom_block_type', array( + 'id' => $disabled_bundle, + 'label' => $disabled_bundle, + 'revision' => FALSE + )); + $bundle->save(); + + // Create a node for each bundle. + $enabled_custom_block = $this->createCustomBlock(); + $disabled_custom_block = $this->createCustomBlock(FALSE, $bundle->id()); + + // Make sure that only a single row was inserted into the + // {translation_entity} table. + $rows = db_query('SELECT * FROM {translation_entity}')->fetchAll(); + $this->assertEqual(1, count($rows)); + $this->assertEqual($enabled_custom_block->id(), reset($rows)->entity_id); + } + + /** + * Overrides \Drupal\translation_entity\Tests\EntityTranslationUITest::getEditValues(). + */ + protected function getEditValues($values, $langcode, $new = FALSE) { + $edit = parent::getEditValues($values, $langcode, $new); + // Can't submit machine name if not new. + if (!$new) { + unset($edit['machine_name']); + } + return $edit; + } +} diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockTypeTest.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockTypeTest.php new file mode 100644 index 0000000..d94e476 --- /dev/null +++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockTypeTest.php @@ -0,0 +1,136 @@ + 'CustomBlock types', + 'description' => 'Ensures that custom block type functions work correctly.', + 'group' => 'Custom Block', + ); + } + + /** + * Tests creating a block type programmatically and via a form. + */ + public function testCustomBlockTypeCreation() { + // Create a block type programmaticaly. + $type = $this->createCustomBlockType('other'); + + $block_type = entity_load('custom_block_type', 'other'); + $this->assertTrue($block_type, 'The new block type has been created.'); + + // Login a test user. + $this->drupalLogin($this->adminUser); + + $this->drupalGet('block/add/' . $type->id()); + $this->assertResponse(200, 'The new block type can be accessed at bloack/add.'); + + // Create a block type via the user interface. + $edit = array( + 'id' => 'foo', + 'label' => 'title for foo', + ); + $this->drupalPost('admin/structure/custom-blocks/add', $edit, t('Save')); + $block_type = entity_load('custom_block_type', 'foo'); + $this->assertTrue($block_type, 'The new block type has been created.'); + } + + /** + * Tests editing a block type using the UI. + */ + public function testCustomBlockTypeEditing() { + $this->drupalLogin($this->adminUser); + // We need two block types to prevent /block/add redirecting. + $this->createCustomBlockType('other'); + + $instance = field_info_instance('custom_block', 'block_body', 'basic'); + $this->assertEqual($instance['label'], 'Block body', 'Body field was found.'); + + // Verify that title and body fields are displayed. + $this->drupalGet('block/add/basic'); + $this->assertRaw('Block description', 'Block info field was found.'); + $this->assertRaw('Block body', 'Body field was found.'); + + // Change the block type name. + $edit = array( + 'label' => 'Bar', + ); + $this->drupalPost('admin/structure/custom-blocks/manage/basic', $edit, t('Save')); + field_info_cache_clear(); + + $this->drupalGet('block/add'); + $this->assertRaw('Bar', 'New name was displayed.'); + $this->clickLink('Bar'); + $this->assertEqual(url('block/add/basic', array('absolute' => TRUE)), $this->getUrl(), 'Original machine name was used in URL.'); + + // Remove the body field. + $this->drupalPost('admin/structure/custom-blocks/manage/basic/fields/block_body/delete', array(), t('Delete')); + // Resave the settings for this type. + $this->drupalPost('admin/structure/custom-blocks/manage/basic', array(), t('Save')); + // Check that the body field doesn't exist. + $this->drupalGet('block/add/basic'); + $this->assertNoRaw('Block body', 'Body field was not found.'); + } + + /** + * Tests deleting a block type that still has content. + */ + public function testCustomBlockTypeDeletion() { + // Create a block type programmatically. + $type = $this->createCustomBlockType('foo'); + + $this->drupalLogin($this->adminUser); + + // Add a new block of this type. + $block = $this->createCustomBlock(FALSE, 'foo'); + // Attempt to delete the block type, which should not be allowed. + $this->drupalGet('admin/structure/custom-blocks/manage/' . $type->id() . '/delete'); + $this->assertRaw( + t('%label is used by 1 custom block on your site. You can not remove this block type until you have removed all of the %label blocks.', array('%label' => $type->label())), + 'The block type will not be deleted until all blocks of that type are removed.' + ); + $this->assertNoText(t('This action cannot be undone.'), 'The node type deletion confirmation form is not available.'); + + // Delete the block. + $block->delete(); + // Attempt to delete the block type, which should now be allowed. + $this->drupalGet('admin/structure/custom-blocks/manage/' . $type->id() . '/delete'); + $this->assertRaw( + t('Are you sure you want to delete %type?', array('%type' => $type->id())), + 'The block type is available for deletion.' + ); + $this->assertText(t('This action cannot be undone.'), 'The custom block type deletion confirmation form is available.'); + } + +} diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/PageEditTest.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/PageEditTest.php new file mode 100644 index 0000000..ad8246e --- /dev/null +++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/PageEditTest.php @@ -0,0 +1,77 @@ + 'Custom Block edit', + 'description' => 'Create a block and test block edit functionality.', + 'group' => 'Custom Block', + ); + } + + /** + * Sets the test up. + */ + public function setUp() { + parent::setUp(); + } + + /** + * Checks block edit functionality. + */ + public function testPageEdit() { + $this->drupalLogin($this->adminUser); + + $langcode = LANGUAGE_NOT_SPECIFIED; + $title_key = 'info'; + $body_key = "block_body[$langcode][0][value]"; + // Create block to edit. + $edit = array(); + $edit['info'] = $edit['machine_name'] = drupal_strtolower($this->randomName(8)); + $edit[$body_key] = $this->randomName(16); + $this->drupalPost('block/add/basic', $edit, t('Save')); + + // Check that the block exists in the database. + $blocks = entity_query('custom_block')->condition('info', $edit['info'])->execute(); + $block = entity_load('custom_block', reset($blocks)); + $this->assertTrue($block, 'Custom block found in database.'); + + // Load the edit page. + $this->drupalGet('block/' . $block->machine_name->value . '/edit'); + $this->assertFieldByName($title_key, $edit[$title_key], 'Title field displayed.'); + $this->assertFieldByName($body_key, $edit[$body_key], 'Body field displayed.'); + + // Edit the content of the block. + $edit = array(); + $edit[$title_key] = $this->randomName(8); + $edit[$body_key] = $this->randomName(16); + // Stay on the current page, without reloading. + $this->drupalPost(NULL, $edit, t('Save')); + + // Edit the same block, creating a new revision. + $this->drupalGet("block/" . $block->machine_name->value . "/edit"); + $edit = array(); + $edit['info'] = $this->randomName(8); + $edit[$body_key] = $this->randomName(16); + $edit['revision'] = TRUE; + $this->drupalPost(NULL, $edit, t('Save')); + + // Ensure that the block revision has been created. + $revised_block = entity_load('custom_block', $block->id->value, TRUE); + $this->assertNotIdentical($block->revision_id->value, $revised_block->revision_id->value, 'A new revision has been created.'); + } +} diff --git a/core/modules/block/custom_block/tests/modules/custom_block_test/custom_block_test.info b/core/modules/block/custom_block/tests/modules/custom_block_test/custom_block_test.info new file mode 100644 index 0000000..9993892 --- /dev/null +++ b/core/modules/block/custom_block/tests/modules/custom_block_test/custom_block_test.info @@ -0,0 +1,5 @@ +name = "Custom Block module tests" +description = "Support module for custom block related testing." +package = Testing +core = 8.x +hidden = TRUE diff --git a/core/modules/block/custom_block/tests/modules/custom_block_test/custom_block_test.module b/core/modules/block/custom_block/tests/modules/custom_block_test/custom_block_test.module new file mode 100644 index 0000000..76bccdc --- /dev/null +++ b/core/modules/block/custom_block/tests/modules/custom_block_test/custom_block_test.module @@ -0,0 +1,82 @@ +custom_block_test_loaded_ids = $ids; + $custom_block->custom_block_test_loaded_types = $types; + } +} + +/** + * Implements hook_custom_block_view(). + */ +function custom_block_test_custom_block_view(CustomBlock $custom_block, $view_mode) { + // Add extra content. + $custom_block->content['extra_content'] = array( + '#markup' => 'Yowser', + ); +} + +/** + * Implements hook_custom_block_presave(). + */ +function custom_block_test_custom_block_presave(CustomBlock $custom_block) { + if ($custom_block->info->value == 'testing_custom_block_presave') { + $custom_block->info->value .= '_presave'; + } + // Determine changes. + if (!empty($custom_block->original) && $custom_block->original->info->value == 'test_changes') { + if ($custom_block->original->info->value != $custom_block->info->value) { + $custom_block->info->value .= '_presave'; + } + } +} + +/** + * Implements hook_custom_block_update(). + */ +function custom_block_test_custom_block_update(CustomBlock $custom_block) { + // Determine changes on update. + if (!empty($custom_block->original) && $custom_block->original->info->value == 'test_changes') { + if ($custom_block->original->info->value != $custom_block->info->value) { + $custom_block->info->value .= '_update'; + } + } +} + +/** + * Implements hook_custom_block_insert(). + * + * This tests saving a custom_block on custom_block insert. + * + * @see \Drupal\custom_block\Tests\CustomBlockSaveTest::testCustomBlockSaveOnInsert() + */ +function custom_block_test_custom_block_insert(CustomBlock $custom_block) { + // Set the custom_block title to the custom_block ID and save. + if ($custom_block->info->value == 'new') { + $custom_block->info->value = 'CustomBlock '. $custom_block->id->value; + $custom_block->save(); + } + if ($custom_block->info->value == 'fail_creation') { + throw new Exception('Test exception for rollback.'); + } +} diff --git a/core/modules/block/lib/Drupal/block/Plugin/system/plugin_ui/BlockPluginUI.php b/core/modules/block/lib/Drupal/block/Plugin/system/plugin_ui/BlockPluginUI.php index 890dde9..a6d8420 100644 --- a/core/modules/block/lib/Drupal/block/Plugin/system/plugin_ui/BlockPluginUI.php +++ b/core/modules/block/lib/Drupal/block/Plugin/system/plugin_ui/BlockPluginUI.php @@ -73,7 +73,7 @@ public function form($form, &$form_state, $facet = NULL) { $rows = array(); foreach ($plugins as $plugin_id => $display_plugin_definition) { if (empty($facet) || $this->facetCompare($facet, $display_plugin_definition)) { - $rows[] = $this->row($plugin_id, $display_plugin_definition); + $rows[$plugin_id] = $this->row($plugin_id, $display_plugin_definition); } foreach ($plugin_definition['facets'] as $key => $title) { $facets[$key][$display_plugin_definition[$key]] = $this->facetLink($key, $plugin_id, $display_plugin_definition); diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockTest.php index ee51d43..4c7d5b8 100644 --- a/core/modules/block/lib/Drupal/block/Tests/BlockTest.php +++ b/core/modules/block/lib/Drupal/block/Tests/BlockTest.php @@ -91,27 +91,32 @@ public function testCustomBlock() { } } - // Add a new custom block by filling out the input form on the admin/structure/block/add page. + // Add a new custom block by filling out the input form on block/add/basic. $info = strtolower($this->randomName(8)); + $langcode = LANGUAGE_NOT_SPECIFIED; + $values = array( + 'info' => $info, + 'machine_name' => $info, + "block_body[$langcode][0][value]" => $this->randomName(8) + ); + $this->drupalPost('block/add/basic', $values, t('Save')); $custom_block['machine_name'] = $info; - $custom_block['info'] = $info; $custom_block['label'] = $this->randomName(8); - $custom_block['body[value]'] = $this->randomName(32); $custom_block['region'] = $this->regions[0]; - $this->drupalPost("admin/structure/block/list/block_plugin_ui:$default_theme/add/custom_blocks", $custom_block, t('Save block')); + $this->drupalPost(NULL, $custom_block, t('Save block')); $block = entity_load('block', $default_theme . '.' . $info); // Confirm that the custom block has been created, and then query the created bid. - $this->assertText(t('The block configuration has been saved.'), 'Custom block successfully created.'); + $this->assertText(t('The block configuration has been saved.'), 'Custom block instance successfully created.'); - // Check that entity_view() returns the correct title and content. - // @todo This assumes that a block's content can be rendered without its - // wrappers. If this is a reasonable expectation, it should be documented - // elsewhere. + // Check that block_block_view() returns the correct content. $data = entity_view($block, 'content'); - $definition = $block->getPlugin()->getDefinition(); - $config = $definition['settings']; - $this->assertEqual(check_markup($custom_block['body[value]'], $config['format']), render($data), 'BlockInterface::build() provides correct block content.'); + $output = render($data); + + $this->drupalSetcontent($output); + $elements = $this->xpath('//div[@class=:class]', array(':class' => 'field-item even')); + + $this->assertEqual($values["block_body[$langcode][0][value]"], $elements[0], 'BlockInterface::build() provides correct block content.'); // Check whether the block can be moved to all available regions. $custom_block['module'] = 'block'; @@ -144,35 +149,40 @@ public function testCustomBlock() { public function testCustomBlockFormat() { $default_theme = variable_get('theme_default', 'stark'); - // Add a new custom block by filling out the input form on the admin/structure/block/add page. + // Add a new custom block by filling out the input form on block/add/basic. $info = strtolower($this->randomName(8)); + $langcode = LANGUAGE_NOT_SPECIFIED; + $values = array( + 'info' => $info, + 'machine_name' => $info, + "block_body[$langcode][0][value]" => '

Full HTML

', + "block_body[$langcode][0][format]" => 'full_html' + ); + $this->drupalPost('block/add/basic', $values, t('Save')); $custom_block['machine_name'] = $info; - $custom_block['info'] = $info; $custom_block['label'] = $this->randomName(8); - $custom_block['body[value]'] = '

Full HTML

'; - $full_html_format = filter_format_load('full_html'); - $custom_block['body[format]'] = $full_html_format->format; $custom_block['region'] = $this->regions[0]; - $this->drupalPost("admin/structure/block/list/block_plugin_ui:$default_theme/add/custom_blocks", $custom_block, t('Save block')); + $this->drupalPost(NULL, $custom_block, t('Save block')); // Set the created custom block to a specific region. $edit['blocks[' . $default_theme . '.' . $custom_block['machine_name'] . '][region]'] = $this->regions[1]; $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); - // Confirm that the custom block is being displayed using configured text format. + // Confirm that the custom block is being displayed using configured text + // format. $this->drupalGet(''); $this->assertRaw('

Full HTML

', 'Custom block successfully being displayed using Full HTML.'); - // Confirm that a user without access to Full HTML can not see the body field, - // but can still submit the form without errors. + // Confirm that a user without access to Full HTML can not see the body + // field, but can still submit the form without errors. $block_admin = $this->drupalCreateUser(array('administer blocks')); $this->drupalLogin($block_admin); - $this->drupalGet("admin/structure/block/manage/$default_theme.$info/configure"); - $this->assertFieldByXPath("//textarea[@name='body[value]' and @disabled='disabled']", t('This field has been disabled because you do not have sufficient permissions to edit it.'), 'Body field contains denied message'); - $this->drupalPost("admin/structure/block/manage/$default_theme.$info/configure", array(), t('Save block')); + $this->drupalGet("block/$info/edit"); + $this->assertFieldByXPath("//textarea[@name='block_body[und][0][value]' and @disabled='disabled']", t('This field has been disabled because you do not have sufficient permissions to edit it.'), 'Body field contains denied message'); $this->assertNoText(t('Ensure that each block description is unique.')); - // Confirm that the custom block is still being displayed using configured text format. + // Confirm that the custom block is still being displayed using configured + // text format. $this->drupalGet(''); $this->assertRaw('

Full HTML

', 'Custom block successfully being displayed using Full HTML.'); } diff --git a/core/modules/translation_entity/translation_entity.module b/core/modules/translation_entity/translation_entity.module index ee16a9c..8c0e9b5 100644 --- a/core/modules/translation_entity/translation_entity.module +++ b/core/modules/translation_entity/translation_entity.module @@ -203,7 +203,8 @@ function translation_entity_menu_alter(array &$items) { // for this entity type. In some cases the actual base path might not have // a menu loader associated, hence we need to check also for the plain "%" // variant. See for instance comment_menu(). - if (!isset($items[$path]) && !isset($items[_translation_entity_menu_strip_loaders($path)])) { + if (!isset($items[$path]) && !isset($items["$path/edit"]) + && !isset($items[_translation_entity_menu_strip_loaders($path)])) { unset( $items["$path/translations"], $items["$path/translations/add/%language/%language"],