4

I have two identical sites on Drupal 8 (A and B), on (A) I created a custom block(block/add), and then I export the configuration(drush config-export) and import this config on (B) (drush config-import), no error, but on the site (B) the block that I imported said on any region that I put "This block is broken or missing. You may be missing content or you might need to enable the original module." And I cant edit the content of this block. How I can fix that?

Site A:

Site A

Site B:

enter image description here

2

5 Answers 5

3

A custom block is made of two entities, one for the placement and the actual content. Only the actual placement can be exported with cmi. The content can not. You can either send your content via REST or you can manually edit the uuid of another block to match. In the future you'll be able to use Entity Pilot (entitypilot.com) - disclaimer - this is my product.

2
  • Where would we edit this UID? In the database? Jun 20, 2015 at 15:58
  • UPDATE block_content SET uuid = 'uuid from block yml file' WHERE id = 'id of another block to match';
    – ya.teck
    Oct 26, 2015 at 14:21
3

For anyone looking for a quick and dirty way to export all custom block data from site A and import it into site B (IMPORTANT: This will overwrite any custom block content on site B):

On site A:

Make a directory to put migration data in:

mkdir block_migration

Export block specific tables to individual files:

mysqldump -u mysql_username -p database_name block_content > block_migration/block_content.sql

mysqldump -u mysql_username -p database_name block_content__body > block_migration/block_content__body.sql

mysqldump -u mysql_username -p database_name block_content_field_data > block_migration/block_content_field_data.sql

mysqldump -u mysql_username -p database_name block_content_field_revision > block_migration/block_content_field_revision.sql

mysqldump -u mysql_username -p database_name block_content_revision > block_migration/block_content_revision.sql

mysqldump -u mysql_username -p database_name block_content_revision__body > block_migration/block_content_revision__body.sql

Transfer these new sql files to Site B via git or other method. If you're doing this often, you could even get fancy and put the above mysqldump commands into a pre-commit hook.

On Site B:

Assuming you've pulled in the code that contains the table drop/create logic we exported above, import the tables into your database:

mysql -u mysql_username -p database_name < block_migration/block_content.sql

mysql -u mysql_username -p database_name < block_migration/block_content__body.sql

mysql -u mysql_username -p database_name < block_migration/block_content_field_data.sql

mysql -u mysql_username -p database_name < block_migration/block_content_field_revision.sql

mysql -u mysql_username -p database_name < block_migration/block_content_revision.sql

mysql -u mysql_username -p database_name < block_migration/block_content_revision__body.sql

Clearing your caches is always a good idea. If you're using drush:

drush cr

2
  • This works great, no messing with UUIDs. One note: you can use drush sqlc < table.sql
    – calebtr
    Apr 12, 2016 at 6:42
  • I'm glad it worked for you @calebtr! Thanks for mentioning drush sqlc! I created git hooks to automate this process for blocks, nodes, users, and menu items: https://github.com/jimafisk/content_mig
    – jimafisk
    Apr 13, 2016 at 13:20
0

Thanks to everyone that posted here; we were really confused about this issue (blocks, block configuration, and the CMI).

