Last active
March 23, 2021 08:07
-
-
Save jhedstrom/0a1723a94484e3a0e46e2692d4698fe6 to your computer and use it in GitHub Desktop.
Custom content moderation update hooks from 8.2.x to 8.3.x
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* @file | |
* Install file for content_moderation. | |
*/ | |
use Drupal\content_moderation\Entity\ContentModerationState; | |
use Drupal\Core\Field\BaseFieldDefinition; | |
use Drupal\Core\Database\Database; | |
use Drupal\Core\Utility\UpdateException; | |
/** | |
* Install the workflows module. | |
*/ | |
function content_moderation_update_8301() { | |
\Drupal::service('module_installer')->install(['workflows']); | |
} | |
/** | |
* Update content_moderation_state schema for 8.3. | |
*/ | |
function content_moderation_update_8302() { | |
// Update max_length of the content_entity_type_id so the unique index can | |
// be added without being too long. | |
// @see https://www.drupal.org/node/2779931 | |
$definition = \Drupal::entityDefinitionUpdateManager()->getFieldStorageDefinition('content_entity_type_id', 'content_moderation_state'); | |
// Calling `setSetting` with a new `max_length` here doesn't appear to work, | |
// so the database columns are directly updated. | |
$spec = $definition->getColumns(); | |
$spec['value']['length'] = EntityTypeInterface::ID_MAX_LENGTH; | |
db_change_field('content_moderation_state_field_data', 'content_entity_type_id', 'content_entity_type_id', $spec['value']); | |
db_change_field('content_moderation_state_field_revision', 'content_entity_type_id', 'content_entity_type_id', $spec['value']); | |
// Install the new workflow entity reference field. | |
$new_workflow_field = BaseFieldDefinition::create('entity_reference') | |
->setLabel(t('Workflow')) | |
->setDescription(t('The workflow the moderation state is in.')) | |
->setSetting('target_type', 'workflow') | |
->setRequired(TRUE) | |
->setRevisionable(TRUE) | |
->setTranslatable(TRUE); | |
\Drupal::entityDefinitionUpdateManager()->installFieldStorageDefinition('workflow', 'content_moderation_state', 'content_moderation', $new_workflow_field); | |
} | |
/** | |
* Create the new workflow entities and migrate states/transitions. | |
*/ | |
function content_moderation_update_8303() { | |
// Most of the logic for this is copied from the simpler use-case in | |
// https://www.drupal.org/node/2846618. | |
$new_workflows = [ | |
// Machine name => label. | |
// Update these as needed for your project. It assumes that states and | |
// transitions in the old 8.2 version were prefixed with these machine | |
// names. Logic for multiple workflows utilizing a different strategy would | |
// need to be tweaked below. | |
'cusalert' => t('Customer alert'), | |
'escal' => t('Escalation'), | |
'general' => t('General'), | |
]; | |
// Create workflow entities for each. | |
$workflows = []; | |
foreach ($new_workflows as $id => $label) { | |
// During update hooks, it is best practice to work with the raw data, | |
// rather than the config entity objects themselves. | |
$workflows[$id] = [ | |
'id' => $id, | |
'label' => $label, | |
'type' => 'content_moderation', | |
'states' => [], | |
'transitions' => [], | |
'type_settings' => [ | |
'states' => [], | |
], | |
]; | |
} | |
// Loop through all states and add them to the appropriate workflow. | |
foreach (\Drupal::configFactory()->listAll('content_moderation.state.') as $id) { | |
$state = \Drupal::configFactory()->getEditable($id)->get(); | |
list($workflow_id, $state_id) = explode('_', $state['id'], 2); | |
if (!isset($workflows[$workflow_id])) { | |
throw new UpdateException('Workflow ID "' . $workflow_id . '"" does not exist.'); | |
} | |
$workflows[$workflow_id]['states'][$state_id] = [ | |
'label' => $state['label'], | |
'weight' => 0, | |
]; | |
$workflows[$workflow_id]['type_settings'][$state_id] = [ | |
'published' => $state['published'], | |
'default_revision' => $state['default_revision'], | |
]; | |
} | |
// Loop through all transitions and add them to the appropriate workflow. | |
foreach (\Drupal::configFactory()->listAll('content_moderation.state_transition.') as $id) { | |
$transition = \Drupal::configFactory()->getEditable($id)->get(); | |
list ($workflow_id, $transition_id) = explode('_', $transition['id'], 2); | |
// Special handling for the 'archive' transition. | |
if ($workflow_id === 'archive') { | |
$workflow_id = 'general'; | |
$transition_id = 'archive'; | |
} | |
// Special handling for the 'keep_in_review' transition. | |
elseif ($workflow_id === 'keep') { | |
$workflow_id = 'general'; | |
$transition_id = 'keep_in_review'; | |
} | |
if (!isset($workflows[$workflow_id])) { | |
throw new UpdateException('Workflow ID "' . $workflow_id . '"" does not exist.'); | |
} | |
// Pull out the old workflow ID from the to and from state IDs. | |
$from_state = str_replace($workflow_id . '_', '', $transition['stateFrom']); | |
$to_state = str_replace($workflow_id . '_', '', $transition['stateTo']); | |
$workflows[$workflow_id]['transitions'][$transition_id] = [ | |
'label' => $transition['label'], | |
'from' => [$from_state], | |
'to' => $to_state, | |
'weight' => 0, | |
]; | |
} | |
// Get enabled node types for old state transitions. | |
$enabled_workflows = _content_moderation_update_8300_get_bundle_info(); | |
// Save all the new workflows. | |
foreach ($workflows as $id => $workflow) { | |
$config = \Drupal::configFactory()->getEditable('workflows.workflow.' . $id); | |
// Set enabled entity types. | |
$workflow['type_settings']['entity_types'] = isset($enabled_workflows[$id]) ? $enabled_workflows[$id] : []; | |
$config->setData($workflow); | |
$config->save(); | |
} | |
// Delete the old states and transitions. | |
foreach (\Drupal::configFactory()->listAll('content_moderation.state_transition.') as $id) { | |
$transition = \Drupal::configFactory()->getEditable($id); | |
$transition->delete(); | |
} | |
foreach (\Drupal::configFactory()->listAll('content_moderation.state.') as $id) { | |
$state = \Drupal::configFactory()->getEditable($id); | |
$state->delete(); | |
} | |
} | |
/** | |
* Update existing content to the new workflows. | |
*/ | |
function content_moderation_update_8304(&$sandbox) { | |
// Work in batches. | |
$moderation_state_storage = \Drupal::entityTypeManager()->getStorage('content_moderation_state'); | |
if (!isset($sandbox['progress'])) { | |
$sandbox['progress'] = 0; | |
$sandbox['current_item_id'] = 0; | |
$sandbox['max'] = Database::getConnection()->query('SELECT COUNT(DISTINCT id, revision_id, langcode, default_langcode, moderation_state) FROM {content_moderation_state_field_revision} WHERE default_langcode = 1')->fetchField(); | |
// Copy data over to a temporary table. This is necessary because we need | |
// to remove the old field, and add the new base field. | |
// @see https://www.drupal.org/node/2846618 | |
Database::getConnection()->query('CREATE TABLE {wdc_workflow_update_tracker} AS (SELECT DISTINCT id, revision_id, langcode, default_langcode, moderation_state) FROM {content_moderation_state_field_revision} ORDER BY revision_id'); | |
Database::getConnection()->query('CREATE INDEX content_moderation_update_index ON {content_moderation_update_tracker} (revision_id, default_langcode)'); | |
// Remove old field, first purging data. | |
Database::getConnection() | |
->update('content_moderation_state_field_data') | |
->fields(['moderation_state' => NULL]) | |
->execute(); | |
Database::getConnection() | |
->update('content_moderation_state_field_revision') | |
->fields(['moderation_state' => NULL]) | |
->execute(); | |
$entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager(); | |
$old_field = $entity_definition_update_manager->getFieldStorageDefinition('moderation_state', 'content_moderation_state'); | |
$entity_definition_update_manager->uninstallFieldStorageDefinition($old_field); | |
// Add the new string field. | |
$new_field = BaseFieldDefinition::create('string') | |
->setLabel(t('Moderation state')) | |
->setDescription(t('The moderation state of the referenced content.')) | |
->setRequired(TRUE) | |
->setTranslatable(TRUE) | |
->setRevisionable(TRUE); | |
$entity_definition_update_manager->installFieldStorageDefinition('moderation_state', 'content_moderation_state', 'content_moderation', $new_field); | |
} | |
// Groups of 200. | |
$moderation_states = Database::getConnection() | |
->select('content_moderation_update_tracker', 'ut') | |
->fields('ut', ['id', 'revision_id', 'moderation_state']) | |
->condition('revision_id', $sandbox['current_item_id'], '>') | |
->condition('default_langcode', 1) | |
->range(0, 200) | |
->orderBy('revision_id', 'ASC') | |
->execute(); | |
foreach ($moderation_states as $record) { | |
list($workflow_id, $state_id) = explode('_', $record->moderation_state, 2); | |
/** @var \Drupal\content_moderation\ContentModerationStateInterface $moderation_state */ | |
$moderation_state = $moderation_state_storage->loadRevision($record->revision_id); | |
$moderation_state->workflow->target_id = $workflow_id; | |
$moderation_state->moderation_state->value = $state_id; | |
ContentModerationState::updateOrCreateFromEntity($moderation_state); | |
// Check for and process translations. | |
_content_moderation_update_8304_process_translations($record, $moderation_state); | |
$sandbox['progress']++; | |
$sandbox['current_item_id'] = $record->revision_id; | |
} | |
$sandbox['#finished'] = empty($sandbox['max']) ? 1 : ($sandbox['progress'] / $sandbox['max']); | |
if ($sandbox['#finished'] >= 1) { | |
// Drop the update table. | |
Database::getConnection()->query('DROP TABLE {content_moderation_update_tracker}'); | |
return t('Updated @count content moderation state records.', ['@count' => $sandbox['max']]); | |
} | |
// Print more useful status if running from drush. | |
if (function_exists('drush_print')) { | |
drush_print(t('Finished updating @percent percent of the content moderation state records', ['@percent' => round($sandbox['#finished'] * 100, 1)]), 5); | |
} | |
} | |
/** | |
* Process any translated workflows. | |
* | |
* @param \stdClass $record | |
* Database result record containing id, revision_id, and moderation_state. | |
* @param \Drupal\content_moderation\Entity\ContentModerationState $moderation_state | |
* The default language moderation state. | |
*/ | |
function _content_moderation_update_8304_process_translations(stdClass $record, ContentModerationState $moderation_state) { | |
$translations = Database::getConnection() | |
->select('content_moderation_update_tracker', 'ut') | |
->fields('ut', ['moderation_state', 'langcode']) | |
->condition('revision_id', $record->revision_id) | |
->condition('default_langcode', 1, '<>') | |
->execute(); | |
foreach ($translations as $translation) { | |
list($workflow_id, $state_id) = explode('_', $translation->moderation_state, 2); | |
$moderation_state = $moderation_state->getTranslation($translation->langcode); | |
$moderation_state->workflow->target_id = $workflow_id; | |
$moderation_state->moderation_state->value = $state_id; | |
ContentModerationState::updateOrCreateFromEntity($moderation_state); | |
} | |
} | |
/** | |
* Helper function to gather which workflow is enabled for node types. | |
* | |
* @return array | |
* An array of node types keyed by workflow ID. | |
*/ | |
function _content_moderation_update_8300_get_bundle_info() { | |
$enabled_bundles = []; | |
// Loop through node type configs and find enabled workflows. | |
// Note, we can hard-code node here since moderation is not enabled on other | |
// entity types. | |
foreach (\Drupal::configFactory()->listAll('node.type.') as $node_type_id) { | |
$bundle_config = \Drupal::configFactory()->getEditable($node_type_id); | |
if (!$third_party_settings = $bundle_config->get('third_party_settings')) { | |
continue; | |
} | |
// Remove content moderation from the third party settings if it exists. | |
$third_party_settings_updated = array_diff_key($third_party_settings, array_flip(['content_moderation'])); | |
if (count($third_party_settings) !== $third_party_settings_updated) { | |
$content_moderation = $third_party_settings['content_moderation']; | |
if (!empty($content_moderation['enabled'])) { | |
// Infer the workflow from the default state ID. | |
list($workflow_id,) = explode('_', $content_moderation['default_moderation_state'], 2); | |
$enabled_bundles[$workflow_id]['node'][] = $bundle_config->get('type'); | |
} | |
} | |
// Update third party settings. | |
if (empty($third_party_settings_updated)) { | |
$bundle_config->clear('third_party_settings'); | |
} | |
else { | |
$bundle_config->set('third_party_settings', $third_party_settings_updated); | |
} | |
$bundle_config->save(); | |
} | |
return $enabled_bundles; | |
} | |
/** | |
* Implements hook_update_dependencies(). | |
*/ | |
function content_moderation_update_dependencies() { | |
$dependencies = []; | |
// The workflows module must be enabled for many of the 8.3 update hooks | |
// to properly complete. | |
$dependencies['system'][8300] = [ | |
'content_moderation' => 8301, | |
]; | |
$dependencies['comment'][8300] = [ | |
'content_moderation' => 8301, | |
]; | |
$dependencies['block_content'][8300] = [ | |
'content_moderation' => 8301, | |
]; | |
return $dependencies; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment