Commit 86563123 authored by catch's avatar catch

Issue #1002164 by alexpott, Carsten Müller, Devin Carlson, linclark, naxoc,...

Issue #1002164 by alexpott, Carsten Müller, Devin Carlson, linclark, naxoc, vijaycs85: The Book module can be uninstalled with nodes with a book node type still existing
parent 842e9942
......@@ -64,6 +64,7 @@ protected function setUp() {
$this->installEntitySchema('block_content');
$this->installEntitySchema('taxonomy_term');
$this->installEntitySchema('node');
$this->installSchema('book', array('book'));
}
/**
......
......@@ -19,6 +19,7 @@
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Extension\Extension;
/**
* Implements hook_help().
......@@ -572,3 +573,33 @@ function book_node_type_update(NodeTypeInterface $type) {
$config->save();
}
}
/**
* Implements hook_system_info_alter().
*
* Prevents book module from being uninstalled whilst any book nodes exist or
* there are any book outline stored.
*/
function book_system_info_alter(&$info, Extension $file, $type) {
// It is not safe use the entity query service during maintenance mode.
if ($type == 'module' && !defined('MAINTENANCE_MODE') && $file->getName() == 'book') {
if (\Drupal::service('book.outline_storage')->hasBooks()) {
$info['required'] = TRUE;
$info['explanation'] = t('To uninstall Book, delete all content that is part of a book.');
}
else {
// The book node type is provided by the Book module. Prevent uninstall if
// there are any nodes of that type.
$factory = \Drupal::service('entity.query');
$nodes = $factory->get('node')
->condition('type', 'book')
->accessCheck(FALSE)
->range(0, 1)
->execute();
if (!empty($nodes)) {
$info['required'] = TRUE;
$info['explanation'] = t('To uninstall Book, delete all content that has the Book content type.');
}
}
}
}
langcode: en
status: true
dependencies: { }
dependencies:
enforced:
module:
- book
name: 'Book page'
type: book
description: '<em>Books</em> have a built-in hierarchical navigation. Use for handbooks or tutorials.'
help: ''
new_revision: false
display_submitted: true
preview_mode: 1
display_submitted: true
third_party_settings: { }
......@@ -35,6 +35,15 @@ public function getBooks() {
return $this->connection->query("SELECT DISTINCT(bid) FROM {book}")->fetchCol();
}
/**
* {@inheritdoc}
*/
public function hasBooks() {
return (bool) $this->connection
->query('SELECT count(bid) FROM {book}')
->fetchField();
}
/**
* {@inheritdoc}
*/
......
......@@ -20,6 +20,14 @@ interface BookOutlineStorageInterface {
*/
public function getBooks();
/**
* Checks if there are any books.
*
* @return bool
* TRUE if there are books, FALSE if not.
*/
public function hasBooks();
/**
* Loads books.
*
......
<?php
/**
* @file
* Contains Drupal\book\Tests\BookUninstallTest.
*/
namespace Drupal\book\Tests;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\simpletest\KernelTestBase;
/**
* Tests that the Book module cannot be uninstalled if books exist.
*
* @group book
*/
class BookUninstallTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('system', 'user', 'field', 'filter', 'text', 'entity_reference', 'node', 'book');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('user');
$this->installEntitySchema('node');
$this->installSchema('book', array('book'));
$this->installSchema('node', array('node_access'));
$this->installConfig(array('node', 'book', 'field'));
// For uninstall to work.
$this->installSchema('user', array('users_data'));
}
/**
* Tests the book_system_info_alter() method.
*/
public function testBookUninstall() {
// No nodes exist.
$module_data = _system_rebuild_module_data();
$this->assertFalse(isset($module_data['book']->info['required']), 'The book module is not required.');
$content_type = NodeType::create(array(
'type' => $this->randomMachineName(),
'name' => $this->randomString(),
));
$content_type->save();
$book_config = \Drupal::config('book.settings');
$allowed_types = $book_config->get('allowed_types');
$allowed_types[] = $content_type->id();
$book_config->set('allowed_types', $allowed_types)->save();
$node = Node::create(array('type' => $content_type->id()));
$node->book['bid'] = 'new';
$node->save();
// One node in a book but not of type book.
$module_data = _system_rebuild_module_data();
$this->assertTrue($module_data['book']->info['required'], 'The book module is required.');
$this->assertEqual($module_data['book']->info['explanation'], t('To uninstall Book, delete all content that is part of a book.'));
$book_node = Node::create(array('type' => 'book'));
$book_node->book['bid'] = FALSE;
$book_node->save();
// Two nodes, one in a book but not of type book and one book node (which is
// not in a book).
$module_data = _system_rebuild_module_data();
$this->assertTrue($module_data['book']->info['required'], 'The book module is required.');
$this->assertEqual($module_data['book']->info['explanation'], t('To uninstall Book, delete all content that is part of a book.'));
$node->delete();
// One node of type book but not actually part of a book.
$module_data = _system_rebuild_module_data();
$this->assertTrue($module_data['book']->info['required'], 'The book module is required.');
$this->assertEqual($module_data['book']->info['explanation'], t('To uninstall Book, delete all content that has the Book content type.'));
$book_node->delete();
// No nodes exist therefore the book module is not required.
$module_data = _system_rebuild_module_data();
$this->assertFalse(isset($module_data['book']->info['required']), 'The book module is not required.');
$node = Node::create(array('type' => $content_type->id()));
$node->save();
// One node exists but is not part of a book therefore the book module is
// not required.
$module_data = _system_rebuild_module_data();
$this->assertFalse(isset($module_data['book']->info['required']), 'The book module is not required.');
// Uninstall the Book module and check the node type is deleted.
\Drupal::service('module_installer')->uninstall(array('book'));
$this->assertNull(NodeType::load('book'), "The book node type does not exist.");
}
}
......@@ -110,6 +110,8 @@ public function testInstallUninstall() {
// Import the configuration thereby re-installing all the modules.
$this->drupalPostForm('admin/config/development/configuration', array(), t('Import all'));
// Modules have been installed that have services.
$this->rebuildContainer();
// Check that there are no errors.
$this->assertIdentical($this->configImporter()->getErrors(), array());
......
......@@ -122,35 +122,6 @@ function testNodeTypeEditing() {
$this->assertNoRaw('Body', 'Body field was not found.');
}
/**
* Tests that node types correctly handles their locking.
*/
function testNodeTypeStatus() {
// Enable all core node modules, and all types should be active.
$this->container->get('module_installer')->install(array('book'), FALSE);
$types = node_type_get_types();
foreach (array('book', 'article', 'page') as $type) {
$this->assertTrue(isset($types[$type]), format_string('%type is found in node types.', array('%type' => $type)));
$this->assertFalse($types[$type]->isLocked(), format_string('%type type is not locked.', array('%type' => $type)));
}
// Disable book module and the respective type should still be active, since
// it is not provided by shipped configuration entity.
$this->container->get('module_installer')->uninstall(array('book'), FALSE);
$types = node_type_get_types();
$this->assertFalse($types['book']->isLocked(), "Book module's node type still active.");
$this->assertFalse($types['article']->isLocked(), 'Article node type still active.');
$this->assertFalse($types['page']->isLocked(), 'Basic page node type still active.');
// Re-install the modules and verify that the types are active again.
$this->container->get('module_installer')->install(array('book'), FALSE);
$types = node_type_get_types();
foreach (array('book', 'article', 'page') as $type) {
$this->assertTrue(isset($types[$type]), format_string('%type is found in node types.', array('%type' => $type)));
$this->assertFalse($types[$type]->isLocked(), format_string('%type type is not locked.', array('%type' => $type)));
}
}
/**
* Tests deleting a content type that still has content.
*/
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment