/** * Ensures that post update functions are removed on uninstall. */ public function testUninstallPostUpdateFunctions() { \Drupal::service('module_installer')->uninstall(['module_test']); $post_update_key_value = \Drupal::keyValue('post_update'); $existing_updates = $post_update_key_value->get('existing_updates', []); $this->assertFalse(in_array('module_test_post_update_test', $existing_updates)); }
/** * Tests hook_post_update_NAME(). */ public function testPostUpdate() { $this->runUpdates(); $this->assertRaw('<h3>Update first</h3>'); $this->assertRaw('First update'); $this->assertRaw('<h3>Update second</h3>'); $this->assertRaw('Second update'); $this->assertRaw('<h3>Update test1</h3>'); $this->assertRaw('Test1 update'); $this->assertRaw('<h3>Update test0</h3>'); $this->assertRaw('Test0 update'); $this->assertRaw('<h3>Update test_batch</h3>'); $this->assertRaw('Test post update batches'); // Test state value set by each post update. $updates = ['update_test_postupdate_post_update_first', 'update_test_postupdate_post_update_second', 'update_test_postupdate_post_update_test1', 'update_test_postupdate_post_update_test0', 'update_test_postupdate_post_update_test_batch-1', 'update_test_postupdate_post_update_test_batch-2', 'update_test_postupdate_post_update_test_batch-3']; $this->assertIdentical($updates, \Drupal::state()->get('post_update_test_execution', [])); // Test post_update key value stores contains a list of the update functions // that have run. $existing_updates = array_count_values(\Drupal::keyValue('post_update')->get('existing_updates')); $expected_updates = ['update_test_postupdate_post_update_first', 'update_test_postupdate_post_update_second', 'update_test_postupdate_post_update_test1', 'update_test_postupdate_post_update_test0', 'update_test_postupdate_post_update_test_batch']; foreach ($expected_updates as $expected_update) { $this->assertEqual($existing_updates[$expected_update], 1, new FormattableMarkup("@expected_update exists in 'existing_updates' key and only appears once.", ['@expected_update' => $expected_update])); } $this->drupalGet('update.php/selection'); $this->assertText('No pending updates.'); }
/** * Tests hook_post_update_NAME(). */ public function testPostUpdate() { // There are expected to be failed updates. $this->checkFailedUpdates = FALSE; $this->runUpdates(); // There should be no post update hooks registered as being run. $this->assertIdentical([], \Drupal::state()->get('post_update_test_execution', [])); $key_value = \Drupal::keyValue('update__post_update'); $this->assertEqual([], $key_value->get('existing_updates')); }
/** * Disable all blocks with missing context IDs in block_update_8001(). */ function block_post_update_disable_blocks_with_missing_contexts() { // Don't execute the function if block_update_8002() got executed already, // which used to do the same. Note: Its okay to check here, because // update_do_one() does not update the installed schema version until the // batch is finished. $module_schema = drupal_get_installed_schema_version('block'); // The state entry 'block_update_8002_placeholder' is used in order to // indicate that the placeholder block_update_8002() function has been // executed, so this function needs to be executed as well. If the non // placeholder version of block_update_8002() got executed already, the state // won't be set and we skip this update. if ($module_schema >= 8002 && !\Drupal::state()->get('block_update_8002_placeholder', FALSE)) { return; } // Cleanup the state entry as its no longer needed. \Drupal::state()->delete('block_update_8002'); $block_update_8001 = \Drupal::keyValue('update_backup')->get('block_update_8001', []); $block_ids = array_keys($block_update_8001); $block_storage = \Drupal::entityManager()->getStorage('block'); $blocks = $block_storage->loadMultiple($block_ids); /** @var $blocks \Drupal\block\BlockInterface[] */ foreach ($blocks as $block) { // This block has had conditions removed due to an inability to resolve // contexts in block_update_8001() so disable it. // Disable currently enabled blocks. if ($block_update_8001[$block->id()]['status']) { $block->setStatus(FALSE); $block->save(); } } // Provides a list of plugin labels, keyed by plugin ID. $condition_plugin_id_label_map = array_column(\Drupal::service('plugin.manager.condition')->getDefinitions(), 'label', 'id'); // Override with the UI labels we are aware of. Sadly they are not machine // accessible, see // \Drupal\node\Plugin\Condition\NodeType::buildConfigurationForm(). $condition_plugin_id_label_map['node_type'] = t('Content types'); $condition_plugin_id_label_map['request_path'] = t('Pages'); $condition_plugin_id_label_map['user_role'] = t('Roles'); if (count($block_ids) > 0) { $message = t('Encountered an unknown context mapping key coming probably from a contributed or custom module: One or more mappings could not be updated. Please manually review your visibility settings for the following blocks, which are disabled now:'); $message .= '<ul>'; foreach ($blocks as $disabled_block_id => $disabled_block) { $message .= '<li>' . t('@label (Visibility: @plugin_ids)', array('@label' => $disabled_block->get('settings')['label'], '@plugin_ids' => implode(', ', array_intersect_key($condition_plugin_id_label_map, array_flip(array_keys($block_update_8001[$disabled_block_id]['missing_context_ids'])))))) . '</li>'; } $message .= '</ul>'; return $message; } }
/** * Tests that column-level schema changes are detected for fields with data. */ public function testColumnUpdate() { // Change the field type in the stored schema. $schema = \Drupal::keyValue('entity.storage_schema.sql')->get('entity_test_rev.field_schema_data.test'); $schema['entity_test_rev__test']['fields']['test_value']['type'] = 'varchar_ascii'; \Drupal::keyValue('entity.storage_schema.sql')->set('entity_test_rev.field_schema_data.test', $schema); // Now attempt to run automatic updates. An exception should be thrown // since there is data in the table. try { \Drupal::service('entity.definition_update_manager')->applyUpdates(); $this->fail('Failed to detect a schema change in a field with data.'); } catch (FieldStorageDefinitionUpdateForbiddenException $e) { $this->pass('Detected a schema change in a field with data.'); } }
/** * Tests state listing. */ public function testStateListing() { // Ensure that state listing page is accessible only by users with the // adequate permissions. $this->drupalGet('devel/state'); $this->assertResponse(403); $this->drupalLogin($this->develUser); $this->drupalGet('devel/state'); $this->assertResponse(200); $this->assertText(t('State editor')); // Ensure that the state variables table is visible. $table = $this->xpath('//table[contains(@class, "devel-state-list")]'); $this->assertTrue($table, 'State list table found.'); // Ensure that all state variables are listed in the table. $states = \Drupal::keyValue('state')->getAll(); $rows = $this->xpath('//table[contains(@class, "devel-state-list")]//tbody//tr'); $this->assertEqual(count($rows), count($states), 'All states are listed in the table.'); // Ensure that the added state variables are listed in the table. $this->state->set('devel.simple', 'Hello!'); $this->drupalGet('devel/state'); $this->assertFieldByXpath('//table[contains(@class, "devel-state-list")]//tbody//td', 'devel.simple', 'Label found for the added state.'); $thead_xpath = '//table[contains(@class, "devel-state-list")]/thead/tr/th'; $action_xpath = '//table[contains(@class, "devel-state-list")]//ul[@class="dropbutton"]/li/a'; // Ensure that the operations column and the actions buttons are not // available for user without 'administer site configuration' permission. $elements = $this->xpath($thead_xpath); $this->assertEqual(count($elements), 2, 'Correct number of table header cells found.'); $expected_items = ['Name', 'Value']; foreach ($elements as $key => $element) { $this->assertIdentical((string) $element[0], $expected_items[$key]); } $this->assertFalse($this->xpath($action_xpath), 'Action buttons are not visible.'); // Ensure that the operations column and the actions buttons are // available for user with 'administer site configuration' permission. $this->drupalLogin($this->adminUser); $this->drupalGet('devel/state'); $elements = $this->xpath($thead_xpath); $this->assertEqual(count($elements), 3, 'Correct number of table header cells found.'); $expected_items = ['Name', 'Value', 'Operations']; foreach ($elements as $key => $element) { $this->assertIdentical((string) $element[0], $expected_items[$key]); } $this->assertTrue($this->xpath($action_xpath), 'Action buttons are visible.'); // Test that the edit button works properly. $this->clickLink(t('Edit')); $this->assertResponse(200); }
/** * Tests that block context mapping is updated properly. */ public function testUpdateHookN() { $this->runUpdates(); $this->assertRaw('Encountered an unknown context mapping key coming probably from a contributed or custom module: One or more mappings could not be updated. Please manually review your visibility settings for the following blocks, which are disabled now:<ul><li>User login (Visibility: Baloney spam)</li></ul>'); // Disable maintenance mode. \Drupal::state()->set('system.maintenance_mode', FALSE); // We finished updating so we can login the user now. $this->drupalLogin($this->rootUser); // The block that we are testing has the following visibility rules: // - only visible on node pages // - only visible to authenticated users. $block_title = 'Test for 2354889'; // Create two nodes, a page and an article. $page = Node::create(['type' => 'page', 'title' => 'Page node']); $page->save(); $article = Node::create(['type' => 'article', 'title' => 'Article node']); $article->save(); // Check that the block appears only on Page nodes for authenticated users. $this->drupalGet('node/' . $page->id()); $this->assertRaw($block_title, 'Test block is visible on a Page node as an authenticated user.'); $this->drupalGet('node/' . $article->id()); $this->assertNoRaw($block_title, 'Test block is not visible on a Article node as an authenticated user.'); $this->drupalLogout(); // Check that the block does not appear on any page for anonymous users. $this->drupalGet('node/' . $page->id()); $this->assertNoRaw($block_title, 'Test block is not visible on a Page node as an anonymous user.'); $this->drupalGet('node/' . $article->id()); $this->assertNoRaw($block_title, 'Test block is not visible on a Article node as an anonymous user.'); // Ensure that all the context mappings got updated properly. $block = Block::load('testfor2354889'); $visibility = $block->get('visibility'); $this->assertEqual('@node.node_route_context:node', $visibility['node_type']['context_mapping']['node']); $this->assertEqual('@user.current_user_context:current_user', $visibility['user_role']['context_mapping']['user']); $this->assertEqual('@language.current_language_context:language_interface', $visibility['language']['context_mapping']['language']); // Check that a block with invalid context is being disabled and that it can // still be edited afterward. $disabled_block = Block::load('thirdtestfor2354889'); $this->assertFalse($disabled_block->status(), 'Block with invalid context is disabled'); $this->assertEqual(['thirdtestfor2354889' => ['missing_context_ids' => ['baloney.spam' => ['node_type']], 'status' => TRUE]], \Drupal::keyValue('update_backup')->get('block_update_8001')); $disabled_block_visibility = $disabled_block->get('visibility'); $this->assertTrue(!isset($disabled_block_visibility['node_type']), 'The problematic visibility condition has been removed.'); $admin_user = $this->drupalCreateUser(['administer blocks']); $this->drupalLogin($admin_user); $this->drupalGet('admin/structure/block/manage/thirdtestfor2354889'); $this->assertResponse('200'); }
/** * Tests that the database was properly loaded. */ public function testDatabaseLoaded() { $hook_updates = ['user' => '8000', 'node' => '8003', 'system' => '8013']; foreach ($hook_updates as $module => $schema) { $this->assertEqual(drupal_get_installed_schema_version($module), $schema, new FormattableMarkup('Module @module schema is @schema', ['@module' => $module, '@schema' => $schema])); } // Test post_update key value stores contains a list of the update functions // that have run. $existing_updates = array_count_values(\Drupal::keyValue('post_update')->get('existing_updates')); $expected_updates = ['system_post_update_recalculate_configuration_entity_dependencies', 'field_post_update_save_custom_storage_property', 'field_post_update_entity_reference_handler_setting', 'block_post_update_disable_blocks_with_missing_contexts', 'views_post_update_update_cacheability_metadata']; foreach ($expected_updates as $expected_update) { $this->assertEqual($existing_updates[$expected_update], 1, new FormattableMarkup("@expected_update exists in 'existing_updates' key and only appears once.", ['@expected_update' => $expected_update])); } // @todo there are no updates to run. // $this->runUpdates(); $this->assertEqual(\Drupal::config('system.site')->get('name'), 'Site-Install'); $this->drupalGet('<front>'); $this->assertText('Site-Install'); }
/** * Tests hook_post_update_NAME(). */ public function testPostUpdate() { $this->runUpdates(); $this->assertRaw('<h3>Update first</h3>'); $this->assertRaw('First update'); $this->assertRaw('<h3>Update second</h3>'); $this->assertRaw('Second update'); $this->assertRaw('<h3>Update test1</h3>'); $this->assertRaw('Test1 update'); $this->assertRaw('<h3>Update test0</h3>'); $this->assertRaw('Test0 update'); $updates = ['update_test_postupdate_post_update_first', 'update_test_postupdate_post_update_second', 'update_test_postupdate_post_update_test1', 'update_test_postupdate_post_update_test0']; $this->assertIdentical($updates, \Drupal::state()->get('post_update_test_execution', [])); $key_value = \Drupal::keyValue('post_update'); $updates = array_merge(['block_post_update_disable_blocks_with_missing_contexts', 'field_post_update_save_custom_storage_property', 'field_post_update_entity_reference_handler_setting', 'system_post_update_recalculate_configuration_entity_dependencies', 'views_post_update_update_cacheability_metadata'], $updates); $this->assertEqual($updates, $key_value->get('existing_updates')); $this->drupalGet('update.php/selection'); $this->assertText('No pending updates.'); }
/** * Tests hook_post_update_NAME(). */ public function testPostUpdate() { $this->runUpdates(); $this->assertRaw('<h3>Update first</h3>'); $this->assertRaw('First update'); $this->assertRaw('<h3>Update second</h3>'); $this->assertRaw('Second update'); $this->assertRaw('<h3>Update test1</h3>'); $this->assertRaw('Test1 update'); $this->assertRaw('<h3>Update test0</h3>'); $this->assertRaw('Test0 update'); $updates = ['update_test_postupdate_post_update_first', 'update_test_postupdate_post_update_second', 'update_test_postupdate_post_update_test1', 'update_test_postupdate_post_update_test0']; $this->assertIdentical($updates, \Drupal::state()->get('post_update_test_execution', [])); $key_value = \Drupal::keyValue('post_update'); array_unshift($updates, 'block_post_update_disable_blocks_with_missing_contexts'); $this->assertEqual($updates, $key_value->get('existing_updates')); $this->drupalGet('update.php/selection'); $this->assertText('No pending updates.'); }
/** * Tests that the database was properly loaded. */ public function testDatabaseLoaded() { $extensions = \Drupal::service('config.storage')->read('core.extension'); $this->assertFalse(isset($extensions['theme']['stable']), 'Stable is not installed before updating.'); $hook_updates = ['user' => '8000', 'node' => '8003', 'system' => '8013']; foreach ($hook_updates as $module => $schema) { $this->assertEqual(drupal_get_installed_schema_version($module), $schema, new FormattableMarkup('Module @module schema is @schema', ['@module' => $module, '@schema' => $schema])); } // Test post_update key value stores contains a list of the update functions // that have run. $existing_updates = array_count_values(\Drupal::keyValue('post_update')->get('existing_updates')); $expected_updates = ['system_post_update_recalculate_configuration_entity_dependencies', 'field_post_update_save_custom_storage_property', 'field_post_update_entity_reference_handler_setting', 'block_post_update_disable_blocks_with_missing_contexts', 'views_post_update_update_cacheability_metadata']; foreach ($expected_updates as $expected_update) { $this->assertEqual($existing_updates[$expected_update], 1, new FormattableMarkup("@expected_update exists in 'existing_updates' key and only appears once.", ['@expected_update' => $expected_update])); } $this->runUpdates(); $this->assertEqual(\Drupal::config('system.site')->get('name'), 'Site-Install'); $this->drupalGet('<front>'); $this->assertText('Site-Install'); $extensions = \Drupal::service('config.storage')->read('core.extension'); $this->assertTrue(isset($extensions['theme']['stable']), 'Stable is installed after updating.'); $blocks = \Drupal::entityManager()->getStorage('block')->loadByProperties(['theme' => 'stable']); $this->assertTrue(empty($blocks), 'No blocks have been placed for Stable.'); }
/** * Builds a row for an entity in the entity listing. * * @param \Drupal\migrate\Entity\EntityInterface $migration * The entity for which to build the row. * * @return array * A render array of the table row for displaying the entity. * * @see Drupal\Core\Entity\EntityListController::render() */ public function buildRow(EntityInterface $migration) { $row['label'] = $migration->label(); $row['machine_name'] = $migration->id(); $row['status'] = $migration->getStatusLabel(); // Derive the stats $source_plugin = $migration->getSourcePlugin(); $row['total'] = $source_plugin->count(); $map = $migration->getIdMap(); $row['imported'] = $map->importedCount(); // -1 indicates uncountable sources. if ($row['total'] == -1) { $row['total'] = $this->t('N/A'); $row['unprocessed'] = $this->t('N/A'); } else { $row['unprocessed'] = $row['total'] - $map->processedCount(); } $migration_group = $migration->getThirdPartySetting('migrate_plus', 'migration_group'); if (!$migration_group) { $migration_group = 'default'; } $route_parameters = array('migration_group' => $migration_group, 'migration' => $migration->id()); $row['messages'] = array('data' => array('#type' => 'link', '#title' => $map->messageCount(), '#url' => Url::fromRoute("migrate_tools.messages", $route_parameters))); $migrate_last_imported_store = \Drupal::keyValue('migrate_last_imported'); $last_imported = $migrate_last_imported_store->get($migration->id(), FALSE); if ($last_imported) { /** @var DateFormatter $date_formatter */ $date_formatter = \Drupal::service('date.formatter'); $row['last_imported'] = $date_formatter->format($last_imported / 1000, 'custom', 'Y-m-d H:i:s'); } else { $row['last_imported'] = ''; } return $row; // + parent::buildRow($migration); }
/** * {@inheritdoc} */ public function interruptMigration($result) { $this->setStatus(MigrationInterface::STATUS_STOPPING); \Drupal::keyValue('migrate_interruption_result')->set($this->id(), $result); }
/** * Retrieves the database information for the test index. * * @return array * The database information stored by the backend for the test index. */ protected function getIndexDbInfo() { return \Drupal::keyValue(BackendDatabase::INDEXES_KEY_VALUE_STORE_ID)->get($this->indexId); }
/** * Adds entity autocomplete functionality to a form element. * * @param array $element * The form element to process. Properties used: * - #target_type: The ID of the target entity type. * - #selection_handler: The plugin ID of the entity reference selection * handler. * - #selection_settings: An array of settings that will be passed to the * selection handler. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. * @param array $complete_form * The complete form structure. * * @return array * The form element. * * @throws \InvalidArgumentException * Exception thrown when the #target_type or #autocreate['bundle'] are * missing. */ public static function processEntityAutocomplete(array &$element, FormStateInterface $form_state, array &$complete_form) { // Nothing to do if there is no target entity type. if (empty($element['#target_type'])) { throw new \InvalidArgumentException('Missing required #target_type parameter.'); } // Provide default values and sanity checks for the #autocreate parameter. if ($element['#autocreate']) { if (!isset($element['#autocreate']['bundle'])) { throw new \InvalidArgumentException("Missing required #autocreate['bundle'] parameter."); } // Default the autocreate user ID to the current user. $element['#autocreate']['uid'] = isset($element['#autocreate']['uid']) ? $element['#autocreate']['uid'] : \Drupal::currentUser()->id(); } // Store the selection settings in the key/value store and pass a hashed key // in the route parameters. $selection_settings = isset($element['#selection_settings']) ? $element['#selection_settings'] : []; $data = serialize($selection_settings) . $element['#target_type'] . $element['#selection_handler']; $selection_settings_key = Crypt::hmacBase64($data, Settings::getHashSalt()); $key_value_storage = \Drupal::keyValue('entity_autocomplete'); if (!$key_value_storage->has($selection_settings_key)) { $key_value_storage->set($selection_settings_key, $selection_settings); } $element['#autocomplete_route_name'] = 'system.entity_autocomplete'; $element['#autocomplete_route_parameters'] = array('target_type' => $element['#target_type'], 'selection_handler' => $element['#selection_handler'], 'selection_settings_key' => $selection_settings_key); return $element; }
/** * Deletes the stored state. */ public function purge() { \Drupal::keyValue($this->getCollection())->delete($this->parent->getEntity()->id()); }
/** * {@inheritdoc} */ public function uninstall(array $module_list, $uninstall_dependents = TRUE) { // Get all module data so we can find dependencies and sort. $module_data = system_rebuild_module_data(); $module_list = $module_list ? array_combine($module_list, $module_list) : array(); if (array_diff_key($module_list, $module_data)) { // One or more of the given modules doesn't exist. return FALSE; } // Only process currently installed modules. $extension_config = \Drupal::config('core.extension'); $installed_modules = $extension_config->get('module') ?: array(); if (!($module_list = array_intersect_key($module_list, $installed_modules))) { // Nothing to do. All modules already uninstalled. return TRUE; } if ($uninstall_dependents) { // Add dependent modules to the list. The new modules will be processed as // the while loop continues. $profile = drupal_get_profile(); while (list($module) = each($module_list)) { foreach (array_keys($module_data[$module]->required_by) as $dependent) { if (!isset($module_data[$dependent])) { // The dependent module does not exist. return FALSE; } // Skip already uninstalled modules. if (isset($installed_modules[$dependent]) && !isset($module_list[$dependent]) && $dependent != $profile) { $module_list[$dependent] = $dependent; } } } } // Set the actual module weights. $module_list = array_map(function ($module) use($module_data) { return $module_data[$module]->sort; }, $module_list); // Sort the module list by their weights. asort($module_list); $module_list = array_keys($module_list); // Only process modules that are enabled. A module is only enabled if it is // configured as enabled. Custom or overridden module handlers might contain // the module already, which means that it might be loaded, but not // necessarily installed. $schema_store = \Drupal::keyValue('system.schema'); $entity_manager = \Drupal::entityManager(); foreach ($module_list as $module) { // Clean up all entity bundles (including fields) of every entity type // provided by the module that is being uninstalled. foreach ($entity_manager->getDefinitions() as $entity_type_id => $entity_type) { if ($entity_type->getProvider() == $module) { foreach (array_keys($entity_manager->getBundleInfo($entity_type_id)) as $bundle) { $entity_manager->onBundleDelete($bundle, $entity_type_id); } } } // Allow modules to react prior to the uninstallation of a module. $this->invokeAll('module_preuninstall', array($module)); // Uninstall the module. module_load_install($module); $this->invoke($module, 'uninstall'); // Remove all configuration belonging to the module. \Drupal::service('config.manager')->uninstall('module', $module); // Notify the entity manager that this module's entity types are being // deleted, so that it can notify all interested handlers. For example, // a SQL-based storage handler can use this as an opportunity to drop // the corresponding database tables. foreach ($entity_manager->getDefinitions() as $entity_type) { if ($entity_type->getProvider() == $module) { $entity_manager->onEntityTypeDelete($entity_type); } } // Remove the schema. drupal_uninstall_schema($module); // Remove the module's entry from the config. $extension_config->clear("module.{$module}")->save(); // Update the module handler to remove the module. // The current ModuleHandler instance is obsolete with the kernel rebuild // below. $module_filenames = $this->getModuleList(); unset($module_filenames[$module]); $this->setModuleList($module_filenames); // Remove any potential cache bins provided by the module. $this->removeCacheBins($module); // Clear the static cache of system_rebuild_module_data() to pick up the // new module, since it merges the installation status of modules into // its statically cached list. drupal_static_reset('system_rebuild_module_data'); // Clear plugin manager caches and flag router to rebuild if requested. \Drupal::getContainer()->get('plugin.cache_clearer')->clearCachedDefinitions(); \Drupal::service('router.builder_indicator')->setRebuildNeeded(); // Update the kernel to exclude the uninstalled modules. \Drupal::service('kernel')->updateModules($module_filenames, $module_filenames); // Update the theme registry to remove the newly uninstalled module. drupal_theme_rebuild(); // Modules can alter theme info, so refresh theme data. // @todo ThemeHandler cannot be injected into ModuleHandler, since that // causes a circular service dependency. // @see https://drupal.org/node/2208429 \Drupal::service('theme_handler')->refreshInfo(); \Drupal::logger('system')->info('%module module uninstalled.', array('%module' => $module)); $schema_store->delete($module); } drupal_get_installed_schema_version(NULL, TRUE); // Let other modules react. $this->invokeAll('modules_uninstalled', array($module_list)); return TRUE; }
/** * Executes an update which is intended to update data, like entities. * * These implementations have to be placed in a MODULE.post_update.php file. * * These updates are executed after all hook_update_N() implementations. At this * stage Drupal is already fully repaired so you can use any API as you wish. * * NAME can be arbitrary machine names. In contrast to hook_update_N() the order * of functions in the file is the only thing which ensures the execution order * of those functions. * * Drupal also ensures to not execute the same hook_post_update_NAME() function * twice. * * @param array $sandbox * Stores information for batch updates. See above for more information. * * @throws \Drupal\Core\Utility\UpdateException|PDOException * In case of error, update hooks should throw an instance of * \Drupal\Core\Utility\UpdateException with a meaningful message for the * user. If a database query fails for whatever reason, it will throw a * PDOException. * * @return string|null * Optionally, hook_post_update_NAME() hooks may return a translated string * that will be displayed to the user after the update has completed. If no * message is returned, no message will be presented to the user. * * @ingroup update_api * * @see hook_update_N() */ function hook_post_update_NAME(&$sandbox) { // Example of updating some content. $node = \Drupal\node\Entity\Node::load(123); $node->setTitle('foo'); $node->save(); $result = t('Node %nid saved', ['%nid' => $node->id()]); // Example of disabling blocks with missing condition contexts. Note: The // block itself is in a state which is valid at that point. // @see block_update_8001() // @see block_post_update_disable_blocks_with_missing_contexts() $block_update_8001 = \Drupal::keyValue('update_backup')->get('block_update_8001', []); $block_ids = array_keys($block_update_8001); $block_storage = \Drupal::entityManager()->getStorage('block'); $blocks = $block_storage->loadMultiple($block_ids); /** @var $blocks \Drupal\block\BlockInterface[] */ foreach ($blocks as $block) { // This block has had conditions removed due to an inability to resolve // contexts in block_update_8001() so disable it. // Disable currently enabled blocks. if ($block_update_8001[$block->id()]['status']) { $block->setStatus(FALSE); $block->save(); } } return $result; }
/** * Tests the keyValue() method. * * @covers ::keyValue */ public function testKeyValue() { $keyvalue = $this->getMockBuilder('Drupal\\Core\\KeyValueStore\\KeyValueFactory')->disableOriginalConstructor()->getMock(); $keyvalue->expects($this->once())->method('get')->with('test_collection')->will($this->returnValue(TRUE)); $this->setMockContainerService('keyvalue', $keyvalue); $this->assertNotNull(\Drupal::keyValue('test_collection')); }
/** * Retrieves the key-value store to use. * * @return \Drupal\Core\KeyValueStore\KeyValueStoreInterface * The key-value store. */ public function getKeyValueStore() { return $this->keyValueStore ? : \Drupal::keyValue(self::INDEXES_KEY_VALUE_STORE_ID); }
/** * {@inheritdoc} */ public function getMigrationResult() { $migrate_result_store = \Drupal::keyValue('migrate_result'); return $migrate_result_store->get($this->id(), static::RESULT_INCOMPLETE); }
/** * Get the highwater storage object. * * @return \Drupal\Core\KeyValueStore\KeyValueStoreInterface * The storage object. */ protected function getHighWaterStorage() { if (!isset($this->highwaterStorage)) { $this->highwaterStorage = \Drupal::keyValue('migrate:highwater'); } return $this->highwaterStorage; }
/** * Gets the keyvalue collection for tracking the installed schema. * * @return \Drupal\Core\KeyValueStore\KeyValueStoreInterface * * @todo Inject this dependency in the constructor once this class can be * instantiated as a regular entity handler: * https://www.drupal.org/node/2332857. */ protected function installedStorageSchema() { if (!isset($this->installedStorageSchema)) { $this->installedStorageSchema = \Drupal::keyValue('entity.storage_schema.sql'); } return $this->installedStorageSchema; }
/** * Tests whether removing the configuration again works as it should. */ protected function checkModuleUninstall() { $db_info = \Drupal::keyValue(BackendDatabase::INDEXES_KEY_VALUE_STORE_ID)->get($this->indexId); $normalized_storage_table = $db_info['index_table']; $field_tables = $db_info['field_tables']; // See whether clearing the server works. // Regression test for #2156151. $server = $this->getServer(); $index = $this->getIndex(); $server->deleteAllIndexItems($index); $query = $this->buildSearch(); $results = $query->execute(); $this->assertEquals(0, $results->getResultCount(), 'Clearing the server worked correctly.'); $this->assertTrue(Database::getConnection()->schema()->tableExists($normalized_storage_table), 'The index tables were left in place.'); // Remove first the index and then the server. $index->setServer(); $index->save(); $db_info = \Drupal::keyValue(BackendDatabase::INDEXES_KEY_VALUE_STORE_ID)->get($this->indexId); $this->assertNull($db_info, 'The index was successfully removed from the server.'); $this->assertFalse(Database::getConnection()->schema()->tableExists($normalized_storage_table), 'The index tables were deleted.'); foreach ($field_tables as $field_table) { $this->assertFalse(\Drupal::database()->schema()->tableExists($field_table['table']), new FormattableMarkup('Field table %table exists', array('%table' => $field_table['table']))); } // Re-add the index to see if the associated tables are also properly // removed when the server is deleted. $index->setServer($server); $index->save(); $server->delete(); $db_info = \Drupal::keyValue(BackendDatabase::INDEXES_KEY_VALUE_STORE_ID)->get($this->indexId); $this->assertNull($db_info, 'The index was successfully removed from the server.'); $this->assertFalse(Database::getConnection()->schema()->tableExists($normalized_storage_table), 'The index tables were deleted.'); foreach ($field_tables as $field_table) { $this->assertFalse(\Drupal::database()->schema()->tableExists($field_table['table']), new FormattableMarkup('Field table %table exists', array('%table' => $field_table['table']))); } // Uninstall the module. \Drupal::service('module_installer')->uninstall(array('search_api_db'), FALSE); $this->assertFalse(\Drupal::moduleHandler()->moduleExists('search_api_db'), 'The Database Search module was successfully uninstalled.'); $tables = \Drupal::database()->schema()->findTables('search_api_db_%'); $this->assertEquals(array(), $tables, 'All the tables of the the Database Search module have been removed.'); }
/** * Tests fields from an uninstalled module are removed from the schema. */ public function testCleanUpStorageDefinition() { // Find all the entity types provided by the entity_test module and install // the schema for them. $entity_type_ids = []; $entities = \Drupal::entityManager()->getDefinitions(); foreach ($entities as $entity_type_id => $definition) { if ($definition->getProvider() == 'entity_test') { $this->installEntitySchema($entity_type_id); $entity_type_ids[] = $entity_type_id; } } // Get a list of all the entities in the schema. $key_value_store = \Drupal::keyValue('entity.storage_schema.sql'); $schema = $key_value_store->getAll(); // Count the storage definitions provided by the entity_test module, so that // after uninstall we can be sure there were some to be deleted. $entity_type_id_count = 0; foreach (array_keys($schema) as $storage_definition_name) { list($entity_type_id, , ) = explode('.', $storage_definition_name); if (in_array($entity_type_id, $entity_type_ids)) { $entity_type_id_count++; } } // Ensure that there are storage definitions from the entity_test module. $this->assertNotEqual($entity_type_id_count, 0, 'There are storage definitions provided by the entity_test module in the schema.'); // Uninstall the entity_test module. $this->container->get('module_installer')->uninstall(array('entity_test')); // Get a list of all the entities in the schema. $key_value_store = \Drupal::keyValue('entity.storage_schema.sql'); $schema = $key_value_store->getAll(); // Count the storage definitions that come from entity types provided by // the entity_test module. $entity_type_id_count = 0; foreach (array_keys($schema) as $storage_definition_name) { list($entity_type_id, , ) = explode('.', $storage_definition_name); if (in_array($entity_type_id, $entity_type_ids)) { $entity_type_id_count++; } } // Ensure that all storage definitions have been removed from the schema. $this->assertEqual($entity_type_id_count, 0, 'After uninstalling entity_test module the schema should not contains fields from entities provided by the module.'); }
/** * {@inheritdoc} */ public function uninstall(array $module_list, $uninstall_dependents = TRUE) { // Get all module data so we can find dependencies and sort. $module_data = system_rebuild_module_data(); $module_list = $module_list ? array_combine($module_list, $module_list) : array(); if (array_diff_key($module_list, $module_data)) { // One or more of the given modules doesn't exist. return FALSE; } $extension_config = \Drupal::configFactory()->getEditable('core.extension'); $installed_modules = $extension_config->get('module') ?: array(); if (!($module_list = array_intersect_key($module_list, $installed_modules))) { // Nothing to do. All modules already uninstalled. return TRUE; } if ($uninstall_dependents) { // Add dependent modules to the list. The new modules will be processed as // the while loop continues. $profile = drupal_get_profile(); while (list($module) = each($module_list)) { foreach (array_keys($module_data[$module]->required_by) as $dependent) { if (!isset($module_data[$dependent])) { // The dependent module does not exist. return FALSE; } // Skip already uninstalled modules. if (isset($installed_modules[$dependent]) && !isset($module_list[$dependent]) && $dependent != $profile) { $module_list[$dependent] = $dependent; } } } } // Use the validators and throw an exception with the reasons. if ($reasons = $this->validateUninstall($module_list)) { foreach ($reasons as $reason) { $reason_message[] = implode(', ', $reason); } throw new ModuleUninstallValidatorException('The following reasons prevent the modules from being uninstalled: ' . implode('; ', $reason_message)); } // Set the actual module weights. $module_list = array_map(function ($module) use($module_data) { return $module_data[$module]->sort; }, $module_list); // Sort the module list by their weights. asort($module_list); $module_list = array_keys($module_list); // Only process modules that are enabled. A module is only enabled if it is // configured as enabled. Custom or overridden module handlers might contain // the module already, which means that it might be loaded, but not // necessarily installed. foreach ($module_list as $module) { // Clean up all entity bundles (including fields) of every entity type // provided by the module that is being uninstalled. // @todo Clean this up in https://www.drupal.org/node/2350111. $entity_manager = \Drupal::entityManager(); foreach ($entity_manager->getDefinitions() as $entity_type_id => $entity_type) { if ($entity_type->getProvider() == $module) { foreach (array_keys($entity_manager->getBundleInfo($entity_type_id)) as $bundle) { $entity_manager->onBundleDelete($bundle, $entity_type_id); } } } // Allow modules to react prior to the uninstallation of a module. $this->moduleHandler->invokeAll('module_preuninstall', array($module)); // Uninstall the module. module_load_install($module); $this->moduleHandler->invoke($module, 'uninstall'); // Remove all configuration belonging to the module. \Drupal::service('config.manager')->uninstall('module', $module); // In order to make uninstalling transactional if anything uses routes. \Drupal::getContainer()->set('router.route_provider.old', \Drupal::service('router.route_provider')); \Drupal::getContainer()->set('router.route_provider', \Drupal::service('router.route_provider.lazy_builder')); // Notify interested components that this module's entity types are being // deleted. For example, a SQL-based storage handler can use this as an // opportunity to drop the corresponding database tables. // @todo Clean this up in https://www.drupal.org/node/2350111. $update_manager = \Drupal::entityDefinitionUpdateManager(); foreach ($entity_manager->getDefinitions() as $entity_type) { if ($entity_type->getProvider() == $module) { $update_manager->uninstallEntityType($entity_type); } elseif ($entity_type->isSubclassOf(FieldableEntityInterface::CLASS)) { // The module being installed may be adding new fields to existing // entity types. Field definitions for any entity type defined by // the module are handled in the if branch. $entity_type_id = $entity_type->id(); /** @var \Drupal\Core\Entity\FieldableEntityStorageInterface $storage */ $storage = $entity_manager->getStorage($entity_type_id); foreach ($entity_manager->getFieldStorageDefinitions($entity_type_id) as $storage_definition) { // @todo We need to trigger field purging here. // See https://www.drupal.org/node/2282119. if ($storage_definition->getProvider() == $module && !$storage->countFieldData($storage_definition, TRUE)) { $update_manager->uninstallFieldStorageDefinition($storage_definition); } } } } // Remove the schema. drupal_uninstall_schema($module); // Remove the module's entry from the config. Don't check schema when // uninstalling a module since we are only clearing a key. \Drupal::configFactory()->getEditable('core.extension')->clear("module.{$module}")->save(TRUE); // Update the module handler to remove the module. // The current ModuleHandler instance is obsolete with the kernel rebuild // below. $module_filenames = $this->moduleHandler->getModuleList(); unset($module_filenames[$module]); $this->moduleHandler->setModuleList($module_filenames); // Remove any potential cache bins provided by the module. $this->removeCacheBins($module); // Clear the static cache of system_rebuild_module_data() to pick up the // new module, since it merges the installation status of modules into // its statically cached list. drupal_static_reset('system_rebuild_module_data'); // Clear plugin manager caches. \Drupal::getContainer()->get('plugin.cache_clearer')->clearCachedDefinitions(); // Update the kernel to exclude the uninstalled modules. $this->updateKernel($module_filenames); // Update the theme registry to remove the newly uninstalled module. drupal_theme_rebuild(); // Modules can alter theme info, so refresh theme data. // @todo ThemeHandler cannot be injected into ModuleHandler, since that // causes a circular service dependency. // @see https://www.drupal.org/node/2208429 \Drupal::service('theme_handler')->refreshInfo(); \Drupal::logger('system')->info('%module module uninstalled.', array('%module' => $module)); $schema_store = \Drupal::keyValue('system.schema'); $schema_store->delete($module); /** @var \Drupal\Core\Update\UpdateRegistry $post_update_registry */ $post_update_registry = \Drupal::service('update.post_update_registry'); $post_update_registry->filterOutInvokedUpdatesByModule($module); } // Rebuild routes after installing module. This is done here on top of // \Drupal\Core\Routing\RouteBuilder::destruct to not run into errors on // fastCGI which executes ::destruct() after the Module uninstallation page // was sent already. \Drupal::service('router.builder')->rebuild(); drupal_get_installed_schema_version(NULL, TRUE); // Let other modules react. $this->moduleHandler->invokeAll('modules_uninstalled', array($module_list)); // Flush all persistent caches. // Any cache entry might implicitly depend on the uninstalled modules, // so clear all of them explicitly. $this->moduleHandler->invokeAll('cache_flush'); foreach (Cache::getBins() as $service_id => $cache_backend) { $cache_backend->deleteAll(); } return TRUE; }
/** * Returns the result of an Entity reference autocomplete request. * * @param string $input * The label of the entity to query by. * * @return mixed * The JSON value encoded in its appropriate PHP type. */ protected function getAutocompleteResult($input) { $request = Request::create('entity_reference_autocomplete/' . $this->entityType . '/default'); $request->query->set('q', $input); $selection_settings = []; $selection_settings_key = Crypt::hmacBase64(serialize($selection_settings) . $this->entityType . 'default', Settings::getHashSalt()); \Drupal::keyValue('entity_autocomplete')->set($selection_settings_key, $selection_settings); $entity_reference_controller = EntityAutocompleteController::create($this->container); $result = $entity_reference_controller->handleAutocomplete($request, $this->entityType, 'default', $selection_settings_key)->getContent(); return Json::decode($result); }
/** * Builds a row for an entity in the entity listing. * * @param EntityInterface $migration * The entity for which to build the row. * * @return array * A render array of the table row for displaying the entity. * * @see Drupal\Core\Entity\EntityListController::render() */ public function buildRow(MigrationInterface $migration) { $row['label'] = $migration->label(); $row['machine_name'] = $migration->id(); $row['status'] = $migration->getStatusLabel(); // Derive the stats $source_plugin = $migration->getSourcePlugin(); $row['total'] = $source_plugin->count(); $map = $migration->getIdMap(); $row['imported'] = $map->importedCount(); // -1 indicates uncountable sources. if ($row['total'] == -1) { $row['total'] = $this->t('N/A'); $row['unprocessed'] = $this->t('N/A'); } else { $row['unprocessed'] = $row['total'] - $map->processedCount(); } $group = $migration->get('migration_group'); if (!$group) { $group = 'default'; } // @todo: This is most likely not a Best Practice (tm). $row['messages']['data']['#markup'] = '<a href="/admin/structure/migrate/manage/' . $group . '/migrations/' . $migration->id() . '/messages">' . $map->messageCount() . '</a>'; $migrate_last_imported_store = \Drupal::keyValue('migrate_last_imported'); $last_imported = $migrate_last_imported_store->get($migration->id(), FALSE); if ($last_imported) { /** @var DateFormatter $date_formatter */ $date_formatter = \Drupal::service('date.formatter'); $row['last_imported'] = $date_formatter->format($last_imported / 1000, 'custom', 'Y-m-d H:i:s'); } else { $row['last_imported'] = ''; } return $row; // + parent::buildRow($migration); }
/** * Asserts the module post update functions after uninstall. * * @param string $module * The module that got installed. */ protected function assertUninstallModuleUpdates($module) { /** @var \Drupal\Core\Update\UpdateRegistry $post_update_registry */ $post_update_registry = \Drupal::service('update.post_update_registry'); $all_update_functions = $post_update_registry->getPendingUpdateFunctions(); switch ($module) { case 'block': $this->assertFalse(array_intersect(['block_post_update_disable_blocks_with_missing_contexts'], $all_update_functions), 'Asserts that no pending post update functions are available.'); $existing_updates = \Drupal::keyValue('post_update')->get('existing_updates', []); $this->assertFalse(array_intersect(['block_post_update_disable_blocks_with_missing_contexts'], $existing_updates), 'Asserts that no post update functions are stored in keyvalue store.'); break; } }
/** * @todo Merge this with existing node test methods? */ public function testNodeState() { $nodeNoAliasUser = $this->drupalCreateUser(array('bypass node access')); $nodeAliasUser = $this->drupalCreateUser(array('bypass node access', 'create url aliases')); $node = $this->drupalCreateNode(array('title' => 'Node version one', 'type' => 'page', 'path' => array('pathauto' => PathautoState::SKIP))); $this->assertNoEntityAlias($node); // Set a manual path alias for the node. $node->path->alias = '/test-alias'; $node->save(); // Ensure that the pathauto field was saved to the database. \Drupal::entityManager()->getStorage('node')->resetCache(); $node = Node::load($node->id()); $this->assertIdentical($node->path->pathauto, PathautoState::SKIP); // Ensure that the manual path alias was saved and an automatic alias was not generated. $this->assertEntityAlias($node, '/test-alias'); $this->assertNoEntityAliasExists($node, '/content/node-version-one'); // Save the node as a user who does not have access to path fieldset. $this->drupalLogin($nodeNoAliasUser); $this->drupalGet('node/' . $node->id() . '/edit'); $this->assertNoFieldByName('path[0][pathauto]'); $edit = array('title[0][value]' => 'Node version two'); $this->drupalPostForm(NULL, $edit, 'Save'); $this->assertText('Basic page Node version two has been updated.'); $this->assertEntityAlias($node, '/test-alias'); $this->assertNoEntityAliasExists($node, '/content/node-version-one'); $this->assertNoEntityAliasExists($node, '/content/node-version-two'); // Load the edit node page and check that the Pathauto checkbox is unchecked. $this->drupalLogin($nodeAliasUser); $this->drupalGet('node/' . $node->id() . '/edit'); $this->assertNoFieldChecked('edit-path-0-pathauto'); // Edit the manual alias and save the node. $edit = array('title[0][value]' => 'Node version three', 'path[0][alias]' => '/manually-edited-alias'); $this->drupalPostForm(NULL, $edit, 'Save'); $this->assertText('Basic page Node version three has been updated.'); $this->assertEntityAlias($node, '/manually-edited-alias'); $this->assertNoEntityAliasExists($node, '/test-alias'); $this->assertNoEntityAliasExists($node, '/content/node-version-one'); $this->assertNoEntityAliasExists($node, '/content/node-version-two'); $this->assertNoEntityAliasExists($node, '/content/node-version-three'); // Programatically save the node with an automatic alias. \Drupal::entityManager()->getStorage('node')->resetCache(); $node = Node::load($node->id()); $node->path->pathauto = PathautoState::CREATE; $node->save(); // Ensure that the pathauto field was saved to the database. \Drupal::entityManager()->getStorage('node')->resetCache(); $node = Node::load($node->id()); $this->assertIdentical($node->path->pathauto, PathautoState::CREATE); $this->assertEntityAlias($node, '/content/node-version-three'); $this->assertNoEntityAliasExists($node, '/manually-edited-alias'); $this->assertNoEntityAliasExists($node, '/test-alias'); $this->assertNoEntityAliasExists($node, '/content/node-version-one'); $this->assertNoEntityAliasExists($node, '/content/node-version-two'); $node->delete(); $this->assertNull(\Drupal::keyValue('pathauto_state.node')->get($node->id()), 'Pathauto state was deleted'); }