denormalizer-8.x-1.x-dev/denormalizer.module
denormalizer.module
<?php use Drupal\webform\Entity\Webform; /** * @file * Denormalizer - module. */ use Drupal\Core\Database\Query\Select; use Drupal\Core\Database\Database; use Drupal\Core\Entity\EntityInterface; use Drupal\denormalizer\Denormalizer; use Drupal\denormalizer\Entity\DenormalizerTable; /** * Implements hook_cron(). * * Run or reload periodically. */ function denormalizer_cron() { $config = \Drupal::service('config.factory') ->getEditable('denormalizer.settings'); if ($config->get('cron_enabled')) { $last_run = $config->get('last_run'); $reset = NULL; if ($config->get('reload_every') && ($last_run + $config->get('reload_every') * 60 * 60) <= \Drupal::time()->getRequestTime()) { $reset = TRUE; } elseif ($config->get('run_every') && ($last_run + $config->get('run_every') * 60 * 60) <= \Drupal::time()->getRequestTime()) { $reset = FALSE; } if (!is_null($reset)) { $denormalizer = new Denormalizer(); $denormalizer->build(); try { $denormalizer->execute($reset); } catch (PDOException $e) { if (in_array($e->getCode(), ['42S22', '21S01'])) { \Drupal::logger('denormalizer') ->info('Different fields detected. Running reload.'); $denormalizer->execute(TRUE); } } $config->set('last_run', \Drupal::time()->getRequestTime())->save(); \Drupal::logger('denormalizer')->info('Ran denormalizer.'); } } } /** * Implements hook_entity_update(). */ function denormalizer_entity_update(EntityInterface $entity) { if ($entity->getEntityType()->getGroup() == 'content') { DenormalizerTable::onContentEntityCRUD($entity); } } /** * Implements hook_entity_insert(). */ function denormalizer_entity_insert(EntityInterface $entity) { if ($entity->getEntityType()->getGroup() == 'content') { DenormalizerTable::onContentEntityCRUD($entity, TRUE); } } /** * Implements hook_entity_delete(). */ function denormalizer_entity_delete(EntityInterface $entity) { if ($entity->getEntityType()->getGroup() == 'content') { DenormalizerTable::onContentEntityCRUD($entity, FALSE, TRUE); } } /** * Get denormalizer info from module implementation. * * This almost mirrors entity_info() but the 'bundles' key is flat. * * @return array * Denormalizer info. * * @throws \Exception */ function denormalizer_get_info() { $info = \Drupal::moduleHandler()->invokeAll('denormalizer_info'); $entity_type_manager = \Drupal::entityTypeManager(); $denormalizer_tbl_storage = $entity_type_manager->getStorage('denormalizer_table'); /** @var \Drupal\denormalizer\Entity\DenormalizerTableInterface[] $tables */ $tables = $denormalizer_tbl_storage->loadByProperties([ 'status' => 1, // Only use the non entity for this process. 'source' => 'non_entity', ]); foreach ($tables as $table) { $configuration = $table->getSourceConfiguration(); $info[$table->getDbTableName()] = [ 'base table' => $table->getBaseTable(), 'changed_key' => $configuration['changed_key'] ?? '', 'external' => $configuration['external'], ]; } \Drupal::moduleHandler()->alter('denormalizer_info', $info); foreach ($info as $target_table => &$entry) { if (!isset($entry['bundles'])) { $entry['bundles'] = []; } if (isset($entry['entity_type'])) { $entry += (array) $entity_type_manager->getDefinition($entry['entity_type']); } if (!isset($entry['fields'])) { $entry['fields'] = []; } } return $info; } /** * Get the primary key of a table in the Drupal schema. * * @param string $dn_table * The dn_table. * * @return string * The primary key label. */ function denormalizer_get_primary_key($dn_table) { $d = denormalizer_get_info(); $dn_info = $d[$dn_table]; if (isset($d[$dn_table]['primary key'])) { // Manually defined primary key. return $d[$dn_table]['primary key']; } if ($dn_table == 'user') { // Special case for the user table. return 'uid'; } // Drupal defined table. // Replaced "$d['schema_module']" by "$d[$dn_table]['schema_module']". $schema = drupal_get_module_schema($d[$dn_table]['schema_module'], $d[$dn_table]['base table']); if (isset($schema['primary key']) && count($schema['primary key']) == 1) { return $schema['primary key'][0]; } if (isset($dn_info['surrogate key'])) { return $dn_info['surrogate key']; } } /** * Convert db_select to SQL. * * Modified from devel module. * * @param Drupal\Core\Database\Driver\mysql\Select $query * SQL query. * * @return string * SQL of db_select object. */ function denormalizer_dpq(Select $query) { $config = \Drupal::service('config.factory') ->getEditable('denormalizer.settings'); if (method_exists($query, 'preExecute')) { $query->preExecute(); } $sql = (string) $query; $quoted = []; $connection = Database::getConnection(); foreach ((array) $query->arguments() as $key => $val) { $quoted[$key] = $connection->quote($val); } $sql = strtr($sql, $quoted); if ($config->get('db') == 'external') { // DB prefix. $dw_prefix = denormalizer_source_db(); $search = ['{', '}']; $replace = [$dw_prefix . '.' . $connection->tablePrefix(), '']; $sql = str_replace($search, $replace, $sql); } else { $sql = $connection->prefixTables($sql); } return $sql; } /** * Get the current (source) database name. * * @return string * Query result. */ function denormalizer_source_db() { // TODO: Drupal Rector Notice: Please delete the following comment after. // You've made any necessary changes. // You will need to use `\Drupal\core\Database\Database::getConnection()`. // If you do not yet have access to the container here. $db = \Drupal::database()->query('select database()')->fetchField(); return $db; } /** * Get the target database name. * * @return string * Database name. */ function denormalizer_target_db() { // TODO: Drupal Rector Notice: Please delete the following comment after. // You've made any necessary changes. // You will need to use `\Drupal\core\Database\Database::getConnection()`. // If you do not yet have access to the container here. $db = \Drupal::database()->query('select database()')->fetchField(); $config = \Drupal::config('denormalizer.settings'); return $config->get('db_prefix') . $db; } /** * Implements hook_hook_info(). */ function denormalizer_hook_info() { $hooks = []; $hooks['denormalizer_alter'] = [ 'group' => 'denormalizer', ]; $hooks['denormalizer_info'] = [ 'group' => 'denormalizer', ]; return $hooks; } /** * Implements hook_denormalizer_alter(). * * Add some default denormalization alters. * * @throws \Exception */ function denormalizer_denormalizer_alter(Select $q, $view, array $dn) { if ($view == 'user') { $q->addJoin('LEFT', 'users_roles', 'ur', 'ur.uid = dn_user.uid'); $q->addJoin('LEFT', 'role', 'r', 'r.rid = ur.rid'); $q->addExpression("group_concat(r.name SEPARATOR '|')", 'roles'); // Group by primary key, so group_concat will work. $q->groupBy('dn_user.uid'); } if ($view == 'webform_submissions') { $fields = &$q->getFields(); // Remove the integer fields and replace them with dates. unset($fields['submitted']); $q->addExpression('from_unixtime(submitted)', 'submitted'); unset($fields['completed']); $q->addExpression('from_unixtime(completed)', 'completed'); unset($fields['modified']); $q->addExpression('from_unixtime(modified)', 'modified'); } // @todo: move webform_component_bi into dw db if ($view == 'webform_component') { // Remove the name field and replace it with a varchar. $fields = &$q->getFields(); unset($fields['name']); unset($fields['weight']); $q->addExpression('substr(name, 1, 255)', 'name'); unset($fields['extra']); $q->addExpression('cast(weight as decimal(8,2))', 'weight'); // Create surrogate key. $q->addExpression("CONCAT_WS('-', nid, dn_webform_component.form_key)", 'pk'); // Extract grid components into their own form components. // TODO: Drupal Rector Notice: Please delete the following comment after. // You've made any necessary changes. // You will need to use `\Drupal\core\Database\Database::getConnection()`. // If you do not yet have access to the container here. $result = \Drupal::database() ->query("select nid, cid, form_key, extra, weight from {webform_component} where type='grid'"); // Make a fake empty webform component table. $start = microtime(TRUE); $tablename = 'webform_component_bi'; if (db_table_exists($tablename)) { db_drop_table($tablename); } // TODO: Drupal Rector Notice: Please delete the following comment after. // You've made any necessary changes. // You will need to use `\Drupal\core\Database\Database::getConnection()`. // If you do not yet have access to the container here. \Drupal::database()->query("create table {{$tablename}} select nid, cid, pid, cast(form_key as char(255)) as form_key, type, '' as value, required, cast('' as char(255)) as name, cast(weight as decimal(8,2)) as weight, cast('' as char(255)) as pk from {webform_component} limit 0"); $batchValues = []; while ($row = $result->fetch()) { $data = unserialize($row->extra); if (isset($data['questions'])) { $questions = list_extract_allowed_values($data['questions'], 'list_text', FALSE); $i = 0; foreach ($questions as $key => $question) { $i++; $form_key = $row->form_key . '-' . $key; $batchValues[] = [ 'nid' => $row->nid, 'cid' => $row->cid, 'pid' => $row->cid, 'form_key' => $form_key, 'name' => substr($question, 0, 255), 'type' => 'grid_sub', 'weight' => $row->weight + ($i / 100), 'pk' => $row->nid . '-' . $form_key, ]; } } } foreach (array_chunk($batchValues, variable_get('denormalizer_chunk_size', 5000)) as $chunk) { // TODO: Drupal Rector Notice: Please delete the following comment after. // You've made any necessary changes. // You will need to use `\Drupal\core\Database\Database::getConnection()`. // If you do not yet have access to the container here. $insert = \Drupal::database()->insert($tablename)->fields([ 'nid', 'cid', 'pid', 'form_key', 'name', 'type', 'weight', 'pk', ]); foreach ($chunk as $set) { $insert->values($set); } $insert->execute(); } $end = microtime(TRUE); \Drupal::messenger() ->addStatus("Created intermediate TABLE $tablename with @c records in @s seconds.", [ '@c' => count($batchValues), '@s' => round($end - $start, 2), ]); // TODO: Drupal Rector Notice: Please delete the following comment after. // You've made any necessary changes. // You will need to use `\Drupal\core\Database\Database::getConnection()`. // If you do not yet have access to the container here. $gridcomponents = \Drupal::database() ->select($tablename, 'dn_webform_component') ->fields('dn_webform_component'); // We need to mirror the incremental conditions to this union subquery. $conditions = $q->havingConditions(); foreach (element_children($conditions) as $key) { $gridcomponents->havingCondition($conditions[$key]['field'], $conditions[$key]['value'], $conditions[$key]['operator']); } $q->union($gridcomponents, 'ALL'); } // @todo: move webform_component_av into dw db if ($view == 'webform_submitted_data') { // Remove the data field and replace it with a varchar. $fields = &$q->getFields(); unset($fields['data']); $q->addExpression('substr(data, 1, 255)', 'data'); // Create surrogate key. $q->addExpression("CONCAT_WS('-', sid, dn_webform_submitted_data.cid, no)", 'pk'); // Dump unserialized data into a temporary table. $start = microtime(TRUE); $tablename = 'webform_component_av'; if (db_table_exists($tablename)) { db_drop_table($tablename); } // TODO: Drupal Rector Notice: Please delete the following comment after. // You've made any necessary changes. // You will need to use `\Drupal\core\Database\Database::getConnection()`. // If you do not yet have access to the container here. \Drupal::database() ->query("create table {{$tablename}} select nid, cid, cast('' as char(255)) as `key`, cast('' as char(255)) as `value` from {webform_component} limit 0"); db_add_primary_key($tablename, ['nid', 'cid', 'key']); // TODO: Drupal Rector Notice: Please delete the following comment after. // You've made any necessary changes. // You will need to use `\Drupal\core\Database\Database::getConnection()`. // If you do not yet have access to the container here. $result = \Drupal::database() ->query("select nid, cid, extra from {webform_component} where type in ('grid', 'select')"); $batchValues = []; while ($row = $result->fetch()) { $data = unserialize($row->extra); $items = list_extract_allowed_values(isset($data['items']) ? $data['items'] : $data['options'], 'list_text', FALSE); foreach ($items as $key => $value) { $batchValues[] = [ 'nid' => $row->nid, 'cid' => $row->cid, '`key`' => $key, 'value' => substr($value, 0, 255), ]; } } foreach (array_chunk($batchValues, variable_get('denormalizer_chunk_size', 5000)) as $chunk) { // TODO: Drupal Rector Notice: Please delete the following comment after. // You've made any necessary changes. // You will need to use `\Drupal\core\Database\Database::getConnection()`. // If you do not yet have access to the container here. $insert = \Drupal::database()->insert($tablename)->fields([ 'nid', 'cid', '`key`', 'value', ]); foreach ($chunk as $set) { $insert->values($set); } $insert->execute(); } $end = microtime(TRUE); \Drupal::messenger() ->addStatus("Created intermediate TABLE $tablename with @c records in @s seconds.", [ '@c' => count($batchValues), '@s' => round($end - $start, 2), ]); // Join the "webform allowed values" table. $q->addJoin('LEFT', $tablename, 'wav', 'dn_webform_submitted_data.data = wav.key and dn_webform_submitted_data.cid = wav.cid and dn_webform_submitted_data.nid = wav.nid'); $q->fields('wav', ['value']); // We really need the form keys. $q->addJoin('LEFT', 'webform_component', 'wc', 'dn_webform_submitted_data.nid = wc.nid and dn_webform_submitted_data.cid = wc.cid'); // $q->fields('wc', array('form_key')); $q->addExpression("IF(wc.type != 'grid', form_key, (CONCAT_WS('-', form_key, no)))", 'form_key'); } } /** * Sanitize webform data. * @param array $webformData * The webform submitted data. * @param array $columns * The webform fields as columns. * * @return array $sanitized * A new formatted data array. */ function denormalizer_webform_data_sanitize($webformData, $columns) { $sanitized = []; $sid = $webformData[0]->sid; $element = []; foreach ($webformData as $index => $column) { if ($sid == $column->sid) { foreach ($columns as $col) { if (!isset($element['webform_id'])) { $element['webform_id'] = $column->webform_id; $element['sid'] = $column->sid; } if ($column->name == $col) { $element[$col] = $column->value; } elseif ($column->name . "_" . $column->value == $col) { $element[$col] = 1; } } if ($index == count($webformData) - 1) { array_push($sanitized, $element); } } else { $sid = $column->sid; array_push($sanitized, $element); $element = []; foreach ($columns as $col) { if (!isset($element['webform_id'])) { $element['webform_id'] = $column->webform_id; $element['sid'] = $column->sid; } if ($column->name == $col) { $element[$col] = $column->value; } elseif ($column->name . "_" . $column->value == $col) { $element[$col] = 1; } } } } return $sanitized; } /** * Add types to the query columns. * @param array $elements * The array of columns. * * @return array $elements * A new formatted array.. */ function denormalizer_webform_sql_columns($elements) { array_unshift($elements,'sid int unsigned'); array_unshift($elements,'webform_id varchar(32)'); foreach ($elements as $index => $element) { if ($index !== 0 && $index !== 1) { $element = $element . ' mediumtext'; $elements[$index] = $element; } } return $elements; } /** * Get elements from the webform decoded object. * @param string $bundles * The webform_id. * * @return array $columns * The field elements to transform in columns. */ function denormalizer_webform_get_columns($bundles) { $webform = Webform::load($bundles); $elements = $webform->getElementsDecoded(); $columns = []; $fieldTypes = [ 'processed_text', 'checkbox', 'select', 'term_select', 'textarea', 'webform_computed_twig', 'webform_likert', 'webform_term_select', 'text\/css', ]; $columns = denormalizer_webform_explore_elements($elements, $columns, $fieldTypes); return $columns; } /** * A recursive function to look for elements name. * @param array $elements * The object to explore. * @param array $columns * The columns to build. * @param array $fieldTypes * The fields to take in count. * * @return array $columns * The field elements to transform in columns. */ function denormalizer_webform_explore_elements($elements,$columns,$fieldTypes) { foreach ($elements as $key => $element) { if (is_array($element)) { if (isset($element['#type']) && in_array($element['#type'], $fieldTypes)) { array_push($columns, $key); } elseif (isset($element['#type']) && in_array($element['#type'], ['webform_likert', 'checkboxes'])) { foreach ($element['#options'] as $optionKey => $optionValue) { array_push($columns, $key . '_' . $optionKey); } } elseif (isset($element['#type'])) { $result = denormalizer_webform_explore_elements($element,$columns, $fieldTypes); if (!empty($result)) { foreach ($result as $value) { if (!in_array($value,$columns)) { array_push($columns, $value); } } } } } } return $columns; }