2021-06-05 18:17:47 +02:00
< ? php
namespace Drupal\farm_migrate\EventSubscriber ;
use Drupal\Component\Datetime\TimeInterface ;
use Drupal\Core\Database\Connection ;
2021-08-03 23:13:24 +02:00
use Drupal\Core\Database\Database ;
2021-09-14 19:51:04 +02:00
use Drupal\Core\Entity\EntityTypeManagerInterface ;
2021-09-14 20:27:34 +02:00
use Drupal\Core\State\StateInterface ;
2021-06-05 18:17:47 +02:00
use Drupal\migrate\Event\MigrateEvents ;
use Drupal\migrate\Event\MigrateImportEvent ;
2021-08-03 23:13:24 +02:00
use Drupal\migrate\Event\MigratePostRowSaveEvent ;
2021-09-15 00:44:24 +02:00
use Drupal\migrate\Event\MigrateRowDeleteEvent ;
2021-09-15 18:26:21 +02:00
use Drupal\migrate\Plugin\MigrationPluginManagerInterface ;
2021-06-05 18:17:47 +02:00
use Symfony\Component\EventDispatcher\EventSubscriberInterface ;
/**
2021-09-14 19:30:40 +02:00
* Farm migration event subscriber .
2021-06-05 18:17:47 +02:00
*/
2021-09-14 19:30:40 +02:00
class FarmMigrationSubscriber implements EventSubscriberInterface {
2021-06-05 18:17:47 +02:00
/**
* The database service .
*
* @ var \Drupal\Core\Database\Connection
*/
protected $database ;
/**
* The time service .
*
* @ var \Drupal\Component\Datetime\TimeInterface
*/
protected $time ;
2021-09-14 19:51:04 +02:00
/**
* The entity type manager service .
*
* @ var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager ;
/**
* The role storage .
*
* @ var \Drupal\user\RoleStorageInterface
*/
protected $roleStorage ;
2021-09-14 20:27:34 +02:00
/**
* The state key / value store .
*
* @ var \Drupal\Core\State\StateInterface
*/
protected $state ;
2021-09-15 18:26:21 +02:00
/**
* Migration plugin manager service .
*
* @ var \Drupal\migrate\Plugin\MigrationPluginManagerInterface
*/
protected $migrationPluginManager ;
2021-06-05 18:17:47 +02:00
/**
2021-09-14 19:30:40 +02:00
* FarmMigrationSubscriber Constructor .
2021-06-05 18:17:47 +02:00
*
* @ param \Drupal\Core\Database\Connection $database
* The database service .
* @ param \Drupal\Component\Datetime\TimeInterface $time
* The time service .
2021-09-14 19:51:04 +02:00
* @ param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager service .
2021-09-14 20:27:34 +02:00
* @ param \Drupal\Core\State\StateInterface $state
* The state key / value store .
2021-09-15 18:26:21 +02:00
* @ param \Drupal\migrate\Plugin\MigrationPluginManagerInterface $migration_plugin_manager
* Migration plugin manager service .
2021-06-05 18:17:47 +02:00
*/
2021-09-15 18:26:21 +02:00
public function __construct ( Connection $database , TimeInterface $time , EntityTypeManagerInterface $entity_type_manager , StateInterface $state , MigrationPluginManagerInterface $migration_plugin_manager ) {
2021-06-05 18:17:47 +02:00
$this -> database = $database ;
$this -> time = $time ;
2021-09-14 19:51:04 +02:00
$this -> entityTypeManager = $entity_type_manager ;
$this -> roleStorage = $entity_type_manager -> getStorage ( 'user_role' );
2021-09-14 20:27:34 +02:00
$this -> state = $state ;
2021-09-15 18:26:21 +02:00
$this -> migrationPluginManager = $migration_plugin_manager ;
2021-06-05 18:17:47 +02:00
}
/**
* Get subscribed events .
*
* @ inheritdoc
*/
public static function getSubscribedEvents () {
2021-09-14 19:51:04 +02:00
$events [ MigrateEvents :: PRE_IMPORT ][] = [ 'onMigratePreImport' ];
2021-06-05 18:17:47 +02:00
$events [ MigrateEvents :: POST_IMPORT ][] = [ 'onMigratePostImport' ];
2021-08-03 23:13:24 +02:00
$events [ MigrateEvents :: POST_ROW_SAVE ][] = [ 'onMigratePostRowSave' ];
2021-09-15 00:44:24 +02:00
$events [ MigrateEvents :: PRE_ROW_DELETE ][] = [ 'onMigratePreRowDelete' ];
2021-06-05 18:17:47 +02:00
return $events ;
}
2021-09-14 19:51:04 +02:00
/**
* Run pre - migration logic .
*
* @ param \Drupal\migrate\Event\MigrateImportEvent $event
* The import event object .
*/
public function onMigratePreImport ( MigrateImportEvent $event ) {
$this -> grantTextFormatPermission ( $event );
2021-09-14 20:27:34 +02:00
$this -> allowPrivateFileReferencing ( $event );
2021-09-14 19:51:04 +02:00
}
2021-06-05 18:17:47 +02:00
/**
2021-09-14 19:30:40 +02:00
* Run post - migration logic .
2021-06-05 18:17:47 +02:00
*
* @ param \Drupal\migrate\Event\MigrateImportEvent $event
* The import event object .
*/
public function onMigratePostImport ( MigrateImportEvent $event ) {
2021-09-14 19:51:04 +02:00
$this -> revokeTextFormatPermission ( $event );
2021-09-14 20:27:34 +02:00
$this -> preventPrivateFileReferencing ( $event );
2021-09-14 19:30:40 +02:00
$this -> addRevisionLogMessage ( $event );
}
2021-08-03 23:13:24 +02:00
/**
* Run row post - save logic .
*
* @ param \Drupal\migrate\Event\MigratePostRowSaveEvent $event
* The row save event object .
*/
public function onMigratePostRowSave ( MigratePostRowSaveEvent $event ) {
$this -> migrateSensorListenerData ( $event );
}
2021-09-15 00:44:24 +02:00
/**
* Run row pre - deletion logic .
*
* @ param \Drupal\migrate\Event\MigrateRowDeleteEvent $event
* The row delete event object .
*/
public function onMigratePreRowDelete ( MigrateRowDeleteEvent $event ) {
2021-09-15 01:49:17 +02:00
$this -> deleteAssetParentReferences ( $event );
2021-09-15 00:44:24 +02:00
$this -> deleteLogQuantityReferences ( $event );
2021-09-15 21:32:05 +02:00
$this -> deleteTermParentReferences ( $event );
2021-09-15 20:59:30 +02:00
$this -> deletePlantTypeCompanionReferences ( $event );
2021-09-20 18:09:07 +02:00
$this -> deleteSensorDataStreamReferences ( $event );
2021-09-15 00:44:24 +02:00
}
2021-09-14 19:51:04 +02:00
/**
* Grant default text format permission to anonymous role .
*
* @ param \Drupal\migrate\Event\MigrateImportEvent $event
* The import event object .
*/
public function grantTextFormatPermission ( MigrateImportEvent $event ) {
// If the migration is in the farm_migrate_taxonomy migration group,
// grant the 'use text format default' permission to anonymous role.
// This allows entity validation to pass even when the migration is run
// via Drush (which runs as the anonymous user). The permission is revoked
// in post-migration.
// @see revokeTextFormatPermission()
$migration = $event -> getMigration ();
if ( isset ( $migration -> migration_group ) && $migration -> migration_group == 'farm_migrate_taxonomy' ) {
$anonymous = $this -> roleStorage -> load ( 'anonymous' );
$anonymous -> grantPermission ( 'use text format default' );
$anonymous -> save ();
}
}
/**
* Revoke default text format permission from anonymous role .
*
* @ param \Drupal\migrate\Event\MigrateImportEvent $event
* The import event object .
*/
public function revokeTextFormatPermission ( MigrateImportEvent $event ) {
// If the migration is in the farm_migrate_taxonomy migration group,
// revoke the 'use text format default' permission to anonymous role.
// This permission was added in pre-migration.
// @see grantTextFormatPermission()
$migration = $event -> getMigration ();
if ( isset ( $migration -> migration_group ) && $migration -> migration_group == 'farm_migrate_taxonomy' ) {
$anonymous = $this -> roleStorage -> load ( 'anonymous' );
$anonymous -> revokePermission ( 'use text format default' );
$anonymous -> save ();
}
}
2021-09-14 20:27:34 +02:00
/**
* Temporarily allow private files to be referenced by entities .
*
* @ param \Drupal\migrate\Event\MigrateImportEvent $event
* The import event object .
*/
public function allowPrivateFileReferencing ( MigrateImportEvent $event ) {
// During farmOS 1.x -> 2.x migrations, Drupal's FileAccessControlHandler
// will not allow file entities to be referenced unless they were originally
// uploaded by the same user that created the entity that references them.
// In farmOS, it is common for an entity to be created by one user, and
// photos to be uploaded to it later by a different user. With entity
// validation enabled on the migration, this throws a validation error and
// doesn't allow the file to be referenced.
// We work around this by setting a Drupal state variable during our
// migrations, and check for it in hook_ENTITY_TYPE_access(), so we can
// explicitly grant access to the files.
// This state is removed post-migration.
// @see \Drupal\file\FileAccessControlHandler
// @see farm_migrate_file_access()
// @see preventPrivateFileReferencing()
$migration_groups = [
'farm_migrate_area' ,
'farm_migrate_asset' ,
'farm_migrate_log' ,
'farm_migrate_plan' ,
'farm_migrate_taxonomy' ,
];
$migration = $event -> getMigration ();
if ( isset ( $migration -> migration_group ) && in_array ( $migration -> migration_group , $migration_groups )) {
$this -> state -> set ( 'farm_migrate_allow_file_referencing' , TRUE );
}
}
/**
* Prevent private files from being referenced by entities .
*
* @ param \Drupal\migrate\Event\MigrateImportEvent $event
* The import event object .
*/
public function preventPrivateFileReferencing ( MigrateImportEvent $event ) {
// Unset the Drupal state variable that was set to temporarily allow private
// files to be referenced by entities.
// @see farm_migrate_file_access()
// @see allowPrivateFileReferencing()
$this -> state -> delete ( 'farm_migrate_allow_file_referencing' );
}
2021-09-14 19:30:40 +02:00
/**
* Add a revision log message to imported entities .
*
* @ param \Drupal\migrate\Event\MigrateImportEvent $event
* The import event object .
*/
public function addRevisionLogMessage ( MigrateImportEvent $event ) {
2021-06-05 18:17:47 +02:00
// Define the migration groups that we will post-process and their
// corresponding entity revision tables.
$groups_revision_tables = [
'farm_migrate_asset' => 'asset_revision' ,
'farm_migrate_area' => 'asset_revision' ,
'farm_migrate_log' => 'log_revision' ,
'farm_migrate_plan' => 'plan_revision' ,
'farm_migrate_quantity' => 'quantity_revision' ,
'farm_migrate_taxonomy' => 'taxonomy_term_revision' ,
];
$migration = $event -> getMigration ();
if ( isset ( $migration -> migration_group ) && array_key_exists ( $migration -> migration_group , $groups_revision_tables )) {
// Define the entity id column name. This will be "id" in all cases
// except taxonomy_terms, which use "tid".
$id_column = 'id' ;
if ( $migration -> migration_group == 'farm_migrate_taxonomy' ) {
$id_column = 'tid' ;
}
// Build a query to set the revision log message.
$revision_table = $groups_revision_tables [ $migration -> migration_group ];
$migration_id = $migration -> id ();
$query = " UPDATE { $revision_table }
SET revision_log_message = : revision_log_message
WHERE revision_id IN (
SELECT r . revision_id
FROM { migrate_map_ $migration_id } mm
INNER JOIN { $revision_table } r ON mm . destid1 = r . $id_column
) " ;
$args = [
':revision_log_message' => 'Migrated from farmOS 1.x on ' . date ( 'Y-m-d' , $this -> time -> getRequestTime ()),
];
$this -> database -> query ( $query , $args );
}
}
2021-08-03 23:13:24 +02:00
/**
* Migrate sensor listener data .
*
* @ param \Drupal\migrate\Event\MigratePostRowSaveEvent $event
* The post row save migrate event .
*/
public function migrateSensorListenerData ( MigratePostRowSaveEvent $event ) {
$migration = $event -> getMigration ();
$migration_id = $migration -> id ();
// Migrate listener sensor data for each data stream.
if ( $migration_id === " farm_migrate_sensor_listener_data_streams " ) {
// Get values to identify the source data.
$migration_row = $event -> getRow ();
$source_id = $migration_row -> getSourceProperty ( 'id' );
$source_name = $migration_row -> getSourceProperty ( 'name' );
// Get the destination data stream ID.
$destination_id = $event -> getDestinationIdValues ()[ 0 ];
// Query the source for data. Override the ID field that is returned with
// each row to be the ID of the migrated data stream.
$query = " SELECT :id as id, timestamp, value_numerator, value_denominator
FROM { farm_sensor_data } fsd
WHERE fsd . id = : sensor_id and fsd . name = : name
" ;
$args = [
'id' => $destination_id ,
'sensor_id' => $source_id ,
'name' => $source_name ,
];
$source_db = Database :: getConnection ( 'default' , 'migrate' );
$source_data = $source_db -> query ( $query , $args );
$source_data -> setFetchMode ( \PDO :: FETCH_ASSOC );
// Start an insert statement.
$insert = $this -> database -> insert ( 'data_stream_basic' )
-> fields ([ 'id' , 'timestamp' , 'value_numerator' , 'value_denominator' ]);
// Loop through the source data and insert in batches.
$batch_size = 10000 ;
$count = 0 ;
foreach ( $source_data as $data ) {
$insert -> values ( $data );
$count ++ ;
if ( $count >= $batch_size ) {
$insert -> execute ();
$count = 0 ;
$insert = $this -> database -> insert ( 'data_stream_basic' )
-> fields ([ 'id' , 'timestamp' , 'value_numerator' , 'value_denominator' ]);
}
}
$insert -> execute ();
}
}
2021-09-15 00:44:24 +02:00
/**
* Delete references to quantities from logs .
*
* @ param \Drupal\migrate\Event\MigrateRowDeleteEvent $event
* The row delete event object .
*/
public function deleteLogQuantityReferences ( MigrateRowDeleteEvent $event ) {
// If the migration is in the farm_migrate_log migration group, delete all
// references to quantity entities from the log that is being deleted.
// This prevents the quantity entity itself from being deleted by
// LogEventSubscriber in the farm_log_quantity module.
2021-09-15 18:26:21 +02:00
// We limit this to quantities that were created via migrations in the
// farm_migrate_quantity migration group to ensure that quantities created
// via the create_quantity process plugin can be deleted normally with logs.
2021-09-15 00:44:24 +02:00
// @see \Drupal\farm_log_quantity\EventSubscriber\LogEventSubscriber
$migration = $event -> getMigration ();
if ( isset ( $migration -> migration_group ) && $migration -> migration_group == 'farm_migrate_log' ) {
$id_values = $event -> getDestinationIdValues ();
if ( ! empty ( $id_values [ 'id' ])) {
2021-09-15 18:26:21 +02:00
$migration_plugins = $this -> migrationPluginManager -> createInstances ([]);
foreach ( $migration_plugins as $migration_id => $migration_plugin ) {
if ( isset ( $migration_plugin -> migration_group ) && $migration_plugin -> migration_group == 'farm_migrate_quantity' ) {
$table = 'migrate_map_' . $migration_id ;
$this -> database -> query ( 'DELETE FROM {log__quantity} WHERE entity_id = :id AND quantity_target_id IN (SELECT destid1 FROM ' . $table . ')' , [ ':id' => $id_values [ 'id' ]]);
$this -> database -> query ( 'DELETE FROM {log_revision__quantity} WHERE entity_id = :id AND quantity_target_id IN (SELECT destid1 FROM ' . $table . ')' , [ ':id' => $id_values [ 'id' ]]);
}
}
2021-09-15 00:44:24 +02:00
}
}
}
2021-09-15 01:49:17 +02:00
/**
* Delete parent references from assets .
*
* @ param \Drupal\migrate\Event\MigrateRowDeleteEvent $event
* The row delete event object .
*/
public function deleteAssetParentReferences ( MigrateRowDeleteEvent $event ) {
// If the migration is in the farm_migrate_asset or farm_migrate_area
// migration groups, delete all parent references to the destination asset.
// This is necessary because the field is populated by migrations in the
// farm_migrate_asset_parent group, which ONLY set the parent field on
// existing assets, and rolling back those migrations does not remove the
// parent references. This causes entity reference integrity constraint
// errors if an attempt is made to roll back assets that are referenced as
// parents.
$migration = $event -> getMigration ();
if ( isset ( $migration -> migration_group ) && in_array ( $migration -> migration_group , [ 'farm_migrate_asset' , 'farm_migrate_area' ])) {
$id_values = $event -> getDestinationIdValues ();
if ( ! empty ( $id_values [ 'id' ])) {
$this -> database -> query ( 'DELETE FROM {asset__parent} WHERE parent_target_id = :id' , [ ':id' => $id_values [ 'id' ]]);
$this -> database -> query ( 'DELETE FROM {asset_revision__parent} WHERE parent_target_id = :id' , [ ':id' => $id_values [ 'id' ]]);
}
}
}
2021-09-15 21:32:05 +02:00
/**
* Delete parent references to taxonomy pterms .
*
* @ param \Drupal\migrate\Event\MigrateRowDeleteEvent $event
* The row delete event object .
*/
public function deleteTermParentReferences ( MigrateRowDeleteEvent $event ) {
// If the migration is in the farm_migrate_taxonomy migration group, delete
// all parent references to the destination term.
// This is necessary to prevent entity reference integrity constraint errors
// if an attempt is made to roll back terms that are referenced as parents.
$migration = $event -> getMigration ();
if ( isset ( $migration -> migration_group ) && $migration -> migration_group == 'farm_migrate_taxonomy' ) {
$id_values = $event -> getDestinationIdValues ();
if ( ! empty ( $id_values [ 'tid' ])) {
$this -> database -> query ( 'DELETE FROM {taxonomy_term__parent} WHERE parent_target_id = :tid' , [ ':tid' => $id_values [ 'tid' ]]);
$this -> database -> query ( 'DELETE FROM {taxonomy_term_revision__parent} WHERE parent_target_id = :tid' , [ ':tid' => $id_values [ 'tid' ]]);
}
}
}
2021-09-15 20:59:30 +02:00
/**
* Delete companion references to plant type terms .
*
* @ param \Drupal\migrate\Event\MigrateRowDeleteEvent $event
* The row delete event object .
*/
public function deletePlantTypeCompanionReferences ( MigrateRowDeleteEvent $event ) {
// If this is the farm_migrate_taxonomy_plant_type migration, delete all
// companion references to the destination term.
// This is necessary to prevent entity reference integrity constraint errors
// if an attempt is made to roll back terms that are referenced as
// companions.
$migration = $event -> getMigration ();
if ( $migration -> id () == 'farm_migrate_taxonomy_plant_type' ) {
$id_values = $event -> getDestinationIdValues ();
if ( ! empty ( $id_values [ 'tid' ])) {
$this -> database -> query ( 'DELETE FROM {taxonomy_term__companions} WHERE companions_target_id = :tid' , [ ':tid' => $id_values [ 'tid' ]]);
$this -> database -> query ( 'DELETE FROM {taxonomy_term_revision__companions} WHERE companions_target_id = :tid' , [ ':tid' => $id_values [ 'tid' ]]);
}
}
}
2021-09-20 18:09:07 +02:00
/**
* Delete data stream references from sensor assets .
*
* @ param \Drupal\migrate\Event\MigrateRowDeleteEvent $event
* The row delete event object .
*/
public function deleteSensorDataStreamReferences ( MigrateRowDeleteEvent $event ) {
// If this is the farm_migrate_sensor_listener_data_streams migration,
// delete all references to the destination data_stream from sensor assets.
// This is necessary to prevent entity reference integrity constraint errors
// if an attempt is made to roll back data_streams that are referenced by
// sensor assets. During migration, these references are created by the
// DataStream migrate destination plugin, so they will be recreated if this
// migration is imported again after rollback.
// @see \Drupal\data_stream\Plugin\migrate\destination\DataStream
$migration = $event -> getMigration ();
if ( $migration -> id () == 'farm_migrate_sensor_listener_data_streams' ) {
$id_values = $event -> getDestinationIdValues ();
if ( ! empty ( $id_values [ 'id' ])) {
$this -> database -> query ( 'DELETE FROM {asset__data_stream} WHERE data_stream_target_id = :id' , [ ':id' => $id_values [ 'id' ]]);
$this -> database -> query ( 'DELETE FROM {asset_revision__data_stream} WHERE data_stream_target_id = :id' , [ ':id' => $id_values [ 'id' ]]);
}
}
}
2021-06-05 18:17:47 +02:00
}