Using the information here as a starting point, I created the following script (you can run it with drush php-script from your Drupal root) which will fix the UUIDs in the database. This assumes that the blocks you want to use are already there however (which isn't a problem for us since our blocks come over from a Drupal 6 migration). The script also assumes that the 'info' value for each block, a.k.a. its 'label' is unique, so your mileage may vary.

We will need to figure out a way to script the creation of new blocks as well so that any configuration exported from our development environment can be used in our production environment as well, but that is another story.

<?php
  use Drupal\Component\Serialization\Yaml;

  // we need to parse all uuids from deploy dir and override local uuids
  $dir = \Drupal::root() . '/' . config_get_config_directory('sync') . '/';
  $files = scandir($dir);

  \Drupal\Core\Database\Database::setActiveConnection();
  $connection = \Drupal\Core\Database\Database::getConnection();

  $logger = \Drupal::logger('code_driven_devel');

  // Gather all 'block_content' blocks; store the label and uuid
  $labelled_blocks_to_uuids = array();
  $labels = array();
  foreach($files as $file) {
    $ext = substr($file, -4);
    if ($ext == '.yml') {
      $info = Yaml::decode(file_get_contents($dir . $file));
      $name = substr($file, 0, -4);
      $isblock = substr($name, 0, 5);
      if($isblock == 'block') {
        if(is_array($info) && array_key_exists('settings', $info)) {
          $id = $info['settings']['id'];
          $id_parts = explode(':', $id);
          if ($id_parts[0] == 'block_content') {
            $uuid = $id_parts[1];
            $label = $info['settings']['label'];
            if (strlen($label) > 0) {
              $labelled_blocks_to_uuids[$label] = $uuid;
              array_push($labels, $label);
            }
          }
        }
      }
    }
  }
  $logger->notice(t("Found " . count($labels) . " block_content config YML that may need to have their UUIDs synched..."));
  $results = $connection->select('block_content_field_data', 't')
    ->fields('t')
    ->condition('info', $labels, 'IN')
    ->execute();
  if ($results) {
    foreach ($results as $record) {
      $logger->notice(t("Updating UUID for block_content with id = " . $record->id . "..."));
      $uuid = $labelled_blocks_to_uuids[$record->info];
      $connection->update('block_content')
        ->fields(array(
            'uuid' => $uuid,
            ))
        ->condition('id', $record->id, '=')
        ->execute();
      $logger->notice(t("UUID for block_content with id = " . $record->id . " updated to " . $uuid));
    }
  }
0

Another approach for keeping content that is added as part of development also pushed to test and live environments is to use the Default Content module to export the content. It is built for the content to be exported to an installation profile's 'content' folder, and then the module, if enabled, automatically brings the content in when the site is installed, but it's also possible to import the content one item at a time, such as in an update hook, with the below code in your example.install or example.profile:

<?php
/**
* Import a piece of content exported by default content module.
*/
function example_import_default_content($path_to_content_json) {
  list($entity_type_id, $filename) = explode('/', $path_to_content_json);
  $p = drupal_get_path('profile', 'guts');
  $encoded_content = file_get_contents($p . '/content/' . $path_to_content_json);
  $serializer = \Drupal::service('serializer');
  $content = $serializer->decode($encoded_content, 'hal_json');
  global $base_url;
  $url = $base_url . base_path();
  $content['_links']['type']['href'] = str_replace('http://drupal.org/', $url, $content['_links']['type']['href']);
  $contents = $serializer->encode($content, 'hal_json');
  $class = 'Drupal\\' . $entity_type_id . '\Entity\\' . str_replace(' ', '', ucwords(str_replace('_', ' ', $entity_type_id)));
  $entity = $serializer->deserialize($contents, $class, 'hal_json', array('request_method' => 'POST'));
  $entity->enforceIsNew(TRUE);
  $entity->save();
}

Export a custom block with an ID of 8:

drush dcer block_content 8

(If you don't set your profile path in Drush settings you'll have to specify it above.)

And use the resultant export in your example.install file like this:

<?php
/**
* Add the footer block content.
*
* Implements hook_update_N().
*/
function example_update_8001() {
  example_import_default_content('block_content/136efd63-021e-42ea-8202-8b97305cc07f.json');
}

See easily add default content with update hooks for more.

0

I have just publish a contributed module that solves this. Essentially, the module provides a type of block based on configuration (the fixed block) that wraps a custom block (the content block). The fixed block is stored in the website configuration along as the default content. In your scenario, the custom block is created automatically in the site B with the default content as soon as needed.

Everything is done through UI, no special files or custom module are needed. I named it "Fixed block content" and it is published at:

https://www.drupal.org/project/fixed_block_content

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.