Update to Drupal 8.0.5. For more information, see https://www.drupal.org/node/2679347
This commit is contained in:
parent
2a9f1f148d
commit
fd3b12cf27
14
composer.lock
generated
14
composer.lock
generated
|
@ -2570,16 +2570,16 @@
|
|||
},
|
||||
{
|
||||
"name": "jcalderonzumba/gastonjs",
|
||||
"version": "dev-master",
|
||||
"version": "v1.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/jcalderonzumba/gastonjs.git",
|
||||
"reference": "5e231b4df98275c404e1371fc5fadd34f6a121ad"
|
||||
"reference": "21bebb8ca03eb0f93ec2f3fad61192fb079e2622"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/jcalderonzumba/gastonjs/zipball/5e231b4df98275c404e1371fc5fadd34f6a121ad",
|
||||
"reference": "5e231b4df98275c404e1371fc5fadd34f6a121ad",
|
||||
"url": "https://api.github.com/repos/jcalderonzumba/gastonjs/zipball/21bebb8ca03eb0f93ec2f3fad61192fb079e2622",
|
||||
"reference": "21bebb8ca03eb0f93ec2f3fad61192fb079e2622",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -2623,7 +2623,7 @@
|
|||
"headless",
|
||||
"phantomjs"
|
||||
],
|
||||
"time": "2015-10-07 11:40:41"
|
||||
"time": "2016-01-18 09:21:03"
|
||||
},
|
||||
{
|
||||
"name": "jcalderonzumba/mink-phantomjs-driver",
|
||||
|
@ -3739,9 +3739,7 @@
|
|||
],
|
||||
"aliases": [],
|
||||
"minimum-stability": "dev",
|
||||
"stability-flags": {
|
||||
"jcalderonzumba/gastonjs": 20
|
||||
},
|
||||
"stability-flags": [],
|
||||
"prefer-stable": true,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
|
|
|
@ -30,13 +30,13 @@
|
|||
"guard-for-in": 2,
|
||||
"indent": [2, 2, {"SwitchCase": 1}],
|
||||
"key-spacing": [2, {"beforeColon": false, "afterColon": true}],
|
||||
"keyword-spacing": [2, {"before": true, "after": true}],
|
||||
"linebreak-style": [2, "unix"],
|
||||
"lines-around-comment": [2, {"beforeBlockComment": true, "afterBlockComment": false}],
|
||||
"new-parens": 2,
|
||||
"no-array-constructor": 2,
|
||||
"no-caller": 2,
|
||||
"no-catch-shadow": 2,
|
||||
"no-empty-label": 2,
|
||||
"no-eval": 2,
|
||||
"no-extend-native": 2,
|
||||
"no-extra-bind": 2,
|
||||
|
@ -74,12 +74,10 @@
|
|||
"quotes": [2, "single", "avoid-escape"],
|
||||
"semi": [2, "always"],
|
||||
"semi-spacing": [2, {"before": false, "after": true}],
|
||||
"space-after-keywords": [2, "always"],
|
||||
"space-before-blocks": [2, "always"],
|
||||
"space-before-function-paren": [2, {"anonymous": "always", "named": "never"}],
|
||||
"space-in-parens": [2, "never"],
|
||||
"space-infix-ops": 2,
|
||||
"space-return-throw-case": 2,
|
||||
"space-unary-ops": [2, { "words": true, "nonwords": false }],
|
||||
"spaced-comment": [2, "always"],
|
||||
"strict": 2,
|
||||
|
|
|
@ -978,7 +978,7 @@ Drupal 4.5.0, 2004-10-18
|
|||
- Syndication:
|
||||
* Added support for RSS ping-notifications of http://technorati.com/.
|
||||
* Refactored the categorization of syndicated news items.
|
||||
* Added an URL alias for 'rss.xml'.
|
||||
* Added a URL alias for 'rss.xml'.
|
||||
* Improved date parsing.
|
||||
- Database backend:
|
||||
* Added support for multiple database connections.
|
||||
|
|
|
@ -88,7 +88,7 @@ INSTALLATION
|
|||
directory within your web server's document root or your public HTML
|
||||
directory, continue with this command:
|
||||
|
||||
mv drupal-x.y.z/* drupal-x.y.z/.htaccess drupal-x.y.z/.csslintrc drupal-x.y.z/.editorconfig drupal-x.y.z/.eslintignore drupal-x.y.z/.eslintrc /path/to/your/installation
|
||||
mv drupal-x.y.z/* drupal-x.y.z/.htaccess drupal-x.y.z/.csslintrc drupal-x.y.z/.editorconfig drupal-x.y.z/.eslintignore drupal-x.y.z/.eslintrc drupal-x.y.z/.gitattributes /path/to/your/installation
|
||||
|
||||
2. Create the Drupal database.
|
||||
|
||||
|
|
|
@ -24,7 +24,8 @@ Drupal 8
|
|||
- Alex Pott 'alexpott' https://www.drupal.org/u/alexpott
|
||||
(Framework Manager)
|
||||
|
||||
Provisional membership: None at this time.
|
||||
Provisional membership:
|
||||
- Scott Reeves 'Cottser' https://www.drupal.org/u/cottser
|
||||
|
||||
Drupal 7
|
||||
- David Rothstein 'David_Rothstein' https://www.drupal.org/u/david_rothstein
|
||||
|
@ -32,12 +33,6 @@ Drupal 7
|
|||
|
||||
Provisional membership: None at this time.
|
||||
|
||||
Drupal 6
|
||||
- Gábor Hojtsy 'Gábor Hojtsy' https://www.drupal.org/u/gábor-hojtsy
|
||||
(Release Manager, Framework Manager, Product Manager)
|
||||
|
||||
Provisional membership: None at this time.
|
||||
|
||||
Subsystem maintainers
|
||||
---------------------
|
||||
|
||||
|
@ -390,12 +385,13 @@ Number module
|
|||
Options module
|
||||
- Yves Chedemois 'yched' https://www.drupal.org/u/yched
|
||||
|
||||
Page Cache module
|
||||
- Lorenz Schori 'znerol' https://www.drupal.org/u/znerol
|
||||
- Fabian Franz 'Fabianx' https://www.drupal.org/u/fabianx
|
||||
|
||||
Path module
|
||||
- ?
|
||||
|
||||
Phone module
|
||||
- Dave Reid 'davereid' https://www.drupal.org/u/davereid
|
||||
|
||||
Quick Edit module
|
||||
- Wim Leers 'Wim Leers' https://www.drupal.org/u/wim-leers
|
||||
- Théodore Biadala 'nod_' https://www.drupal.org/u/nod_
|
||||
|
@ -442,6 +438,9 @@ Taxonomy module
|
|||
- Nathaniel Catchpole 'catch' https://www.drupal.org/u/catch
|
||||
- Benjamin Doherty 'bangpound' https://www.drupal.org/u/bangpound
|
||||
|
||||
Telephone module
|
||||
- Dave Reid 'davereid' https://www.drupal.org/u/davereid
|
||||
|
||||
Text module
|
||||
- ?
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
"require-dev": {
|
||||
"behat/mink": "~1.6",
|
||||
"behat/mink-goutte-driver": "~1.2",
|
||||
"jcalderonzumba/gastonjs": "^1.1@dev",
|
||||
"jcalderonzumba/gastonjs": "~1.0.2",
|
||||
"jcalderonzumba/mink-phantomjs-driver": "~0.3.1",
|
||||
"mikey179/vfsStream": "~1.2",
|
||||
"phpunit/phpunit": "~4.8",
|
||||
|
|
|
@ -178,6 +178,9 @@ field.widget.settings.email_default:
|
|||
placeholder:
|
||||
type: label
|
||||
label: 'Placeholder'
|
||||
size:
|
||||
type: integer
|
||||
label: 'Size of email field'
|
||||
|
||||
field.widget.settings.datetime_timestamp:
|
||||
type: mapping
|
||||
|
|
|
@ -1045,8 +1045,8 @@
|
|||
* - The class name needs to end in the word Test.
|
||||
* - The namespace must be a subspace/subdirectory of \Drupal\yourmodule\Tests,
|
||||
* where yourmodule is your module's machine name.
|
||||
* - The test class file must be named and placed under the yourmodule/tests/src
|
||||
* directory, according to the PSR-4 standard.
|
||||
* - The test class file must be named and placed under the
|
||||
* yourmodule/tests/src/Unit directory, according to the PSR-4 standard.
|
||||
* - Your test class needs a phpDoc comment block with a description and
|
||||
* a @group annotation, which gives information about the test.
|
||||
* - Methods in your test class whose names start with 'test' are the actual
|
||||
|
@ -1071,7 +1071,7 @@
|
|||
* $modules member variable -- keep in mind that by default, WebTestBase uses
|
||||
* a "testing" install profile, with a minimal set of modules enabled.
|
||||
* - For functional tests that do not test web output, define a class that
|
||||
* extends \Drupal\simpletest\KernelTestBase. This class is much faster
|
||||
* extends \Drupal\KernelTests\KernelTestBase. This class is much faster
|
||||
* than WebTestBase, because instead of making a full install of Drupal, it
|
||||
* uses an in-memory pseudo-installation (similar to what the installer and
|
||||
* update scripts use). To use this test class, you will need to create the
|
||||
|
|
|
@ -258,11 +258,10 @@ function drupal_get_path($type, $name) {
|
|||
* Translates a string to the current language or to a given language.
|
||||
*
|
||||
* In order for strings to be localized, make them available in one of the ways
|
||||
* supported by the
|
||||
* @link https://www.drupal.org/node/322729 Localization API @endlink. When
|
||||
* possible, use the \Drupal\Core\StringTranslation\StringTranslationTrait
|
||||
* $this->t(). Otherwise create a new
|
||||
* \Drupal\Core\StringTranslation\TranslatableMarkup object directly.
|
||||
* supported by the @link i18n Localization API. @endlink When possible, use
|
||||
* the \Drupal\Core\StringTranslation\StringTranslationTrait $this->t().
|
||||
* Otherwise create a new \Drupal\Core\StringTranslation\TranslatableMarkup
|
||||
* object directly.
|
||||
*
|
||||
* See \Drupal\Core\StringTranslation\TranslatableMarkup::__construct() for
|
||||
* important security information and usage guidelines.
|
||||
|
|
|
@ -325,6 +325,10 @@ function template_preprocess_vertical_tabs(&$variables) {
|
|||
*/
|
||||
function template_preprocess_input(&$variables) {
|
||||
$element = $variables['element'];
|
||||
// Remove name attribute if empty, for W3C compliance.
|
||||
if (isset($variables['attributes']['name']) && empty((string) $variables['attributes']['name'])) {
|
||||
unset($variables['attributes']['name']);
|
||||
}
|
||||
$variables['children'] = $element['#children'];
|
||||
}
|
||||
|
||||
|
@ -882,15 +886,13 @@ function &batch_get() {
|
|||
*
|
||||
* Depending on whether the batch is progressive or not, the
|
||||
* Drupal\Core\Queue\Batch or Drupal\Core\Queue\BatchMemory handler classes will
|
||||
* be used.
|
||||
* be used. The name and class of the queue are added by reference to the
|
||||
* batch set.
|
||||
*
|
||||
* @param $batch
|
||||
* The batch array.
|
||||
* @param $set_id
|
||||
* The id of the set to process.
|
||||
*
|
||||
* @return
|
||||
* The name and class of the queue are added by reference to the batch set.
|
||||
*/
|
||||
function _batch_populate_queue(&$batch, $set_id) {
|
||||
$batch_set = &$batch['sets'][$set_id];
|
||||
|
|
|
@ -1313,9 +1313,7 @@ function template_preprocess_html(&$variables) {
|
|||
*
|
||||
* Default template: page.html.twig.
|
||||
*
|
||||
* Most themes use their own copy of page.html.twig. The default is located
|
||||
* inside "modules/system/page.html.twig". Look in there for the full list of
|
||||
* variables.
|
||||
* See the page.html.twig template for the list of variables.
|
||||
*/
|
||||
function template_preprocess_page(&$variables) {
|
||||
$language_interface = \Drupal::languageManager()->getCurrentLanguage();
|
||||
|
|
|
@ -81,7 +81,7 @@ class Drupal {
|
|||
/**
|
||||
* The current system version.
|
||||
*/
|
||||
const VERSION = '8.0.4';
|
||||
const VERSION = '8.0.5';
|
||||
|
||||
/**
|
||||
* Core API compatibility.
|
||||
|
|
|
@ -43,7 +43,7 @@ class InsertCommand implements CommandInterface, CommandWithAttachedAssetsInterf
|
|||
protected $content;
|
||||
|
||||
/**
|
||||
* A settings array to be passed to any any attached JavaScript behavior.
|
||||
* A settings array to be passed to any attached JavaScript behavior.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
|
|
|
@ -11,8 +11,8 @@ namespace Drupal\Core\Ajax;
|
|||
* AJAX command for calling the jQuery replace() method.
|
||||
*
|
||||
* The 'insert/replaceWith' command instructs the client to use jQuery's
|
||||
* replaceWith() method to replace each element matched matched by the given
|
||||
* selector with the given HTML.
|
||||
* replaceWith() method to replace each element matched by the given selector
|
||||
* with the given HTML.
|
||||
*
|
||||
* This command is implemented by Drupal.AjaxCommands.prototype.insert()
|
||||
* defined in misc/ajax.js.
|
||||
|
|
|
@ -14,7 +14,7 @@ use Symfony\Component\HttpFoundation\Request;
|
|||
*
|
||||
* Some authentication methods should not be available throughout a whole site.
|
||||
* For instance, there are good reasons to restrict insecure methods like HTTP
|
||||
* basic authentication or an URL token authentication method to API-only
|
||||
* basic authentication or a URL token authentication method to API-only
|
||||
* routes.
|
||||
*/
|
||||
interface AuthenticationProviderFilterInterface {
|
||||
|
|
|
@ -375,8 +375,12 @@ class DbDumpCommand extends DbCommandBase {
|
|||
* The template for the generated PHP script.
|
||||
*/
|
||||
protected function getTemplate() {
|
||||
// The template contains an instruction for the file to be ignored by PHPCS.
|
||||
// This is because the files can be huge and coding standards are
|
||||
// irrelevant.
|
||||
$script = <<<'ENDOFSCRIPT'
|
||||
<?php
|
||||
// @codingStandardsIgnoreFile
|
||||
/**
|
||||
* @file
|
||||
* A database agnostic dump for testing purposes.
|
||||
|
|
|
@ -302,7 +302,7 @@ class ConfigManager implements ConfigManagerInterface {
|
|||
$dependency_manager = $this->getConfigDependencyManager();
|
||||
$dependents = $this->findConfigEntityDependentsAsEntities($type, $names, $dependency_manager);
|
||||
$original_dependencies = $dependents;
|
||||
$update_uuids = [];
|
||||
$delete_uuids = $update_uuids = [];
|
||||
|
||||
$return = [
|
||||
'update' => [],
|
||||
|
@ -311,26 +311,35 @@ class ConfigManager implements ConfigManagerInterface {
|
|||
];
|
||||
|
||||
// Try to fix any dependencies and find out what will happen to the
|
||||
// dependency graph.
|
||||
foreach ($dependents as $dependent) {
|
||||
// dependency graph. Entities are processed in the order of most dependent
|
||||
// first. For example, this ensures that fields are removed before
|
||||
// field storages.
|
||||
while ($dependent = array_pop($dependents)) {
|
||||
/** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $dependent */
|
||||
if ($dry_run) {
|
||||
// Clone the entity so any changes do not change any static caches.
|
||||
$dependent = clone $dependent;
|
||||
}
|
||||
$fixed = FALSE;
|
||||
if ($this->callOnDependencyRemoval($dependent, $original_dependencies, $type, $names)) {
|
||||
// Recalculate dependencies and update the dependency graph data.
|
||||
$dependent->calculateDependencies();
|
||||
$dependency_manager->updateData($dependent->getConfigDependencyName(), $dependent->getDependencies());
|
||||
// Based on the updated data rebuild the list of dependents.
|
||||
// Based on the updated data rebuild the list of dependents. This will
|
||||
// remove entities that are no longer dependent after the recalculation.
|
||||
$dependents = $this->findConfigEntityDependentsAsEntities($type, $names, $dependency_manager);
|
||||
// Remove any entities that we've already marked for deletion.
|
||||
$dependents = array_filter($dependents, function ($dependent) use ($delete_uuids) {
|
||||
return !in_array($dependent->uuid(), $delete_uuids);
|
||||
});
|
||||
// Ensure that the dependency has actually been fixed. It is possible
|
||||
// that the dependent has multiple dependencies that cause it to be in
|
||||
// the dependency chain.
|
||||
$fixed = TRUE;
|
||||
foreach ($dependents as $entity) {
|
||||
foreach ($dependents as $key => $entity) {
|
||||
if ($entity->uuid() == $dependent->uuid()) {
|
||||
$fixed = FALSE;
|
||||
unset($dependents[$key]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -339,15 +348,12 @@ class ConfigManager implements ConfigManagerInterface {
|
|||
$update_uuids[] = $dependent->uuid();
|
||||
}
|
||||
}
|
||||
// If the entity cannot be fixed then it has to be deleted.
|
||||
if (!$fixed) {
|
||||
$delete_uuids[] = $dependent->uuid();
|
||||
$return['delete'][] = $dependent;
|
||||
}
|
||||
}
|
||||
// Now that we've fixed all the possible dependencies the remaining need to
|
||||
// be deleted. Reverse the deletes so that entities are removed in the
|
||||
// correct order of dependence. For example, this ensures that fields are
|
||||
// removed before field storages.
|
||||
$return['delete'] = array_reverse($dependents);
|
||||
$delete_uuids = array_map(function($dependent) {
|
||||
return $dependent->uuid();
|
||||
}, $return['delete']);
|
||||
// Use the lists of UUIDs to filter the original list to work out which
|
||||
// configuration entities are unchanged.
|
||||
$return['unchanged'] = array_filter($original_dependencies, function ($dependent) use ($delete_uuids, $update_uuids) {
|
||||
|
|
|
@ -352,6 +352,32 @@ abstract class ConfigEntityBase extends Entity implements ConfigEntityInterface
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __sleep() {
|
||||
$keys_to_unset = [];
|
||||
if ($this instanceof EntityWithPluginCollectionInterface) {
|
||||
$vars = get_object_vars($this);
|
||||
foreach ($this->getPluginCollections() as $plugin_config_key => $plugin_collection) {
|
||||
// Save any changes to the plugin configuration to the entity.
|
||||
$this->set($plugin_config_key, $plugin_collection->getConfiguration());
|
||||
// If the plugin collections are stored as properties on the entity,
|
||||
// mark them to be unset.
|
||||
$keys_to_unset += array_filter($vars, function ($value) use ($plugin_collection) {
|
||||
return $plugin_collection === $value;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$vars = parent::__sleep();
|
||||
|
||||
if (!empty($keys_to_unset)) {
|
||||
$vars = array_diff($vars, array_keys($keys_to_unset));
|
||||
}
|
||||
return $vars;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
|
|
@ -207,8 +207,9 @@ class FileStorage implements StorageInterface {
|
|||
$files = scandir($dir);
|
||||
|
||||
$names = array();
|
||||
$pattern = '/^' . preg_quote($prefix, '/') . '.*' . preg_quote($extension, '/') . '$/';
|
||||
foreach ($files as $file) {
|
||||
if ($file[0] !== '.' && fnmatch($prefix . '*' . $extension, $file)) {
|
||||
if ($file[0] !== '.' && preg_match($pattern, $file)) {
|
||||
$names[] = basename($file, $extension);
|
||||
}
|
||||
}
|
||||
|
@ -290,6 +291,7 @@ class FileStorage implements StorageInterface {
|
|||
*/
|
||||
protected function getAllCollectionNamesHelper($directory) {
|
||||
$collections = array();
|
||||
$pattern = '/\.' . preg_quote($this->getFileExtension(), '/') . '$/';
|
||||
foreach (new \DirectoryIterator($directory) as $fileinfo) {
|
||||
if ($fileinfo->isDir() && !$fileinfo->isDot()) {
|
||||
$collection = $fileinfo->getFilename();
|
||||
|
@ -309,7 +311,7 @@ class FileStorage implements StorageInterface {
|
|||
// collection.
|
||||
// @see \Drupal\Core\Config\FileStorage::listAll()
|
||||
foreach (scandir($directory . '/' . $collection) as $file) {
|
||||
if ($file[0] !== '.' && fnmatch('*.' . $this->getFileExtension(), $file)) {
|
||||
if ($file[0] !== '.' && preg_match($pattern, $file)) {
|
||||
$collections[] = $collection;
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -190,6 +190,7 @@ class InstallStorage extends FileStorage {
|
|||
*/
|
||||
public function getComponentNames(array $list) {
|
||||
$extension = '.' . $this->getFileExtension();
|
||||
$pattern = '/' . preg_quote($extension, '/') . '$/';
|
||||
$folders = array();
|
||||
foreach ($list as $extension_object) {
|
||||
// We don't have to use ExtensionDiscovery here because our list of
|
||||
|
@ -203,7 +204,7 @@ class InstallStorage extends FileStorage {
|
|||
$files = scandir($directory);
|
||||
|
||||
foreach ($files as $file) {
|
||||
if ($file[0] !== '.' && fnmatch('*' . $extension, $file)) {
|
||||
if ($file[0] !== '.' && preg_match($pattern, $file)) {
|
||||
$folders[basename($file, $extension)] = $directory;
|
||||
}
|
||||
}
|
||||
|
@ -220,6 +221,7 @@ class InstallStorage extends FileStorage {
|
|||
*/
|
||||
public function getCoreNames() {
|
||||
$extension = '.' . $this->getFileExtension();
|
||||
$pattern = '/' . preg_quote($extension, '/') . '$/';
|
||||
$folders = array();
|
||||
$directory = $this->getCoreFolder();
|
||||
if (is_dir($directory)) {
|
||||
|
@ -230,7 +232,7 @@ class InstallStorage extends FileStorage {
|
|||
$files = scandir($directory);
|
||||
|
||||
foreach ($files as $file) {
|
||||
if ($file[0] !== '.' && fnmatch('*' . $extension, $file)) {
|
||||
if ($file[0] !== '.' && preg_match($pattern, $file)) {
|
||||
$folders[basename($file, $extension)] = $directory;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -138,7 +138,38 @@ class Connection extends DatabaseConnection {
|
|||
}
|
||||
}
|
||||
|
||||
return parent::query($query, $args, $options);
|
||||
// We need to wrap queries with a savepoint if:
|
||||
// - Currently in a transaction.
|
||||
// - A 'mimic_implicit_commit' does not exist already.
|
||||
// - The query is not a savepoint query.
|
||||
$wrap_with_savepoint = $this->inTransaction() &&
|
||||
!isset($this->transactionLayers['mimic_implicit_commit']) &&
|
||||
!(is_string($query) && (
|
||||
stripos($query, 'ROLLBACK TO SAVEPOINT ') === 0 ||
|
||||
stripos($query, 'RELEASE SAVEPOINT ') === 0 ||
|
||||
stripos($query, 'SAVEPOINT ') === 0
|
||||
)
|
||||
);
|
||||
if ($wrap_with_savepoint) {
|
||||
// Create a savepoint so we can rollback a failed query. This is so we can
|
||||
// mimic MySQL and SQLite transactions which don't fail if a single query
|
||||
// fails. This is important for tables that are created on demand. For
|
||||
// example, \Drupal\Core\Cache\DatabaseBackend.
|
||||
$this->addSavepoint();
|
||||
try {
|
||||
$return = parent::query($query, $args, $options);
|
||||
$this->releaseSavepoint();
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->rollbackSavepoint();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$return = parent::query($query, $args, $options);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
public function prepareQuery($query) {
|
||||
|
|
|
@ -99,12 +99,25 @@ class Insert extends QueryInsert {
|
|||
elseif ($options['return'] == Database::RETURN_INSERT_ID) {
|
||||
$options['return'] = Database::RETURN_NULL;
|
||||
}
|
||||
// Only use the returned last_insert_id if it is not already set.
|
||||
if (!empty($last_insert_id)) {
|
||||
$this->connection->query($stmt, array(), $options);
|
||||
|
||||
// Create a savepoint so we can rollback a failed query. This is so we can
|
||||
// mimic MySQL and SQLite transactions which don't fail if a single query
|
||||
// fails. This is important for tables that are created on demand. For
|
||||
// example, \Drupal\Core\Cache\DatabaseBackend.
|
||||
$this->connection->addSavepoint();
|
||||
try {
|
||||
// Only use the returned last_insert_id if it is not already set.
|
||||
if (!empty($last_insert_id)) {
|
||||
$this->connection->query($stmt, array(), $options);
|
||||
}
|
||||
else {
|
||||
$last_insert_id = $this->connection->query($stmt, array(), $options);
|
||||
}
|
||||
$this->connection->releaseSavepoint();
|
||||
}
|
||||
else {
|
||||
$last_insert_id = $this->connection->query($stmt, array(), $options);
|
||||
catch (\Exception $e) {
|
||||
$this->connection->rollbackSavepoint();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
// Re-initialize the values array so that we can re-use this query.
|
||||
|
|
|
@ -76,11 +76,23 @@ class NativeUpsert extends QueryUpsert {
|
|||
$options['sequence_name'] = $table_information->sequences[0];
|
||||
}
|
||||
|
||||
$this->connection->query($stmt, [], $options);
|
||||
|
||||
// Re-initialize the values array so that we can re-use this query.
|
||||
$this->insertValues = [];
|
||||
|
||||
// Create a savepoint so we can rollback a failed query. This is so we can
|
||||
// mimic MySQL and SQLite transactions which don't fail if a single query
|
||||
// fails. This is important for tables that are created on demand. For
|
||||
// example, \Drupal\Core\Cache\DatabaseBackend.
|
||||
$this->connection->addSavepoint();
|
||||
try {
|
||||
$this->connection->query($stmt, [], $options);
|
||||
$this->connection->releaseSavepoint();
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->connection->rollbackSavepoint();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
|
|
@ -765,7 +765,7 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
|
|||
* The cache key used for the service container.
|
||||
*/
|
||||
protected function getContainerCacheKey() {
|
||||
$parts = array('service_container', $this->environment, \Drupal::VERSION, Settings::get('deployment_identifier'));
|
||||
$parts = array('service_container', $this->environment, \Drupal::VERSION, Settings::get('deployment_identifier'), serialize(Settings::get('container_yamls')));
|
||||
return implode(':', $parts);
|
||||
}
|
||||
|
||||
|
@ -866,7 +866,7 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
|
|||
|
||||
// If needs dumping flag was set, dump the container.
|
||||
if ($this->containerNeedsDumping && !$this->cacheDrupalContainer($container_definition)) {
|
||||
$this->container->get('logger.factory')->get('DrupalKernel')->notice('Container cannot be saved to cache.');
|
||||
$this->container->get('logger.factory')->get('DrupalKernel')->error('Container cannot be saved to cache.');
|
||||
}
|
||||
|
||||
return $this->container;
|
||||
|
|
|
@ -139,7 +139,7 @@ abstract class ContentEntityStorageBase extends EntityStorageBase implements Con
|
|||
$values[$this->langcodeKey] = $langcode;
|
||||
$values[$this->getEntityType()->getKey('default_langcode')] = FALSE;
|
||||
$this->initFieldValues($translation, $values, $field_names);
|
||||
$this->invokeHook('translation_create', $entity);
|
||||
$this->invokeHook('translation_create', $translation);
|
||||
return $translation;
|
||||
}
|
||||
|
||||
|
|
|
@ -309,6 +309,9 @@ interface EntityInterface extends AccessibleInterface, CacheableDependencyInterf
|
|||
* The entity storage object.
|
||||
*
|
||||
* @see \Drupal\Core\Field\FieldItemListInterface::preSave()
|
||||
*
|
||||
* @throws \Exception
|
||||
* When there is a problem that should prevent saving the entity.
|
||||
*/
|
||||
public function preSave(EntityStorageInterface $storage);
|
||||
|
||||
|
|
|
@ -177,25 +177,20 @@ class EntityTypeManager extends DefaultPluginManager implements EntityTypeManage
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormObject($entity_type, $operation) {
|
||||
if (!isset($this->handlers['form'][$operation][$entity_type])) {
|
||||
if (!$class = $this->getDefinition($entity_type, TRUE)->getFormClass($operation)) {
|
||||
throw new InvalidPluginDefinitionException($entity_type, sprintf('The "%s" entity type did not specify a "%s" form class.', $entity_type, $operation));
|
||||
}
|
||||
|
||||
$form_object = $this->classResolver->getInstanceFromDefinition($class);
|
||||
|
||||
$form_object
|
||||
->setStringTranslation($this->stringTranslation)
|
||||
->setModuleHandler($this->moduleHandler)
|
||||
->setEntityTypeManager($this)
|
||||
->setOperation($operation)
|
||||
// The entity manager cannot be injected due to a circular dependency.
|
||||
// @todo Remove this set call in https://www.drupal.org/node/2603542.
|
||||
->setEntityManager(\Drupal::entityManager());
|
||||
$this->handlers['form'][$operation][$entity_type] = $form_object;
|
||||
if (!$class = $this->getDefinition($entity_type, TRUE)->getFormClass($operation)) {
|
||||
throw new InvalidPluginDefinitionException($entity_type, sprintf('The "%s" entity type did not specify a "%s" form class.', $entity_type, $operation));
|
||||
}
|
||||
|
||||
return $this->handlers['form'][$operation][$entity_type];
|
||||
$form_object = $this->classResolver->getInstanceFromDefinition($class);
|
||||
|
||||
return $form_object
|
||||
->setStringTranslation($this->stringTranslation)
|
||||
->setModuleHandler($this->moduleHandler)
|
||||
->setEntityTypeManager($this)
|
||||
->setOperation($operation)
|
||||
// The entity manager cannot be injected due to a circular dependency.
|
||||
// @todo Remove this set call in https://www.drupal.org/node/2603542.
|
||||
->setEntityManager(\Drupal::entityManager());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -699,7 +699,6 @@ function hook_entity_type_alter(array &$entity_types) {
|
|||
*
|
||||
* @see \Drupal\Core\Entity\EntityManagerInterface::getAllViewModes()
|
||||
* @see \Drupal\Core\Entity\EntityManagerInterface::getViewModes()
|
||||
* @see hook_entity_view_mode_info()
|
||||
*/
|
||||
function hook_entity_view_mode_info_alter(&$view_modes) {
|
||||
$view_modes['user']['full']['status'] = TRUE;
|
||||
|
|
|
@ -131,7 +131,7 @@ class TimestampFormatter extends FormatterBase implements ContainerFactoryPlugin
|
|||
);
|
||||
|
||||
$elements['custom_date_format']['#states']['visible'][] = array(
|
||||
':input[name="name="fields[' . $this->fieldDefinition->getName() . '][settings_edit_form][settings][date_format]"]' => array('value' => 'custom'),
|
||||
':input[name="fields[' . $this->fieldDefinition->getName() . '][settings_edit_form][settings][date_format]"]' => array('value' => 'custom'),
|
||||
);
|
||||
|
||||
$elements['timezone'] = array(
|
||||
|
|
|
@ -10,6 +10,7 @@ namespace Drupal\Core\Field\Plugin\Field\FieldWidget;
|
|||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Field\WidgetBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Render\Element\Email;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'email_default' widget.
|
||||
|
@ -29,6 +30,7 @@ class EmailDefaultWidget extends WidgetBase {
|
|||
*/
|
||||
public static function defaultSettings() {
|
||||
return array(
|
||||
'size' => 60,
|
||||
'placeholder' => '',
|
||||
) + parent::defaultSettings();
|
||||
}
|
||||
|
@ -37,6 +39,13 @@ class EmailDefaultWidget extends WidgetBase {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsForm(array $form, FormStateInterface $form_state) {
|
||||
$element['size'] = array(
|
||||
'#type' => 'number',
|
||||
'#title' => $this->t('Textfield size'),
|
||||
'#default_value' => $this->getSetting('size'),
|
||||
'#required' => TRUE,
|
||||
'#min' => 1,
|
||||
);
|
||||
$element['placeholder'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Placeholder'),
|
||||
|
@ -59,6 +68,7 @@ class EmailDefaultWidget extends WidgetBase {
|
|||
else {
|
||||
$summary[] = t('No placeholder');
|
||||
}
|
||||
$summary[] = t('Textfield size: @size', array('@size' => $this->getSetting('size')));
|
||||
|
||||
return $summary;
|
||||
}
|
||||
|
@ -71,6 +81,8 @@ class EmailDefaultWidget extends WidgetBase {
|
|||
'#type' => 'email',
|
||||
'#default_value' => isset($items[$delta]->value) ? $items[$delta]->value : NULL,
|
||||
'#placeholder' => $this->getSetting('placeholder'),
|
||||
'#size' => $this->getSetting('size'),
|
||||
'#maxlength' => Email::EMAIL_MAX_LENGTH,
|
||||
);
|
||||
return $element;
|
||||
}
|
||||
|
|
|
@ -81,6 +81,15 @@ class FormAjaxResponseBuilder implements FormAjaxResponseBuilderInterface {
|
|||
$response = $result;
|
||||
}
|
||||
else {
|
||||
// At this point we know callback returned a render element. If the
|
||||
// element is part of the group (#group is set on it) it won't be rendered
|
||||
// unless we remove #group from it. This is caused by
|
||||
// \Drupal\Core\Render\Element\RenderElement::preRenderGroup(), which
|
||||
// prevents all members of groups from being rendered directly.
|
||||
if (!empty($result['#group'])) {
|
||||
unset($result['#group']);
|
||||
}
|
||||
|
||||
/** @var \Drupal\Core\Ajax\AjaxResponse $response */
|
||||
$response = $this->ajaxRenderer->renderResponse($result, $request, $this->routeMatch);
|
||||
}
|
||||
|
|
|
@ -29,7 +29,8 @@ use Drupal\Core\Language\LanguageInterface;
|
|||
* - Any time UI text is displayed using PHP code, it should be passed through
|
||||
* either the global t() function or a t() method on the class. If it
|
||||
* involves plurals, it should be passed through either the global
|
||||
* formatPlural() function or a formatPlural() method on the class. Use
|
||||
* \Drupal\Core\StringTranslation\PluralTranslatableMarkup::createFromTranslatedString()
|
||||
* or a formatPlural() method on the class. Use
|
||||
* \Drupal\Core\StringTranslation\StringTranslationTrait to get these methods
|
||||
* into a class.
|
||||
* - Dates displayed in the UI should be passed through the 'date' service
|
||||
|
|
|
@ -14,11 +14,11 @@ namespace Drupal\Core\Lock;
|
|||
*
|
||||
* In most environments, multiple Drupal page requests (a.k.a. threads or
|
||||
* processes) will execute in parallel. This leads to potential conflicts or
|
||||
* race conditions when two requests execute the same code at the same time. A
|
||||
* common example of this is a rebuild like menu_router_rebuild() where we
|
||||
* invoke many hook implementations to get and process data from all active
|
||||
* modules, and then delete the current data in the database to insert the new
|
||||
* afterwards.
|
||||
* race conditions when two requests execute the same code at the same time. For
|
||||
* instance, some implementations of hook_cron() implicitly assume they are
|
||||
* running only once, rather than having multiple calls in parallel. To prevent
|
||||
* problems with such code, the cron system uses a locking process to ensure
|
||||
* that cron is not started again if it is already running.
|
||||
*
|
||||
* This is a cooperative, advisory lock system. Any long-running operation
|
||||
* that could potentially be attempted in parallel by multiple requests should
|
||||
|
|
|
@ -9,6 +9,14 @@ namespace Drupal\Core\Menu;
|
|||
|
||||
/**
|
||||
* Defines a contextual link plugin.
|
||||
*
|
||||
* Contextual links by default are in the module_name.links.contextual.yml
|
||||
* file. These YAML files contain a list of contextual link plugin definitions,
|
||||
* keyed by the plugin ID. Each definition must define a route_name and a group
|
||||
* and might define title, options, and weight. See the getter methods on this
|
||||
* interface for an explanation of each.
|
||||
*
|
||||
* @ingroup menu
|
||||
*/
|
||||
interface ContextualLinkInterface {
|
||||
|
||||
|
|
|
@ -110,6 +110,10 @@ class MenuLinkDefaultForm implements MenuLinkFormInterface, ContainerInjectionIn
|
|||
'#type' => 'item',
|
||||
'#title' => $this->t('This link is provided by the @name module. The title and path cannot be edited.', array('@name' => $this->moduleHandler->getName($provider))),
|
||||
);
|
||||
$form['id'] = array(
|
||||
'#type' => 'value',
|
||||
'#value' => $this->menuLink->getPluginId(),
|
||||
);
|
||||
$link = array(
|
||||
'#type' => 'link',
|
||||
'#title' => $this->menuLink->getTitle(),
|
||||
|
@ -158,7 +162,11 @@ class MenuLinkDefaultForm implements MenuLinkFormInterface, ContainerInjectionIn
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function extractFormValues(array &$form, FormStateInterface $form_state) {
|
||||
$new_definition = array();
|
||||
// Start from the complete, original, definition.
|
||||
$new_definition = $this->menuLink->getPluginDefinition();
|
||||
// Since the ID may not be present in the definition used to construct the
|
||||
// plugin, add it here so it's available to any consumers of this method.
|
||||
$new_definition['id'] = $form_state->getValue('id');
|
||||
$new_definition['enabled'] = $form_state->getValue('enabled') ? 1 : 0;
|
||||
$new_definition['weight'] = (int) $form_state->getValue('weight');
|
||||
$new_definition['expanded'] = $form_state->getValue('expanded') ? 1 : 0;
|
||||
|
|
|
@ -38,7 +38,8 @@ interface MenuLinkFormInterface extends PluginFormInterface {
|
|||
* The current state of the form.
|
||||
*
|
||||
* @return array
|
||||
* The new plugin definition values taken from the form values.
|
||||
* The new plugin definition values taken from the form values. The plugin
|
||||
* ID must be returned as part of the definition.
|
||||
*/
|
||||
public function extractFormValues(array &$form, FormStateInterface $form_state);
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace Drupal\Core\Path;
|
|||
interface PathValidatorInterface {
|
||||
|
||||
/**
|
||||
* Returns an URL object, if the path is valid and accessible.
|
||||
* Returns a URL object, if the path is valid and accessible.
|
||||
*
|
||||
* @param string $path
|
||||
* The path to check.
|
||||
|
@ -24,7 +24,7 @@ interface PathValidatorInterface {
|
|||
public function getUrlIfValid($path);
|
||||
|
||||
/**
|
||||
* Returns an URL object, if the path is valid.
|
||||
* Returns a URL object, if the path is valid.
|
||||
*
|
||||
* Unlike getUrlIfValid(), access check is not performed. Do not use this
|
||||
* method if the $path is about to be presented to a user.
|
||||
|
|
|
@ -27,7 +27,7 @@ use Drupal\Core\Render\Element;
|
|||
* @code
|
||||
* $form['actions']['preview'] = array(
|
||||
* '#type' => 'button',
|
||||
* '#value => $this->t('Preview'),
|
||||
* '#value' => $this->t('Preview'),
|
||||
* );
|
||||
* @endcode
|
||||
*
|
||||
|
|
|
@ -16,13 +16,40 @@ use Drupal\Core\StringTranslation\TranslatableMarkup;
|
|||
* Provides a form element for a table with radios or checkboxes in left column.
|
||||
*
|
||||
* Properties:
|
||||
* - #header: Table headers used in the table.
|
||||
* - #header: An array of table header labels.
|
||||
* - #options: An associative array where each key is the value returned when
|
||||
* a user selects the radio button or checkbox, and each value is the row of
|
||||
* table data.
|
||||
* - #empty: The message to display if table does not have any options.
|
||||
* - #multiple: Set to FALSE to render the table with radios instead checkboxes.
|
||||
* - #js_select: Set to FALSE if you don't want the select all checkbox added to
|
||||
* the header.
|
||||
*
|
||||
* Other properties of the \Drupal\Core\Render\Element\Table element are also
|
||||
* available.
|
||||
*
|
||||
* Usage example:
|
||||
* See https://www.drupal.org/node/945102 for an example and full explanation.
|
||||
* @code
|
||||
* $header = [
|
||||
* 'first_name' => t('First Name'),
|
||||
* 'last_name' => t('Last Name'),
|
||||
* ];
|
||||
*
|
||||
* $options = [
|
||||
* 1 => ['first_name' => 'Indy', 'last_name' => 'Jones'],
|
||||
* 2 => ['first_name' => 'Darth', 'last_name' => 'Vader'],
|
||||
* 3 => ['first_name' => 'Super', 'last_name' => 'Man'],
|
||||
* ];
|
||||
*
|
||||
* $form['table'] = array(
|
||||
* '#type' => 'tableselect',
|
||||
* '#header' => $header,
|
||||
* '#options' => $options,
|
||||
* '#empty' => t('No users found'),
|
||||
* );
|
||||
* @endcode
|
||||
*
|
||||
* See https://www.drupal.org/node/945102 for a full explanation.
|
||||
*
|
||||
* @see \Drupal\Core\Render\Element\Table
|
||||
*
|
||||
|
|
|
@ -47,7 +47,7 @@ class ContentTypeHeaderMatcher implements RouteFilterInterface {
|
|||
// We do not throw a
|
||||
// \Symfony\Component\Routing\Exception\ResourceNotFoundException here
|
||||
// because we don't want to return a 404 status code, but rather a 415.
|
||||
throw new UnsupportedMediaTypeHttpException('No route found that matches the Content-Type header.');
|
||||
throw new UnsupportedMediaTypeHttpException('No route found that matches "Content-Type: ' . $request->headers->get('Content-Type') . '"');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -34,7 +34,13 @@ interface RedirectDestinationInterface {
|
|||
public function getAsArray();
|
||||
|
||||
/**
|
||||
* Gets the destination as URL.
|
||||
* Gets the destination as a path.
|
||||
*
|
||||
* To convert to a URL suitable for
|
||||
* \Symfony\Component\HttpFoundation\RedirectResponse::__construct() use
|
||||
* @code
|
||||
* \Drupal\Core\Url::fromUserInput(\Drupal::destination()->get())->setAbsolute()->toString()
|
||||
* @endcode
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
|
|
|
@ -305,14 +305,18 @@ class UrlGenerator implements UrlGeneratorInterface {
|
|||
return $collect_bubbleable_metadata ? $generated_url->setGeneratedUrl($url) : $url;
|
||||
}
|
||||
|
||||
$options += array('prefix' => '');
|
||||
$options += $route->getOption('default_url_options') ?: [];
|
||||
$options += array('prefix' => '', 'path_processing' => TRUE);
|
||||
|
||||
$name = $this->getRouteDebugMessage($name);
|
||||
$this->processRoute($name, $route, $parameters, $generated_url);
|
||||
$path = $this->getInternalPathFromRoute($name, $route, $parameters, $query_params);
|
||||
// Outbound path processors might need the route object for the path, e.g.
|
||||
// to get the path pattern.
|
||||
$options['route'] = $route;
|
||||
$path = $this->processPath($path, $options, $generated_url);
|
||||
if ($options['path_processing']) {
|
||||
$path = $this->processPath($path, $options, $generated_url);
|
||||
}
|
||||
|
||||
if (!empty($options['prefix'])) {
|
||||
$path = ltrim($path, '/');
|
||||
|
|
|
@ -55,10 +55,15 @@ class StaticTranslation implements TranslatorInterface {
|
|||
}
|
||||
|
||||
/**
|
||||
* Add translations for new language.
|
||||
* Retrieves translations for a given language.
|
||||
*
|
||||
* @param string $langcode
|
||||
* The langcode of the language.
|
||||
*
|
||||
* @return array
|
||||
* A multidimensional array of translations, indexed by the context the
|
||||
* source string belongs to. The second level is using original strings as
|
||||
* keys. An empty array will be returned when no translations are available.
|
||||
*/
|
||||
protected function getLanguage($langcode) {
|
||||
// This class is usually a base class but we do not declare as abstract
|
||||
|
|
|
@ -254,7 +254,7 @@ class TwigExtension extends \Twig_Extension {
|
|||
}
|
||||
|
||||
/**
|
||||
* Gets a rendered link from an url object.
|
||||
* Gets a rendered link from a url object.
|
||||
*
|
||||
* @param string $text
|
||||
* The link text for the anchor tag as a translated string.
|
||||
|
@ -313,7 +313,7 @@ class TwigExtension extends \Twig_Extension {
|
|||
* Saves the unneeded automatic escaping for performance reasons.
|
||||
*
|
||||
* The URL generation process percent encodes non-alphanumeric characters.
|
||||
* Thus, the only character within an URL that must be escaped in HTML is the
|
||||
* Thus, the only character within a URL that must be escaped in HTML is the
|
||||
* ampersand ("&") which separates query params. Thus we cannot mark
|
||||
* the generated URL as always safe, but only when we are sure there won't be
|
||||
* multiple query params. This is the case when there are none or only one
|
||||
|
@ -402,6 +402,10 @@ class TwigExtension extends \Twig_Extension {
|
|||
* @return string|null
|
||||
* The escaped, rendered output, or NULL if there is no valid output.
|
||||
*
|
||||
* @throws \Exception
|
||||
* When $arg is passed as an object which does not implement __toString(),
|
||||
* RenderableInterface or toString().
|
||||
*
|
||||
* @todo Refactor this to keep it in sync with theme_render_and_autoescape()
|
||||
* in https://www.drupal.org/node/2575065
|
||||
*/
|
||||
|
@ -433,7 +437,7 @@ class TwigExtension extends \Twig_Extension {
|
|||
elseif (method_exists($arg, '__toString')) {
|
||||
$return = (string) $arg;
|
||||
}
|
||||
// You can't throw exceptions in the magic PHP __toString methods, see
|
||||
// You can't throw exceptions in the magic PHP __toString() methods, see
|
||||
// http://php.net/manual/en/language.oop5.magic.php#object.tostring so
|
||||
// we also support a toString method.
|
||||
elseif (method_exists($arg, 'toString')) {
|
||||
|
@ -471,10 +475,11 @@ class TwigExtension extends \Twig_Extension {
|
|||
/**
|
||||
* Wrapper around render() for twig printed output.
|
||||
*
|
||||
* If an object is passed that has no __toString method an exception is thrown;
|
||||
* other objects are casted to string. However in the case that the object is an
|
||||
* instance of a Twig_Markup object it is returned directly to support auto
|
||||
* escaping.
|
||||
* If an object is passed which does not implement __toString(),
|
||||
* RenderableInterface or toString() then an exception is thrown;
|
||||
* Other objects are casted to string. However in the case that the
|
||||
* object is an instance of a Twig_Markup object it is returned directly
|
||||
* to support auto escaping.
|
||||
*
|
||||
* If an array is passed it is rendered via render() and scalar values are
|
||||
* returned directly.
|
||||
|
@ -485,6 +490,10 @@ class TwigExtension extends \Twig_Extension {
|
|||
* @return mixed
|
||||
* The rendered output or an Twig_Markup object.
|
||||
*
|
||||
* @throws \Exception
|
||||
* When $arg is passed as an object which does not implement __toString(),
|
||||
* RenderableInterface or toString().
|
||||
*
|
||||
* @see render
|
||||
* @see TwigNodeVisitor
|
||||
*/
|
||||
|
@ -511,7 +520,7 @@ class TwigExtension extends \Twig_Extension {
|
|||
elseif (method_exists($arg, '__toString')) {
|
||||
return (string) $arg;
|
||||
}
|
||||
// You can't throw exceptions in the magic PHP __toString methods, see
|
||||
// You can't throw exceptions in the magic PHP __toString() methods, see
|
||||
// http://php.net/manual/en/language.oop5.magic.php#object.tostring so
|
||||
// we also support a toString method.
|
||||
elseif (method_exists($arg, 'toString')) {
|
||||
|
|
|
@ -40,7 +40,8 @@ class Registry implements DestructableInterface {
|
|||
* The complete theme registry.
|
||||
*
|
||||
* @var array
|
||||
* An associative array keyed by theme hook names, whose values are
|
||||
* An array of theme registries, keyed by the theme name. Each registry is
|
||||
* an associative array keyed by theme hook names, whose values are
|
||||
* associative arrays containing the aggregated hook definition:
|
||||
* - type: The type of the extension the original theme hook originates
|
||||
* from; e.g., 'module' for theme hook 'node' of Node module.
|
||||
|
@ -79,7 +80,7 @@ class Registry implements DestructableInterface {
|
|||
* - process: An array of theme variable process callbacks to invoke
|
||||
* before invoking the actual theme function or template.
|
||||
*/
|
||||
protected $registry;
|
||||
protected $registry = [];
|
||||
|
||||
/**
|
||||
* The cache backend to use for the complete theme registry data.
|
||||
|
@ -96,11 +97,11 @@ class Registry implements DestructableInterface {
|
|||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* The incomplete, runtime theme registry.
|
||||
* An array of incomplete, runtime theme registries, keyed by theme name.
|
||||
*
|
||||
* @var \Drupal\Core\Utility\ThemeRegistry
|
||||
* @var \Drupal\Core\Utility\ThemeRegistry[]
|
||||
*/
|
||||
protected $runtimeRegistry;
|
||||
protected $runtimeRegistry = [];
|
||||
|
||||
/**
|
||||
* Stores whether the registry was already initialized.
|
||||
|
@ -209,20 +210,20 @@ class Registry implements DestructableInterface {
|
|||
*/
|
||||
public function get() {
|
||||
$this->init($this->themeName);
|
||||
if (isset($this->registry)) {
|
||||
return $this->registry;
|
||||
if (isset($this->registry[$this->theme->getName()])) {
|
||||
return $this->registry[$this->theme->getName()];
|
||||
}
|
||||
if ($cache = $this->cache->get('theme_registry:' . $this->theme->getName())) {
|
||||
$this->registry = $cache->data;
|
||||
$this->registry[$this->theme->getName()] = $cache->data;
|
||||
}
|
||||
else {
|
||||
$this->registry = $this->build();
|
||||
$this->build();
|
||||
// Only persist it if all modules are loaded to ensure it is complete.
|
||||
if ($this->moduleHandler->isLoaded()) {
|
||||
$this->setCache();
|
||||
}
|
||||
}
|
||||
return $this->registry;
|
||||
return $this->registry[$this->theme->getName()];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -235,17 +236,17 @@ class Registry implements DestructableInterface {
|
|||
*/
|
||||
public function getRuntime() {
|
||||
$this->init($this->themeName);
|
||||
if (!isset($this->runtimeRegistry)) {
|
||||
$this->runtimeRegistry = new ThemeRegistry('theme_registry:runtime:' . $this->theme->getName(), $this->cache, $this->lock, array('theme_registry'), $this->moduleHandler->isLoaded());
|
||||
if (!isset($this->runtimeRegistry[$this->theme->getName()])) {
|
||||
$this->runtimeRegistry[$this->theme->getName()] = new ThemeRegistry('theme_registry:runtime:' . $this->theme->getName(), $this->cache, $this->lock, array('theme_registry'), $this->moduleHandler->isLoaded());
|
||||
}
|
||||
return $this->runtimeRegistry;
|
||||
return $this->runtimeRegistry[$this->theme->getName()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Persists the theme registry in the cache backend.
|
||||
*/
|
||||
protected function setCache() {
|
||||
$this->cache->set('theme_registry:' . $this->theme->getName(), $this->registry, Cache::PERMANENT, array('theme_registry'));
|
||||
$this->cache->set('theme_registry:' . $this->theme->getName(), $this->registry[$this->theme->getName()], Cache::PERMANENT, array('theme_registry'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -359,9 +360,9 @@ class Registry implements DestructableInterface {
|
|||
unset($cache[$hook]['preprocess functions']);
|
||||
}
|
||||
}
|
||||
$this->registry = $cache;
|
||||
$this->registry[$this->theme->getName()] = $cache;
|
||||
|
||||
return $this->registry;
|
||||
return $this->registry[$this->theme->getName()];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -719,12 +720,12 @@ class Registry implements DestructableInterface {
|
|||
*/
|
||||
public function reset() {
|
||||
// Reset the runtime registry.
|
||||
if (isset($this->runtimeRegistry) && $this->runtimeRegistry instanceof ThemeRegistry) {
|
||||
$this->runtimeRegistry->clear();
|
||||
foreach ($this->runtimeRegistry as $runtime_registry) {
|
||||
$runtime_registry->clear();
|
||||
}
|
||||
$this->runtimeRegistry = NULL;
|
||||
$this->runtimeRegistry = [];
|
||||
|
||||
$this->registry = NULL;
|
||||
$this->registry = [];
|
||||
Cache::invalidateTags(array('theme_registry'));
|
||||
return $this;
|
||||
}
|
||||
|
@ -733,8 +734,8 @@ class Registry implements DestructableInterface {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function destruct() {
|
||||
if (isset($this->runtimeRegistry)) {
|
||||
$this->runtimeRegistry->destruct();
|
||||
foreach ($this->runtimeRegistry as $runtime_registry) {
|
||||
$runtime_registry->destruct();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -28,11 +28,9 @@ abstract class ComplexDataDefinitionBase extends DataDefinition implements Compl
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPropertyDefinition($name) {
|
||||
if (!isset($this->propertyDefinitions)) {
|
||||
$this->getPropertyDefinitions();
|
||||
}
|
||||
if (isset($this->propertyDefinitions[$name])) {
|
||||
return $this->propertyDefinitions[$name];
|
||||
$definitions = $this->getPropertyDefinitions();
|
||||
if (isset($definitions[$name])) {
|
||||
return $definitions[$name];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -199,4 +199,23 @@ abstract class TypedData implements TypedDataInterface, PluginInspectionInterfac
|
|||
public function getParent() {
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __sleep() {
|
||||
$vars = get_object_vars($this);
|
||||
// Prevent services from being serialized. static::getStringTranslation()
|
||||
// and static::getTypedDataManager() lazy-load them after $this has been
|
||||
// unserialized.
|
||||
// @todo Replace this with
|
||||
// \Drupal\Core\DependencyInjection\DependencySerializationTrait before
|
||||
// Drupal 9.0.0. We cannot use that now, because child classes already use
|
||||
// it and PHP 5 would consider that conflicts.
|
||||
unset($vars['stringTranslation']);
|
||||
unset($vars['typedDataManager']);
|
||||
|
||||
return array_keys($vars);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -76,6 +76,7 @@ class Error {
|
|||
'%line' => $caller['line'],
|
||||
'severity_level' => static::ERROR,
|
||||
'backtrace' => $backtrace,
|
||||
'backtrace_string' => $exception->getTraceAsString(),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -179,7 +180,12 @@ class Error {
|
|||
}
|
||||
}
|
||||
|
||||
$return .= $call['function'] . '(' . implode(', ', $call['args']) . ")\n";
|
||||
$line = '';
|
||||
if (isset($trace['line'])) {
|
||||
$line = " (Line: {$trace['line']})";
|
||||
}
|
||||
|
||||
$return .= $call['function'] . '(' . implode(', ', $call['args']) . ")$line\n";
|
||||
}
|
||||
|
||||
return $return;
|
||||
|
|
|
@ -167,9 +167,10 @@
|
|||
this.windowHeight = 0;
|
||||
|
||||
/**
|
||||
* Check this table's settings to see if there are parent relationships in
|
||||
* this table. For efficiency, large sections of code can be skipped if we
|
||||
* don't need to track horizontal movement and indentations.
|
||||
* Check this table's settings for parent relationships.
|
||||
*
|
||||
* For efficiency, large sections of code can be skipped if we don't need to
|
||||
* track horizontal movement and indentations.
|
||||
*
|
||||
* @type {bool}
|
||||
*/
|
||||
|
@ -299,8 +300,10 @@
|
|||
* In order to adjust the colspan instead of hiding them altogether.
|
||||
*
|
||||
* @param {number} columnIndex
|
||||
* The column index to add colspan class to.
|
||||
*
|
||||
* @return {function}
|
||||
* Function to add colspan class.
|
||||
*/
|
||||
Drupal.tableDrag.prototype.addColspanClass = function (columnIndex) {
|
||||
return function () {
|
||||
|
@ -409,9 +412,12 @@
|
|||
* Find the target used within a particular row and group.
|
||||
*
|
||||
* @param {string} group
|
||||
* Group selector.
|
||||
* @param {HTMLElement} row
|
||||
* The row HTML element.
|
||||
*
|
||||
* @return {object}
|
||||
* The table row settings.
|
||||
*/
|
||||
Drupal.tableDrag.prototype.rowSettings = function (group, row) {
|
||||
var field = $(row).find('.' + group);
|
||||
|
@ -437,11 +443,12 @@
|
|||
* Take an item and add event handlers to make it become draggable.
|
||||
*
|
||||
* @param {HTMLElement} item
|
||||
* The item to add event handlers to.
|
||||
*/
|
||||
Drupal.tableDrag.prototype.makeDraggable = function (item) {
|
||||
var self = this;
|
||||
var $item = $(item);
|
||||
// Add a class to the title link
|
||||
// Add a class to the title link.
|
||||
$item.find('td:first-of-type').find('a').addClass('menu-item__link');
|
||||
// Create the handle.
|
||||
var handle = $('<a href="#" class="tabledrag-handle"><div class="handle"> </div></a>').attr('title', Drupal.t('Drag to re-order'));
|
||||
|
@ -676,9 +683,12 @@
|
|||
* Pointer movement handler, bound to document.
|
||||
*
|
||||
* @param {jQuery.Event} event
|
||||
* The pointer event.
|
||||
* @param {Drupal.tableDrag} self
|
||||
* The tableDrag instance.
|
||||
*
|
||||
* @return {bool|undefined}
|
||||
* Undefined if no dragObject is defined, false otherwise.
|
||||
*/
|
||||
Drupal.tableDrag.prototype.dragRow = function (event, self) {
|
||||
if (self.dragObject) {
|
||||
|
@ -737,7 +747,9 @@
|
|||
* Pointerup behavior.
|
||||
*
|
||||
* @param {jQuery.Event} event
|
||||
* The pointer event.
|
||||
* @param {Drupal.tableDrag} self
|
||||
* The tableDrag instance.
|
||||
*/
|
||||
Drupal.tableDrag.prototype.dropRow = function (event, self) {
|
||||
var droppedRow;
|
||||
|
@ -798,8 +810,10 @@
|
|||
* Get the coordinates from the event (allowing for browser differences).
|
||||
*
|
||||
* @param {jQuery.Event} event
|
||||
* The pointer event.
|
||||
*
|
||||
* @return {{x: number, y: number}}
|
||||
* @return {object}
|
||||
* An object with `x` and `y` keys indicating the position.
|
||||
*/
|
||||
Drupal.tableDrag.prototype.pointerCoords = function (event) {
|
||||
if (event.pageX || event.pageY) {
|
||||
|
@ -818,9 +832,12 @@
|
|||
* element. To do this we need the element's position and the target position.
|
||||
*
|
||||
* @param {HTMLElement} target
|
||||
* The target HTML element.
|
||||
* @param {jQuery.Event} event
|
||||
* The pointer event.
|
||||
*
|
||||
* @return {{x: number, y: number}}
|
||||
* @return {object}
|
||||
* An object with `x` and `y` keys indicating the position.
|
||||
*/
|
||||
Drupal.tableDrag.prototype.getPointerOffset = function (target, event) {
|
||||
var docPos = $(target).offset();
|
||||
|
@ -839,6 +856,7 @@
|
|||
* The y coordinate of the mouse on the page (not the screen).
|
||||
*
|
||||
* @return {*}
|
||||
* The drop target row, if found.
|
||||
*/
|
||||
Drupal.tableDrag.prototype.findDropTargetRow = function (x, y) {
|
||||
var rows = $(this.table.tBodies[0].rows).not(':hidden');
|
||||
|
@ -1055,8 +1073,11 @@
|
|||
* may have had.
|
||||
*
|
||||
* @param {HTMLElement} sourceRow
|
||||
* The element for the source row.
|
||||
* @param {HTMLElement} targetRow
|
||||
* The element for the target row.
|
||||
* @param {string} group
|
||||
* The group selector.
|
||||
*/
|
||||
Drupal.tableDrag.prototype.copyDragClasses = function (sourceRow, targetRow, group) {
|
||||
var sourceElement = $(sourceRow).find('.' + group);
|
||||
|
@ -1067,8 +1088,13 @@
|
|||
};
|
||||
|
||||
/**
|
||||
* Check the suggested scroll of the table.
|
||||
*
|
||||
* @param {number} cursorY
|
||||
* The Y position of the cursor.
|
||||
*
|
||||
* @return {number}
|
||||
* The suggested scroll.
|
||||
*/
|
||||
Drupal.tableDrag.prototype.checkScroll = function (cursorY) {
|
||||
var de = document.documentElement;
|
||||
|
@ -1099,7 +1125,10 @@
|
|||
};
|
||||
|
||||
/**
|
||||
* Set the scroll for the table.
|
||||
*
|
||||
* @param {number} scrollAmount
|
||||
* The amount of scroll to apply to the window.
|
||||
*/
|
||||
Drupal.tableDrag.prototype.setScroll = function (scrollAmount) {
|
||||
var self = this;
|
||||
|
@ -1131,7 +1160,8 @@
|
|||
/**
|
||||
* Stub function. Allows a custom handler when a row begins dragging.
|
||||
*
|
||||
* @return {?bool}
|
||||
* @return {null}
|
||||
* Returns null when the stub function is used.
|
||||
*/
|
||||
Drupal.tableDrag.prototype.onDrag = function () {
|
||||
return null;
|
||||
|
@ -1140,7 +1170,8 @@
|
|||
/**
|
||||
* Stub function. Allows a custom handler when a row is dropped.
|
||||
*
|
||||
* @return {?bool}
|
||||
* @return {null}
|
||||
* Returns null when the stub function is used.
|
||||
*/
|
||||
Drupal.tableDrag.prototype.onDrop = function () {
|
||||
return null;
|
||||
|
@ -1194,6 +1225,7 @@
|
|||
* relationships.
|
||||
*
|
||||
* @return {Array}
|
||||
* An array of children of the row.
|
||||
*/
|
||||
Drupal.tableDrag.prototype.row.prototype.findChildren = function (addClasses) {
|
||||
var parentIndentation = this.indents;
|
||||
|
@ -1241,6 +1273,7 @@
|
|||
* DOM object for the row being considered for swapping.
|
||||
*
|
||||
* @return {bool}
|
||||
* Whether the swap is a valid swap or not.
|
||||
*/
|
||||
Drupal.tableDrag.prototype.row.prototype.isValidSwap = function (row) {
|
||||
var $row = $(row);
|
||||
|
@ -1303,7 +1336,9 @@
|
|||
* DOM object for the row after the tested position
|
||||
* (or null for last position in the table).
|
||||
*
|
||||
* @return {{min: number, max: number}}
|
||||
* @return {object}
|
||||
* An object with the keys `min` and `max` to indicate the valid indent
|
||||
* interval.
|
||||
*/
|
||||
Drupal.tableDrag.prototype.row.prototype.validIndentInterval = function (prevRow, nextRow) {
|
||||
var $prevRow = $(prevRow);
|
||||
|
@ -1343,6 +1378,7 @@
|
|||
* indentation level for the row.
|
||||
*
|
||||
* @return {number}
|
||||
* The number of indentations applied.
|
||||
*/
|
||||
Drupal.tableDrag.prototype.row.prototype.indent = function (indentDiff) {
|
||||
var $group = $(this.group);
|
||||
|
@ -1390,6 +1426,7 @@
|
|||
* The field settings we're using to identify what constitutes a sibling.
|
||||
*
|
||||
* @return {Array}
|
||||
* An array of siblings.
|
||||
*/
|
||||
Drupal.tableDrag.prototype.row.prototype.findSiblings = function (rowSettings) {
|
||||
var siblings = [];
|
||||
|
@ -1459,7 +1496,8 @@
|
|||
/**
|
||||
* Stub function. Allows a custom handler when a row is indented.
|
||||
*
|
||||
* @return {?bool}
|
||||
* @return {null}
|
||||
* Returns null when the stub function is used.
|
||||
*/
|
||||
Drupal.tableDrag.prototype.row.prototype.onIndent = function () {
|
||||
return null;
|
||||
|
@ -1469,8 +1507,10 @@
|
|||
* Stub function. Allows a custom handler when a row is swapped.
|
||||
*
|
||||
* @param {HTMLElement} swappedRow
|
||||
* The element for the swapped row.
|
||||
*
|
||||
* @return {?bool}
|
||||
* @return {null}
|
||||
* Returns null when the stub function is used.
|
||||
*/
|
||||
Drupal.tableDrag.prototype.row.prototype.onSwap = function (swappedRow) {
|
||||
return null;
|
||||
|
@ -1480,6 +1520,7 @@
|
|||
|
||||
/**
|
||||
* @return {string}
|
||||
* Markup for the marker.
|
||||
*/
|
||||
tableDragChangedMarker: function () {
|
||||
return '<abbr class="warning tabledrag-changed" title="' + Drupal.t('Changed') + '">*</abbr>';
|
||||
|
@ -1487,6 +1528,7 @@
|
|||
|
||||
/**
|
||||
* @return {string}
|
||||
* Markup for the indentation.
|
||||
*/
|
||||
tableDragIndentation: function () {
|
||||
return '<div class="js-indentation indentation"> </div>';
|
||||
|
@ -1494,6 +1536,7 @@
|
|||
|
||||
/**
|
||||
* @return {string}
|
||||
* Markup for the warning.
|
||||
*/
|
||||
tableDragChangedWarning: function () {
|
||||
return '<div class="tabledrag-changed-warning messages messages--warning" role="alert">' + Drupal.theme('tableDragChangedMarker') + ' ' + Drupal.t('You have unsaved changes.') + '</div>';
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Hooks provided by the Actions module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @addtogroup hooks
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Executes code after an action is deleted.
|
||||
*
|
||||
* @param $aid
|
||||
* The action ID.
|
||||
*/
|
||||
function hook_action_delete($aid) {
|
||||
db_delete('actions_assignments')
|
||||
->condition('aid', $aid)
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup hooks".
|
||||
*/
|
|
@ -88,7 +88,10 @@ class BookBreadcrumbBuilder implements BreadcrumbBuilderInterface {
|
|||
$depth = 1;
|
||||
while (!empty($book['p' . ($depth + 1)])) {
|
||||
if (!empty($parent_books[$book['p' . $depth]]) && ($parent_book = $parent_books[$book['p' . $depth]])) {
|
||||
if ($parent_book->access('view', $this->account)) {
|
||||
$access = $parent_book->access('view', $this->account, TRUE);
|
||||
$breadcrumb->addCacheableDependency($access);
|
||||
if ($access->isAllowed()) {
|
||||
$breadcrumb->addCacheableDependency($parent_book);
|
||||
$links[] = Link::createFromRoute($parent_book->label(), 'entity.node.canonical', array('node' => $parent_book->id()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -397,7 +397,7 @@ class BookManager implements BookManagerInterface {
|
|||
foreach ($tree as $data) {
|
||||
if ($data['link']['depth'] > $depth_limit) {
|
||||
// Don't iterate through any links on this level.
|
||||
break;
|
||||
return;
|
||||
}
|
||||
if (!in_array($data['link']['nid'], $exclude)) {
|
||||
$nids[] = $data['link']['nid'];
|
||||
|
@ -408,7 +408,8 @@ class BookManager implements BookManagerInterface {
|
|||
|
||||
foreach ($tree as $data) {
|
||||
$nid = $data['link']['nid'];
|
||||
if (in_array($nid, $exclude)) {
|
||||
// Check for excluded or missing node.
|
||||
if (empty($nodes[$nid])) {
|
||||
continue;
|
||||
}
|
||||
$toc[$nid] = $indent . ' ' . Unicode::truncate($nodes[$nid]->label(), 30, TRUE, TRUE);
|
||||
|
|
|
@ -11,6 +11,7 @@ use Drupal\book\BookManagerInterface;
|
|||
use Drupal\Core\Entity\ContentEntityForm;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
|
@ -91,7 +92,8 @@ class BookOutlineForm extends ContentEntityForm {
|
|||
protected function actions(array $form, FormStateInterface $form_state) {
|
||||
$actions = parent::actions($form, $form_state);
|
||||
$actions['submit']['#value'] = $this->entity->book['original_bid'] ? $this->t('Update book outline') : $this->t('Add to book outline');
|
||||
$actions['delete']['#value'] = $this->t('Remove from book outline');
|
||||
$actions['delete']['#title'] = $this->t('Remove from book outline');
|
||||
$actions['delete']['#url'] = new Url('entity.node.book_remove_form', ['node' => $this->entity->book['nid']]);
|
||||
$actions['delete']['#access'] = $this->bookManager->checkNodeIsRemovable($this->entity);
|
||||
return $actions;
|
||||
}
|
||||
|
@ -126,11 +128,4 @@ class BookOutlineForm extends ContentEntityForm {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function delete(array $form, FormStateInterface $form_state) {
|
||||
$form_state->setRedirectUrl($this->entity->urlInfo('book-remove-form'));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
209
core/modules/book/src/Tests/BookBreadcrumbTest.php
Normal file
209
core/modules/book/src/Tests/BookBreadcrumbTest.php
Normal file
|
@ -0,0 +1,209 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\book\Tests\BookBreadcrumbTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\book\Tests;
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Create a book, add pages, and test book interface.
|
||||
*
|
||||
* @group book
|
||||
*/
|
||||
class BookBreadcrumbTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('book', 'block', 'book_breadcrumb_test');
|
||||
|
||||
/**
|
||||
* A book node.
|
||||
*
|
||||
* @var \Drupal\node\NodeInterface
|
||||
*/
|
||||
protected $book;
|
||||
|
||||
/**
|
||||
* A user with permission to create and edit books.
|
||||
*
|
||||
* @var \Drupal\user\Entity\User
|
||||
*/
|
||||
protected $bookAuthor;
|
||||
|
||||
/**
|
||||
* A user without the 'node test view' permission.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $webUserWithoutNodeAccess;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->drupalPlaceBlock('system_breadcrumb_block');
|
||||
$this->drupalPlaceBlock('page_title_block');
|
||||
|
||||
// Create users.
|
||||
$this->bookAuthor = $this->drupalCreateUser(array('create new books', 'create book content', 'edit own book content', 'add content to books'));
|
||||
$this->adminUser = $this->drupalCreateUser(array('create new books', 'create book content', 'edit any book content', 'delete any book content', 'add content to books', 'administer blocks', 'administer permissions', 'administer book outlines', 'administer content types', 'administer site configuration'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new book with a page hierarchy.
|
||||
*
|
||||
* @return \Drupal\node\NodeInterface[]
|
||||
* The created book nodes.
|
||||
*/
|
||||
protected function createBreadcrumbBook() {
|
||||
// Create new book.
|
||||
$this->drupalLogin($this->bookAuthor);
|
||||
|
||||
$this->book = $this->createBookNode('new');
|
||||
$book = $this->book;
|
||||
|
||||
/*
|
||||
* Add page hierarchy to book.
|
||||
* Book
|
||||
* |- Node 0
|
||||
* |- Node 1
|
||||
* |- Node 2
|
||||
* |- Node 3
|
||||
* |- Node 4
|
||||
* |- Node 5
|
||||
* |- Node 6
|
||||
*/
|
||||
$nodes = array();
|
||||
$nodes[0] = $this->createBookNode($book->id());
|
||||
$nodes[1] = $this->createBookNode($book->id(), $nodes[0]->id());
|
||||
$nodes[2] = $this->createBookNode($book->id(), $nodes[0]->id());
|
||||
$nodes[3] = $this->createBookNode($book->id(), $nodes[2]->id());
|
||||
$nodes[4] = $this->createBookNode($book->id(), $nodes[3]->id());
|
||||
$nodes[5] = $this->createBookNode($book->id(), $nodes[4]->id());
|
||||
$nodes[6] = $this->createBookNode($book->id());
|
||||
|
||||
$this->drupalLogout();
|
||||
|
||||
return $nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a book node.
|
||||
*
|
||||
* @param int|string $book_nid
|
||||
* A book node ID or set to 'new' to create a new book.
|
||||
* @param int|null $parent
|
||||
* (optional) Parent book reference ID. Defaults to NULL.
|
||||
*
|
||||
* @return \Drupal\node\NodeInterface
|
||||
* The created node.
|
||||
*/
|
||||
protected function createBookNode($book_nid, $parent = NULL) {
|
||||
// $number does not use drupal_static as it should not be reset since it
|
||||
// uniquely identifies each call to createBookNode(). It is used to ensure
|
||||
// that when sorted nodes stay in same order.
|
||||
static $number = 0;
|
||||
|
||||
$edit = array();
|
||||
$edit['title[0][value]'] = str_pad($number, 2, '0', STR_PAD_LEFT) . ' - SimpleTest test node ' . $this->randomMachineName(10);
|
||||
$edit['body[0][value]'] = 'SimpleTest test body ' . $this->randomMachineName(32) . ' ' . $this->randomMachineName(32);
|
||||
$edit['book[bid]'] = $book_nid;
|
||||
|
||||
if ($parent !== NULL) {
|
||||
$this->drupalPostForm('node/add/book', $edit, t('Change book (update list of parents)'));
|
||||
|
||||
$edit['book[pid]'] = $parent;
|
||||
$this->drupalPostForm(NULL, $edit, t('Save'));
|
||||
// Make sure the parent was flagged as having children.
|
||||
$parent_node = \Drupal::entityManager()->getStorage('node')->loadUnchanged($parent);
|
||||
$this->assertFalse(empty($parent_node->book['has_children']), 'Parent node is marked as having children');
|
||||
}
|
||||
else {
|
||||
$this->drupalPostForm('node/add/book', $edit, t('Save'));
|
||||
}
|
||||
|
||||
// Check to make sure the book node was created.
|
||||
$node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
|
||||
$this->assertNotNull(($node === FALSE ? NULL : $node), 'Book node found in database.');
|
||||
$number++;
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the breadcrumb is updated when book content changes.
|
||||
*/
|
||||
public function testBreadcrumbTitleUpdates() {
|
||||
// Create a new book.
|
||||
$nodes = $this->createBreadcrumbBook();
|
||||
$book = $this->book;
|
||||
|
||||
$this->drupalLogin($this->bookAuthor);
|
||||
|
||||
$this->drupalGet($nodes[4]->toUrl());
|
||||
// Fetch each node title in the current breadcrumb.
|
||||
$links = $this->xpath('//nav[@class="breadcrumb"]/ol/li/a');
|
||||
$got_breadcrumb = array();
|
||||
foreach ($links as $link) {
|
||||
$got_breadcrumb[] = (string) $link;
|
||||
}
|
||||
// Home link and four parent book nodes should be in the breadcrumb.
|
||||
$this->assertEqual(5, count($got_breadcrumb));
|
||||
$this->assertEqual($nodes[3]->getTitle(), end($got_breadcrumb));
|
||||
$edit = [
|
||||
'title[0][value]' => 'Updated node5 title',
|
||||
];
|
||||
$this->drupalPostForm($nodes[3]->toUrl('edit-form'), $edit, 'Save');
|
||||
$this->drupalGet($nodes[4]->toUrl());
|
||||
// Fetch each node title in the current breadcrumb.
|
||||
$links = $this->xpath('//nav[@class="breadcrumb"]/ol/li/a');
|
||||
$got_breadcrumb = array();
|
||||
foreach ($links as $link) {
|
||||
$got_breadcrumb[] = (string) $link;
|
||||
}
|
||||
$this->assertEqual(5, count($got_breadcrumb));
|
||||
$this->assertEqual($edit['title[0][value]'], end($got_breadcrumb));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the breadcrumb is updated when book access changes.
|
||||
*/
|
||||
public function testBreadcrumbAccessUpdates() {
|
||||
// Create a new book.
|
||||
$nodes = $this->createBreadcrumbBook();
|
||||
$this->drupalLogin($this->bookAuthor);
|
||||
$edit = [
|
||||
'title[0][value]' => "you can't see me",
|
||||
];
|
||||
$this->drupalPostForm($nodes[3]->toUrl('edit-form'), $edit, 'Save');
|
||||
$this->drupalGet($nodes[4]->toUrl());
|
||||
$links = $this->xpath('//nav[@class="breadcrumb"]/ol/li/a');
|
||||
$got_breadcrumb = array();
|
||||
foreach ($links as $link) {
|
||||
$got_breadcrumb[] = (string) $link;
|
||||
}
|
||||
$this->assertEqual(5, count($got_breadcrumb));
|
||||
$this->assertEqual($edit['title[0][value]'], end($got_breadcrumb));
|
||||
$config = $this->container->get('config.factory')->getEditable('book_breadcrumb_test.settings');
|
||||
$config->set('hide', TRUE)->save();
|
||||
$this->drupalGet($nodes[4]->toUrl());
|
||||
$links = $this->xpath('//nav[@class="breadcrumb"]/ol/li/a');
|
||||
$got_breadcrumb = array();
|
||||
foreach ($links as $link) {
|
||||
$got_breadcrumb[] = (string) $link;
|
||||
}
|
||||
$this->assertEqual(4, count($got_breadcrumb));
|
||||
$this->assertEqual($nodes[2]->getTitle(), end($got_breadcrumb));
|
||||
$this->drupalGet($nodes[3]->toUrl());
|
||||
$this->assertResponse(403);
|
||||
}
|
||||
|
||||
}
|
|
@ -443,6 +443,60 @@ class BookTest extends WebTestBase {
|
|||
$this->assertNoText($nodes[0]->label(), 'No links to individual book pages are displayed.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests BookManager::getTableOfContents().
|
||||
*/
|
||||
public function testGetTableOfContents() {
|
||||
// Create new book.
|
||||
$nodes = $this->createBook();
|
||||
$book = $this->book;
|
||||
|
||||
$this->drupalLogin($this->bookAuthor);
|
||||
|
||||
/*
|
||||
* Add Node 5 under Node 2.
|
||||
* Add Node 6, 7, 8, 9, 10, 11 under Node 3.
|
||||
* Book
|
||||
* |- Node 0
|
||||
* |- Node 1
|
||||
* |- Node 2
|
||||
* |- Node 5
|
||||
* |- Node 3
|
||||
* |- Node 6
|
||||
* |- Node 7
|
||||
* |- Node 8
|
||||
* |- Node 9
|
||||
* |- Node 10
|
||||
* |- Node 11
|
||||
* |- Node 4
|
||||
*/
|
||||
foreach ([5 => 2, 6 => 3, 7 => 6, 8 => 7, 9 => 8, 10 => 9, 11 => 10] as $child => $parent) {
|
||||
$nodes[$child] = $this->createBookNode($book->id(), $nodes[$parent]->id());
|
||||
}
|
||||
$this->drupalGet($nodes[0]->toUrl('edit-form'));
|
||||
// Snice Node 0 has children 2 levels deep, nodes 10 and 11 should not
|
||||
// appear in the selector.
|
||||
$this->assertNoOption('edit-book-pid', $nodes[10]->id());
|
||||
$this->assertNoOption('edit-book-pid', $nodes[11]->id());
|
||||
// Node 9 should be available as an option.
|
||||
$this->assertOption('edit-book-pid', $nodes[9]->id());
|
||||
|
||||
// Get a shallow set of options.
|
||||
/** @var \Drupal\book\BookManagerInterface $manager */
|
||||
$manager = $this->container->get('book.manager');
|
||||
$options = $manager->getTableOfContents($book->id(), 3);
|
||||
$expected_nids = [$book->id(), $nodes[0]->id(), $nodes[1]->id(), $nodes[2]->id(), $nodes[3]->id(), $nodes[6]->id(), $nodes[4]->id()];
|
||||
$this->assertEqual(count($options), count($expected_nids));
|
||||
$diff = array_diff($expected_nids, array_keys($options));
|
||||
$this->assertTrue(empty($diff), 'Found all expected option keys');
|
||||
// Exclude Node 3.
|
||||
$options = $manager->getTableOfContents($book->id(), 3, array($nodes[3]->id()));
|
||||
$expected_nids = array($book->id(), $nodes[0]->id(), $nodes[1]->id(), $nodes[2]->id(), $nodes[4]->id());
|
||||
$this->assertEqual(count($options), count($expected_nids));
|
||||
$diff = array_diff($expected_nids, array_keys($options));
|
||||
$this->assertTrue(empty($diff), 'Found all expected option keys after excluding Node 3');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the book navigation block when an access module is installed.
|
||||
*/
|
||||
|
@ -564,6 +618,7 @@ class BookTest extends WebTestBase {
|
|||
$this->drupalGet('node/' . $empty_book->id() . '/outline');
|
||||
$this->assertRaw(t('Book outline'));
|
||||
$this->assertOptionSelected('edit-book-bid', 0, 'Node does not belong to a book');
|
||||
$this->assertNoLink(t('Remove from book outline'));
|
||||
|
||||
$edit = array();
|
||||
$edit['book[bid]'] = '1';
|
||||
|
@ -583,6 +638,8 @@ class BookTest extends WebTestBase {
|
|||
$this->drupalLogin($this->adminUser);
|
||||
$this->drupalGet('node/' . $book->id() . '/outline');
|
||||
$this->assertRaw(t('Book outline'));
|
||||
$this->clickLink(t('Remove from book outline'));
|
||||
$this->assertRaw(t('Are you sure you want to remove %title from the book hierarchy?', array('%title' => $book->label())));
|
||||
|
||||
// Create a new node and set the book after the node was created.
|
||||
$node = $this->drupalCreateNode(array('type' => 'book'));
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
name: 'Book module breadcrumb tests'
|
||||
type: module
|
||||
description: 'Support module for book module breadcrumb testing.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
core: 8.x
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Test module for testing the book module breadcrumb.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Access\AccessResultForbidden;
|
||||
use Drupal\Core\Access\AccessResultNeutral;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\node\NodeInterface;
|
||||
|
||||
/**
|
||||
* Implements hook_node_access().
|
||||
*/
|
||||
function book_breadcrumb_test_node_access(NodeInterface $node, $operation, AccountInterface $account) {
|
||||
$config = \Drupal::config('book_breadcrumb_test.settings');
|
||||
if ($config->get('hide') && $node->getTitle() == "you can't see me" && $operation == 'view') {
|
||||
$access = new AccessResultForbidden();
|
||||
}
|
||||
else {
|
||||
$access = new AccessResultNeutral();
|
||||
}
|
||||
$access->addCacheableDependency($config);
|
||||
$access->addCacheableDependency($node);
|
||||
return $access;
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
'hide': false
|
|
@ -0,0 +1,9 @@
|
|||
# Schema for the configuration files of the book_breadcrumb_test module.
|
||||
|
||||
book_breadcrumb_test.settings:
|
||||
type: config_object
|
||||
label: 'Book Breadcrumb Test module settings'
|
||||
mapping:
|
||||
hide:
|
||||
type: boolean
|
||||
label: 'Setting for hiding content'
|
|
@ -18,15 +18,20 @@
|
|||
for (var attrIndex = 0; attrIndex < domElement.attributes.length; attrIndex++) {
|
||||
attribute = domElement.attributes.item(attrIndex);
|
||||
attributeName = attribute.nodeName.toLowerCase();
|
||||
// Don't consider data-cke-saved- attributes; they're just there to work
|
||||
// around browser quirks.
|
||||
if (attributeName.substring(0, 15) === 'data-cke-saved-') {
|
||||
// Ignore data-cke-* attributes; they're CKEditor internals.
|
||||
if (attributeName.indexOf('data-cke-') === 0) {
|
||||
continue;
|
||||
}
|
||||
// Store the value for this attribute, unless there's a data-cke-saved-
|
||||
// alternative for it, which will contain the quirk-free, original value.
|
||||
parsedAttributes[attributeName] = element.data('cke-saved-' + attributeName) || attribute.nodeValue;
|
||||
}
|
||||
|
||||
// Remove any cke_* classes.
|
||||
if (parsedAttributes.class) {
|
||||
parsedAttributes.class = CKEDITOR.tools.trim(parsedAttributes.class.replace(/cke_\S+/, ''));
|
||||
}
|
||||
|
||||
return parsedAttributes;
|
||||
}
|
||||
|
||||
|
|
|
@ -335,7 +335,7 @@ class CKEditor extends EditorBase implements ContainerFactoryPluginInterface {
|
|||
// Collect languages included with CKEditor based on file listing.
|
||||
$files = scandir('core/assets/vendor/ckeditor/lang');
|
||||
foreach ($files as $file) {
|
||||
if ($file[0] !== '.' && fnmatch('*.js', $file)) {
|
||||
if ($file[0] !== '.' && preg_match('/\.js$/', $file)) {
|
||||
$langcode = basename($file, '.js');
|
||||
$langcodes[$langcode] = $langcode;
|
||||
}
|
||||
|
|
|
@ -270,6 +270,7 @@ class CKEditorTest extends KernelTestBase {
|
|||
$expected[] = file_url_transform_relative(file_create_url('core/themes/bartik/css/base/elements.css'));
|
||||
$expected[] = file_url_transform_relative(file_create_url('core/themes/bartik/css/components/captions.css'));
|
||||
$expected[] = file_url_transform_relative(file_create_url('core/themes/bartik/css/components/table.css'));
|
||||
$expected[] = file_url_transform_relative(file_create_url('core/themes/bartik/css/components/text-formatted.css'));
|
||||
$this->assertIdentical($expected, $this->ckeditor->buildContentsCssJSSetting($editor), '"contentsCss" configuration part of JS settings built correctly while a theme providing a CKEditor stylesheet exists.');
|
||||
}
|
||||
|
||||
|
|
|
@ -229,7 +229,7 @@ class ConfigDependencyTest extends EntityUnitTestBase {
|
|||
|
||||
// Set a more complicated test where dependencies will be fixed.
|
||||
\Drupal::state()->set('config_test.fix_dependencies', array($entity1->getConfigDependencyName()));
|
||||
|
||||
\Drupal::state()->set('config_test.on_dependency_removal_called', []);
|
||||
// Entity1 will be deleted because it depends on node.
|
||||
$entity1 = $storage->create(
|
||||
array(
|
||||
|
@ -259,7 +259,8 @@ class ConfigDependencyTest extends EntityUnitTestBase {
|
|||
$entity2->save();
|
||||
|
||||
// Entity3 will be unchanged because it is dependent on Entity2 which can
|
||||
// be fixed.
|
||||
// be fixed. The ConfigEntityInterface::onDependencyRemoval() method will
|
||||
// not be called for this entity.
|
||||
$entity3 = $storage->create(
|
||||
array(
|
||||
'id' => 'entity3',
|
||||
|
@ -295,6 +296,10 @@ class ConfigDependencyTest extends EntityUnitTestBase {
|
|||
$this->assertEqual($entity3->uuid(), reset($config_entities['unchanged'])->uuid(), 'Entity 3 is not changed.');
|
||||
$this->assertEqual($entity4->uuid(), $config_entities['delete'][1]->uuid(), 'Entity 4 will be deleted.');
|
||||
|
||||
$called = \Drupal::state()->get('config_test.on_dependency_removal_called', []);
|
||||
$this->assertFalse(in_array($entity3->id(), $called), 'ConfigEntityInterface::onDependencyRemoval() is not called for entity 3.');
|
||||
$this->assertIdentical(['entity1', 'entity2', 'entity4'], $called, 'The most dependent entites have ConfigEntityInterface::onDependencyRemoval() called first.');
|
||||
|
||||
// Perform the uninstall.
|
||||
$config_manager->uninstall('module', 'node');
|
||||
|
||||
|
|
|
@ -70,6 +70,12 @@ class FileStorageTest extends ConfigStorageTestBase {
|
|||
$config_files = $this->storage->listAll();
|
||||
$this->assertIdentical($config_files, $expected_files, 'Relative path, two config files found.');
|
||||
|
||||
// @todo https://www.drupal.org/node/2666954 FileStorage::listAll() is
|
||||
// case-sensitive. However, \Drupal\Core\Config\DatabaseStorage::listAll()
|
||||
// is case-insensitive.
|
||||
$this->assertIdentical(['system.performance'], $this->storage->listAll('system'), 'The FileStorage::listAll() with prefix works.');
|
||||
$this->assertIdentical([], $this->storage->listAll('System'), 'The FileStorage::listAll() is case sensitive.');
|
||||
|
||||
// Initialize FileStorage with absolute file path.
|
||||
$absolute_path = realpath($this->directory);
|
||||
$storage_absolute_path = new FileStorage($absolute_path);
|
||||
|
|
|
@ -122,6 +122,11 @@ class ConfigTest extends ConfigEntityBase implements ConfigTestInterface {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function onDependencyRemoval(array $dependencies) {
|
||||
// Record which entities have this method called on.
|
||||
$called = \Drupal::state()->get('config_test.on_dependency_removal_called', []);
|
||||
$called[] = $this->id();
|
||||
\Drupal::state()->set('config_test.on_dependency_removal_called', $called);
|
||||
|
||||
$changed = parent::onDependencyRemoval($dependencies);
|
||||
if (!isset($this->dependencies['enforced']['config'])) {
|
||||
return $changed;
|
||||
|
|
|
@ -19,11 +19,14 @@ use Drupal\Core\Render\Element;
|
|||
*
|
||||
* @param \Drupal\Core\Field\FieldDefinitionInterface $field
|
||||
* A field definition object.
|
||||
* @param string $element_name
|
||||
* (optional) The element name, which is added to drupalSettings so that
|
||||
* javascript can manipulate the form element.
|
||||
*
|
||||
* @return array
|
||||
* A form element to configure field synchronization.
|
||||
*/
|
||||
function content_translation_field_sync_widget(FieldDefinitionInterface $field) {
|
||||
function content_translation_field_sync_widget(FieldDefinitionInterface $field, $element_name = 'third_party_settings[content_translation][translation_sync]') {
|
||||
// No way to store field sync information on this field.
|
||||
if (!($field instanceof ThirdPartySettingsInterface)) {
|
||||
return array();
|
||||
|
@ -33,15 +36,18 @@ function content_translation_field_sync_widget(FieldDefinitionInterface $field)
|
|||
$definition = \Drupal::service('plugin.manager.field.field_type')->getDefinition($field->getType());
|
||||
$column_groups = $definition['column_groups'];
|
||||
if (!empty($column_groups) && count($column_groups) > 1) {
|
||||
$options = array();
|
||||
$default = array();
|
||||
$options = [];
|
||||
$default = [];
|
||||
$require_all_groups_for_translation = [];
|
||||
|
||||
foreach ($column_groups as $group => $info) {
|
||||
$options[$group] = $info['label'];
|
||||
$default[$group] = !empty($info['translatable']) ? $group : FALSE;
|
||||
if (!empty($info['require_all_groups_for_translation'])) {
|
||||
$require_all_groups_for_translation[] = $group;
|
||||
}
|
||||
}
|
||||
|
||||
$settings = array('dependent_selectors' => array('instance[third_party_settings][content_translation][translation_sync]' => array('file')));
|
||||
$default = $field->getThirdPartySetting('content_translation', 'translation_sync', $default);
|
||||
|
||||
$element = array(
|
||||
|
@ -49,15 +55,19 @@ function content_translation_field_sync_widget(FieldDefinitionInterface $field)
|
|||
'#title' => t('Translatable elements'),
|
||||
'#options' => $options,
|
||||
'#default_value' => $default,
|
||||
'#attached' => array(
|
||||
'library' => array(
|
||||
'content_translation/drupal.content_translation.admin',
|
||||
),
|
||||
'drupalSettings' => [
|
||||
'contentTranslationDependentOptions' => $settings,
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if ($require_all_groups_for_translation) {
|
||||
// The actual checkboxes are sometimes rendered separately and the parent
|
||||
// element is ignored. Attach to the first option to ensure that this
|
||||
// does not get lost.
|
||||
$element[key($options)]['#attached']['drupalSettings']['contentTranslationDependentOptions'] = [
|
||||
'dependent_selectors' => [
|
||||
$element_name => $require_all_groups_for_translation
|
||||
],
|
||||
];
|
||||
$element[key($options)]['#attached']['library'][] = 'content_translation/drupal.content_translation.admin';
|
||||
}
|
||||
}
|
||||
|
||||
return $element;
|
||||
|
@ -82,7 +92,6 @@ function _content_translation_form_language_content_settings_form_alter(array &$
|
|||
|
||||
$form['#attached']['library'][] = 'content_translation/drupal.content_translation.admin';
|
||||
|
||||
$dependent_options_settings = array();
|
||||
$entity_manager = Drupal::entityManager();
|
||||
foreach ($form['#labels'] as $entity_type_id => $label) {
|
||||
$entity_type = $entity_manager->getDefinition($entity_type_id);
|
||||
|
@ -110,13 +119,9 @@ function _content_translation_form_language_content_settings_form_alter(array &$
|
|||
'#default_value' => $definition->isTranslatable(),
|
||||
);
|
||||
// Display the column translatability configuration widget.
|
||||
$column_element = content_translation_field_sync_widget($definition);
|
||||
$column_element = content_translation_field_sync_widget($definition, "settings[{$entity_type_id}][{$bundle}][columns][{$field_name}]");
|
||||
if ($column_element) {
|
||||
$form['settings'][$entity_type_id][$bundle]['columns'][$field_name] = $column_element;
|
||||
// @todo This should not concern only files.
|
||||
if (isset($column_element['#options']['file'])) {
|
||||
$dependent_options_settings["settings[{$entity_type_id}][{$bundle}][columns][{$field_name}]"] = array('file');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -132,8 +137,6 @@ function _content_translation_form_language_content_settings_form_alter(array &$
|
|||
}
|
||||
}
|
||||
|
||||
$settings = array('dependent_selectors' => $dependent_options_settings);
|
||||
$form['#attached']['drupalSettings']['contentTranslationDependentOptions'] = $settings;
|
||||
$form['#validate'][] = 'content_translation_form_language_content_settings_validate';
|
||||
$form['#submit'][] = 'content_translation_form_language_content_settings_submit';
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
// We're given a generic name to look for so we find all inputs containing
|
||||
// that name and copy over the input values that require all columns to be
|
||||
// translatable.
|
||||
if (options.dependent_selectors) {
|
||||
if (options && options.dependent_selectors) {
|
||||
for (var field in options.dependent_selectors) {
|
||||
if (options.dependent_selectors.hasOwnProperty(field)) {
|
||||
$fields = $context.find('input[name^="' + field + '"]');
|
||||
|
|
|
@ -45,3 +45,19 @@ function content_translation_update_8001() {
|
|||
/**
|
||||
* @} End of "addtogroup updates-8.0.0-rc".
|
||||
*/
|
||||
|
||||
/**
|
||||
* @addtogroup updates-8.0.x
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Clear field type plugin caches to fix image field translatability.
|
||||
*/
|
||||
function content_translation_update_8002() {
|
||||
\Drupal::service('plugin.manager.field.field_type')->clearCachedDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup updates-8.0.x".
|
||||
*/
|
||||
|
|
|
@ -14,6 +14,7 @@ use Drupal\Core\Form\FormStateInterface;
|
|||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\field\FieldConfigInterface;
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
|
@ -199,9 +200,17 @@ function content_translation_entity_base_field_info(EntityTypeInterface $entity_
|
|||
* which columns should be synchronized across different translations and
|
||||
* which are translatable. This is useful for instance to translate the
|
||||
* "alt" and "title" textual elements of an image field, while keeping the
|
||||
* same image on every translation.
|
||||
* same image on every translation. Each group has the following keys:
|
||||
* - title: Title of the column group.
|
||||
* - translatable: (optional) If the column group should be translatable by
|
||||
* default, defaults to FALSE.
|
||||
* - columns: (optional) A list of columns of this group. Defaults to the
|
||||
* name of he group as the single column.
|
||||
* - require_all_groups_for_translation: (optional) Set to TRUE to enforce
|
||||
* that making this column group translatable requires all others to be
|
||||
* translatable too.
|
||||
*
|
||||
* @see Drupal\image\Plugin\Field\FieldType\imageItem.
|
||||
* @see Drupal\image\Plugin\Field\FieldType\ImageItem
|
||||
*/
|
||||
function content_translation_field_info_alter(&$info) {
|
||||
foreach ($info as $key => $settings) {
|
||||
|
|
|
@ -66,6 +66,17 @@ class FieldTranslationSynchronizer implements FieldTranslationSynchronizerInterf
|
|||
// Retrieve all the untranslatable column groups and merge them into
|
||||
// single list.
|
||||
$groups = array_keys(array_diff($translation_sync, array_filter($translation_sync)));
|
||||
|
||||
// If a group was selected has the require_all_groups_for_translation
|
||||
// flag set, there are no untranslatable columns. This is done because
|
||||
// the UI adds Javascript that disables the other checkboxes, so their
|
||||
// values are not saved.
|
||||
foreach (array_filter($translation_sync) as $group) {
|
||||
if (!empty($column_groups[$group]['require_all_groups_for_translation'])) {
|
||||
$groups = [];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!empty($groups)) {
|
||||
$columns = array();
|
||||
foreach ($groups as $group) {
|
||||
|
@ -163,7 +174,16 @@ class FieldTranslationSynchronizer implements FieldTranslationSynchronizerInterf
|
|||
continue;
|
||||
}
|
||||
// If a synchronized column has changed or has been created from
|
||||
// scratch we need to override the full items array for all languages.
|
||||
// scratch we need to replace the values for this language as a
|
||||
// combination of the values that need to be synced from the source
|
||||
// items and the other columns from the existing values. This only
|
||||
// works if the delta exists in the language.
|
||||
elseif ($created && !empty($original_field_values[$langcode][$delta])) {
|
||||
$item_columns_to_sync = array_intersect_key($source_items[$delta], array_flip($columns));
|
||||
$item_columns_to_keep = array_diff_key($original_field_values[$langcode][$delta], array_flip($columns));
|
||||
$values[$langcode][$delta] = $item_columns_to_sync + $item_columns_to_keep;
|
||||
}
|
||||
// If the delta doesn't exist, copy from the source language.
|
||||
elseif ($created) {
|
||||
$values[$langcode][$delta] = $source_items[$delta];
|
||||
}
|
||||
|
|
|
@ -201,7 +201,9 @@ class ContentTranslationSyncUnitTest extends KernelTestBase {
|
|||
for ($delta = 0; $delta < $this->cardinality; $delta++) {
|
||||
if ($delta_callback($delta)) {
|
||||
foreach ($this->columns as $column) {
|
||||
$field_values[$sync_langcode][$delta][$column] = $field_values[$sync_langcode][0][$column];
|
||||
if (in_array($column, $this->synchronized)) {
|
||||
$field_values[$sync_langcode][$delta][$column] = $field_values[$sync_langcode][0][$column];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -209,19 +211,18 @@ class ContentTranslationSyncUnitTest extends KernelTestBase {
|
|||
$changed_items = $field_values[$sync_langcode];
|
||||
$this->synchronizer->synchronizeItems($field_values, $unchanged_items, $sync_langcode, $this->langcodes, $this->synchronized);
|
||||
|
||||
$result = TRUE;
|
||||
foreach ($this->unchangedFieldValues as $langcode => $unchanged_items) {
|
||||
for ($delta = 0; $delta < $this->cardinality; $delta++) {
|
||||
foreach ($this->columns as $column) {
|
||||
// The first item is always unchanged hence it is retained by the
|
||||
// synchronization process. The other ones are retained or synced
|
||||
// depending on the logic implemented by the delta callback.
|
||||
$value = $delta > 0 && $delta_callback($delta) ? $changed_items[0][$column] : $unchanged_items[$delta][$column];
|
||||
$result = $result && ($field_values[$langcode][$delta][$column] == $value);
|
||||
// depending on the logic implemented by the delta callback and
|
||||
// whether it is a sync column or not.
|
||||
$value = $delta > 0 && $delta_callback($delta) && in_array($column, $this->synchronized) ? $changed_items[0][$column] : $unchanged_items[$delta][$column];
|
||||
$this->assertEqual($field_values[$langcode][$delta][$column], $value, "Item $delta column $column for langcode $langcode synced correctly");
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->assertTrue($result, 'Multiple synced items have been correctly synchronized.');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -241,15 +242,16 @@ class ContentTranslationSyncUnitTest extends KernelTestBase {
|
|||
$changed_items = $field_values[$sync_langcode];
|
||||
$this->synchronizer->synchronizeItems($field_values, $unchanged_items, $sync_langcode, $this->langcodes, $this->synchronized);
|
||||
|
||||
$result = TRUE;
|
||||
foreach ($this->unchangedFieldValues as $langcode => $unchanged_items) {
|
||||
for ($delta = 0; $delta < $this->cardinality; $delta++) {
|
||||
foreach ($this->columns as $column) {
|
||||
$result = $result && ($field_values[$langcode][$delta][$column] == $changed_items[$delta][$column]);
|
||||
// If the column is synchronized, the value should have been synced,
|
||||
// for unsychronized columns, the value must not change.
|
||||
$expected_value = in_array($column, $this->synchronized) ? $changed_items[$delta][$column] : $this->unchangedFieldValues[$langcode][$delta][$column];
|
||||
$this->assertEqual($field_values[$langcode][$delta][$column], $expected_value, "Differing Item $delta column $column for langcode $langcode synced correctly");
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->assertTrue($result, 'Differing synced columns have been correctly synchronized.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ namespace Drupal\dblog\Plugin\rest\resource;
|
|||
|
||||
use Drupal\rest\Plugin\ResourceBase;
|
||||
use Drupal\rest\ResourceResponse;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
|
@ -36,7 +36,10 @@ class DBLogResource extends ResourceBase {
|
|||
* @return \Drupal\rest\ResourceResponse
|
||||
* The response containing the log entry.
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\HttpException
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
|
||||
* Thrown when the log entry was not found.
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException
|
||||
* Thrown when no log entry was provided.
|
||||
*/
|
||||
public function get($id = NULL) {
|
||||
if ($id) {
|
||||
|
@ -49,6 +52,6 @@ class DBLogResource extends ResourceBase {
|
|||
throw new NotFoundHttpException(t('Log entry with ID @id was not found', array('@id' => $id)));
|
||||
}
|
||||
|
||||
throw new HttpException(t('No log entry ID was provided'));
|
||||
throw new BadRequestHttpException(t('No log entry ID was provided'));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,5 +59,11 @@ class DbLogResourceTest extends RESTTestBase {
|
|||
$this->assertResponse(404);
|
||||
$decoded = Json::decode($response);
|
||||
$this->assertEqual($decoded['error'], 'Log entry with ID 9999 was not found', 'Response message is correct.');
|
||||
|
||||
// Make a bad request (a true malformed request would never be a route match).
|
||||
$response = $this->httpRequest(Url::fromRoute('rest.dblog.GET.' . $this->defaultFormat, ['id' => 0, '_format' => $this->defaultFormat]), 'GET');
|
||||
$this->assertResponse(400);
|
||||
$decoded = Json::decode($response);
|
||||
$this->assertEqual($decoded['error'], 'No log entry ID was provided', 'Response message is correct.');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* Post update functions for Field module.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Entity\Entity\EntityFormDisplay;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
|
||||
|
@ -44,3 +45,34 @@ function field_post_update_entity_reference_handler_setting() {
|
|||
/**
|
||||
* @} End of "addtogroup updates-8.0.0-beta".
|
||||
*/
|
||||
|
||||
/**
|
||||
* @addtogroup updates-8.1.0
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Adds the 'size' setting for email widgets.
|
||||
*/
|
||||
function field_post_update_email_widget_size_setting() {
|
||||
foreach (EntityFormDisplay::loadMultiple() as $entity_form_display) {
|
||||
$changed = FALSE;
|
||||
foreach ($entity_form_display->getComponents() as $name => $options) {
|
||||
if (isset($options['type']) && $options['type'] === 'email_default') {
|
||||
$options['settings']['size'] = '60';
|
||||
$entity_form_display->setComponent($name, $options);
|
||||
$changed = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
if ($changed) {
|
||||
$entity_form_display->save();
|
||||
}
|
||||
}
|
||||
|
||||
return t('The new size setting for email widgets has been added.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup updates-8.1.0".
|
||||
*/
|
||||
|
|
|
@ -51,14 +51,16 @@ class FieldInstanceSettings extends ProcessPluginBase {
|
|||
|
||||
case 'imagefield_widget':
|
||||
$settings['file_extensions'] = $widget_settings['file_extensions'];
|
||||
$settings['file_directory'] = 'public://';
|
||||
$settings['file_directory'] = $widget_settings['file_path'];
|
||||
$settings['max_filesize'] = $this->convertSizeUnit($widget_settings['max_filesize_per_file']);
|
||||
$settings['alt_field'] = $widget_settings['alt'];
|
||||
$settings['alt_field_required'] = $widget_settings['custom_alt'];
|
||||
$settings['title_field'] = $widget_settings['title'];
|
||||
$settings['title_field_required'] = $widget_settings['custom_title'];
|
||||
$settings['max_resolution'] = $widget_settings['max_resolution'];
|
||||
$settings['min_resolution'] = $widget_settings['min_resolution'];
|
||||
// With nothing entered for min or max resolution in Drupal 6, zero is
|
||||
// stored. For Drupal 8 this should be an empty string.
|
||||
$settings['max_resolution'] = !empty($widget_settings['max_resolution']) ? $widget_settings['max_resolution'] : '';
|
||||
$settings['min_resolution'] = !empty($widget_settings['min_resolution']) ? $widget_settings['min_resolution'] : '';
|
||||
break;
|
||||
|
||||
}
|
||||
|
|
|
@ -62,6 +62,16 @@ class MigrateFieldInstanceTest extends MigrateDrupal6TestBase {
|
|||
$this->assertIdentical('Email Field', $field->label());
|
||||
$this->assertIdentical('benjy@example.com', $entity->field_test_email->value);
|
||||
|
||||
// Test image field.
|
||||
$field = FieldConfig::load('node.story.field_test_imagefield');
|
||||
$this->assertIdentical('Image Field', $field->label());
|
||||
$field_settings = $field->getSettings();
|
||||
$this->assertIdentical('', $field_settings['max_resolution']);
|
||||
$this->assertIdentical('', $field_settings['min_resolution']);
|
||||
$this->assertIdentical('', $field_settings['file_directory']);
|
||||
$this->assertIdentical('png gif jpg jpeg', $field_settings['file_extensions']);
|
||||
$this->assertIdentical('public', $field_settings['uri_scheme']);
|
||||
|
||||
// Test a filefield.
|
||||
$field = FieldConfig::load('node.story.field_test_filefield');
|
||||
$this->assertIdentical('File Field', $field->label());
|
||||
|
|
|
@ -56,6 +56,7 @@ class MigrateFieldWidgetSettingsTest extends MigrateDrupal6TestBase {
|
|||
$component = $form_display->getComponent('field_test_email');
|
||||
$expected['type'] = 'email_default';
|
||||
$expected['weight'] = 6;
|
||||
$expected['settings'] = array('placeholder' => '', 'size' => 60);
|
||||
$this->assertIdentical($expected, $component);
|
||||
|
||||
// Link field.
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\field\Tests\Update\EmailWidgetSizeSettingUpdateTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\field\Tests\Update;
|
||||
|
||||
use Drupal\system\Tests\Update\UpdatePathTestBase;
|
||||
|
||||
/**
|
||||
* Tests the update for the 'size' setting of the 'email_default' field widget.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class EmailWidgetSizeSettingUpdateTest extends UpdatePathTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setDatabaseDumpFiles() {
|
||||
$this->databaseDumpFiles = [
|
||||
__DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
|
||||
__DIR__ . '/../../../tests/fixtures/update/drupal-8.email_widget_size_setting-2578741.php',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests field_post_update_email_widget_size_setting().
|
||||
*
|
||||
* @see field_post_update_email_widget_size_setting()
|
||||
*/
|
||||
public function testFieldPostUpdateEmailWidgetSizeSetting() {
|
||||
$configFactory = $this->container->get('config.factory');
|
||||
|
||||
// Load the 'node.article.default' entity form display and check that the
|
||||
// widget for 'field_email_2578741' does not have a 'size' setting.
|
||||
/** @var \Drupal\Core\Config\Config $config */
|
||||
$config = $configFactory->get('core.entity_form_display.node.article.default');
|
||||
$settings = $config->get('content.field_email_2578741.settings');
|
||||
$this->assertTrue(!isset($settings['size']), 'The size setting does not exist prior to running the update functions.');
|
||||
|
||||
// Run updates.
|
||||
$this->runUpdates();
|
||||
|
||||
// Reload the config and check that the 'size' setting has been populated.
|
||||
$config = $configFactory->get('core.entity_form_display.node.article.default');
|
||||
$settings = $config->get('content.field_email_2578741.settings');
|
||||
$this->assertEqual($settings['size'], 60, 'The size setting exists and it has the correct default value.');
|
||||
}
|
||||
|
||||
}
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
namespace Drupal\field\Tests\Views;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\views\Views;
|
||||
|
||||
/**
|
||||
|
@ -112,4 +114,32 @@ class FieldUITest extends FieldTestBase {
|
|||
$this->assertEqual($options, array('format', 'value'), 'The expected sort field options were found.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests adding a boolean field filter handler.
|
||||
*/
|
||||
public function testBooleanFilterHandler() {
|
||||
// Create a boolean field.
|
||||
$field_name = 'field_boolean';
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'node',
|
||||
'type' => 'boolean',
|
||||
]);
|
||||
$field_storage->save();
|
||||
$field = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => 'page',
|
||||
]);
|
||||
$field->save();
|
||||
|
||||
$url = "admin/structure/views/nojs/add-handler/test_view_fieldapi/default/filter";
|
||||
$this->drupalPostForm($url, ['name[node__' . $field_name . '.' . $field_name . '_value]' => TRUE], t('Add and configure @handler', array('@handler' => t('filter criteria'))));
|
||||
$this->assertResponse(200);
|
||||
// Verify that using a boolean field as a filter also results in using the
|
||||
// boolean plugin.
|
||||
$option = $this->xpath('//label[@for="edit-options-value-1"]');
|
||||
$this->assertEqual(t('True'), (string) $option[0]);
|
||||
$option = $this->xpath('//label[@for="edit-options-value-0"]');
|
||||
$this->assertEqual(t('False'), (string) $option[0]);
|
||||
}
|
||||
}
|
||||
|
|
37
core/modules/field/tests/fixtures/update/drupal-8.email_widget_size_setting-2578741.php
vendored
Normal file
37
core/modules/field/tests/fixtures/update/drupal-8.email_widget_size_setting-2578741.php
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains SQL necessary to add a new component for an email field/widget to
|
||||
* the 'node.article.default' entity form display.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
|
||||
$connection = Database::getConnection();
|
||||
|
||||
$config = $connection->select('config', 'c')
|
||||
->fields('c')
|
||||
->condition('collection', '')
|
||||
->condition('name', 'core.entity_form_display.node.article.default')
|
||||
->execute()
|
||||
->fetchAssoc();
|
||||
|
||||
$data = unserialize($config['data']);
|
||||
|
||||
// Manually add a new component that simulates an email field using the default
|
||||
// email widget.
|
||||
$data['content']['field_email_2578741'] = [
|
||||
'weight' => 20,
|
||||
'settings' => [
|
||||
'placeholder' => '',
|
||||
],
|
||||
'third_party_settings' => [],
|
||||
'type' => 'email_default',
|
||||
];
|
||||
|
||||
$connection->update('config')
|
||||
->fields(['data' => serialize($data)])
|
||||
->condition('collection', '')
|
||||
->condition('name', 'core.entity_form_display.node.article.default')
|
||||
->execute();
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Tests\field\Unit\Plugin\migrate\process\d7\FieldInstanceSettingsTest.
|
||||
* Contains \Drupal\Tests\field\Unit\Plugin\migrate\process\d7\FieldSettingsTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\Tests\field\Unit\Plugin\migrate\process\d7;
|
||||
|
|
|
@ -51,10 +51,12 @@ function file_help($route_name, RouteMatchInterface $route_match) {
|
|||
/**
|
||||
* Loads file entities from the database.
|
||||
*
|
||||
* @param array $fids
|
||||
* (optional) An array of entity IDs. If omitted, all entities are loaded.
|
||||
* @param array|null $fids
|
||||
* (optional) An array of entity IDs. If omitted or NULL, all entities are
|
||||
* loaded.
|
||||
* @param bool $reset
|
||||
* Whether to reset the internal file_load_multiple() cache.
|
||||
* (optional) Whether to reset the internal file_load_multiple() cache.
|
||||
* Defaults to FALSE.
|
||||
*
|
||||
* @return array
|
||||
* An array of file entities, indexed by fid.
|
||||
|
@ -80,9 +82,10 @@ function file_load_multiple(array $fids = NULL, $reset = FALSE) {
|
|||
* @param int $fid
|
||||
* A file ID.
|
||||
* @param bool $reset
|
||||
* Whether to reset the internal file_load_multiple() cache.
|
||||
* (optional) Whether to reset the internal file_load_multiple() cache.
|
||||
* Defaults to FALSE.
|
||||
*
|
||||
* @return \Drupal\file\FileInterface
|
||||
* @return \Drupal\file\FileInterface|null
|
||||
* A file entity or NULL if the file was not found.
|
||||
*
|
||||
* @deprecated in Drupal 8.x, will be removed before Drupal 9.0.
|
||||
|
@ -117,19 +120,20 @@ function file_load($fid, $reset = FALSE) {
|
|||
* @param \Drupal\file\FileInterface $source
|
||||
* A file entity.
|
||||
* @param string $destination
|
||||
* A string containing the destination that $source should be copied to.
|
||||
* This must be a stream wrapper URI.
|
||||
* A string containing the destination that $source should be
|
||||
* copied to. This must be a stream wrapper URI.
|
||||
* @param int $replace
|
||||
* Replace behavior when the destination file already exists:
|
||||
* - FILE_EXISTS_REPLACE - Replace the existing file. If a managed file with
|
||||
* the destination name exists then its database entry will be updated. If
|
||||
* no database entry is found then a new one will be created.
|
||||
* - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
|
||||
* unique.
|
||||
* - FILE_EXISTS_ERROR - Do nothing and return FALSE.
|
||||
* (optional) Replace behavior when the destination file already exists.
|
||||
* Possible values include:
|
||||
* - FILE_EXISTS_REPLACE: Replace the existing file. If a managed file with
|
||||
* the destination name exists, then its database entry will be updated. If
|
||||
* no database entry is found, then a new one will be created.
|
||||
* - FILE_EXISTS_RENAME: (default) Append _{incrementing number} until the
|
||||
* filename is unique.
|
||||
* - FILE_EXISTS_ERROR: Do nothing and return FALSE.
|
||||
*
|
||||
* @return \Drupal\file\FileInterface|FALSE
|
||||
* File object if the copy is successful, or FALSE in the event of an error.
|
||||
* @return \Drupal\file\FileInterface|false
|
||||
* File entity if the copy is successful, or FALSE in the event of an error.
|
||||
*
|
||||
* @see file_unmanaged_copy()
|
||||
* @see hook_file_copy()
|
||||
|
@ -192,20 +196,20 @@ function file_copy(FileInterface $source, $destination = NULL, $replace = FILE_E
|
|||
* @param \Drupal\file\FileInterface $source
|
||||
* A file entity.
|
||||
* @param string $destination
|
||||
* A string containing the destination that $source should be moved to.
|
||||
* This must be a stream wrapper URI.
|
||||
* A string containing the destination that $source should be moved
|
||||
* to. This must be a stream wrapper URI.
|
||||
* @param int $replace
|
||||
* Replace behavior when the destination file already exists:
|
||||
* - FILE_EXISTS_REPLACE - Replace the existing file. If a managed file with
|
||||
* the destination name exists then its database entry will be updated and
|
||||
* $source->delete() called after invoking hook_file_move().
|
||||
* If no database entry is found then the source files record will be
|
||||
* updated.
|
||||
* - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
|
||||
* unique.
|
||||
* - FILE_EXISTS_ERROR - Do nothing and return FALSE.
|
||||
* (optional) The replace behavior when the destination file already exists.
|
||||
* Possible values include:
|
||||
* - FILE_EXISTS_REPLACE: Replace the existing file. If a managed file with
|
||||
* the destination name exists then its database entry will be updated and
|
||||
* $source->delete() called after invoking hook_file_move(). If no database
|
||||
* entry is found, then the source files record will be updated.
|
||||
* - FILE_EXISTS_RENAME: (default) Append _{incrementing number} until the
|
||||
* filename is unique.
|
||||
* - FILE_EXISTS_ERROR: Do nothing and return FALSE.
|
||||
*
|
||||
* @return \Drupal\file\FileInterface
|
||||
* @return \Drupal\file\FileInterface|false
|
||||
* Resulting file entity for success, or FALSE in the event of an error.
|
||||
*
|
||||
* @see file_unmanaged_move()
|
||||
|
@ -268,12 +272,13 @@ function file_move(FileInterface $source, $destination = NULL, $replace = FILE_E
|
|||
* @param \Drupal\file\FileInterface $file
|
||||
* A file entity.
|
||||
* @param array $validators
|
||||
* An optional, associative array of callback functions used to validate the
|
||||
* file. The keys are function names and the values arrays of callback
|
||||
* parameters which will be passed in after the file entity. The
|
||||
* functions should return an array of error messages; an empty array
|
||||
* indicates that the file passed validation. The functions will be called in
|
||||
* the order specified.
|
||||
* (optional) An associative array of callback functions used to validate
|
||||
* the file. The keys are function names and the values arrays of callback
|
||||
* parameters which will be passed in after the file entity. The functions
|
||||
* should return an array of error messages; an empty array indicates that
|
||||
* the file passed validation. The callback functions will be called in the
|
||||
* order specified in the array, then the hook hook_file_validate()
|
||||
* will be invoked so other modules can validate the new file.
|
||||
*
|
||||
* @return array
|
||||
* An array containing validation error messages.
|
||||
|
@ -301,7 +306,8 @@ function file_validate(FileInterface $file, $validators = array()) {
|
|||
* A file entity.
|
||||
*
|
||||
* @return array
|
||||
* An array. If the file name is too long, it will contain an error message.
|
||||
* An empty array if the file name length is smaller than the limit or an
|
||||
* array containing an error message if it's not or is empty.
|
||||
*/
|
||||
function file_validate_name_length(FileInterface $file) {
|
||||
$errors = array();
|
||||
|
@ -324,8 +330,8 @@ function file_validate_name_length(FileInterface $file) {
|
|||
* A string with a space separated list of allowed extensions.
|
||||
*
|
||||
* @return array
|
||||
* An array. If the file extension is not allowed, it will contain an error
|
||||
* message.
|
||||
* An empty array if the file extension is allowed or an array containing an
|
||||
* error message if it's not.
|
||||
*
|
||||
* @see hook_file_validate()
|
||||
*/
|
||||
|
@ -345,15 +351,15 @@ function file_validate_extensions(FileInterface $file, $extensions) {
|
|||
* @param \Drupal\file\FileInterface $file
|
||||
* A file entity.
|
||||
* @param int $file_limit
|
||||
* An integer specifying the maximum file size in bytes. Zero indicates that
|
||||
* no limit should be enforced.
|
||||
* (optional) The maximum file size in bytes. Zero (the default) indicates
|
||||
* that no limit should be enforced.
|
||||
* @param int $user_limit
|
||||
* An integer specifying the maximum number of bytes the user is allowed.
|
||||
* Zero indicates that no limit should be enforced.
|
||||
* (optional) The maximum number of bytes the user is allowed. Zero (the
|
||||
* default) indicates that no limit should be enforced.
|
||||
*
|
||||
* @return array
|
||||
* An array. If the file size exceeds limits, it will contain an error
|
||||
* message.
|
||||
* An empty array if the file size is below limits or an array containing an
|
||||
* error message if it's not.
|
||||
*
|
||||
* @see hook_file_validate()
|
||||
*/
|
||||
|
@ -380,7 +386,8 @@ function file_validate_size(FileInterface $file, $file_limit = 0, $user_limit =
|
|||
* A file entity.
|
||||
*
|
||||
* @return array
|
||||
* An array. If the file is not an image, it will contain an error message.
|
||||
* An empty array if the file is a valid image or an array containing an error
|
||||
* message if it's not.
|
||||
*
|
||||
* @see hook_file_validate()
|
||||
*/
|
||||
|
@ -405,18 +412,21 @@ function file_validate_is_image(FileInterface $file) {
|
|||
*
|
||||
* @param \Drupal\file\FileInterface $file
|
||||
* A file entity. This function may resize the file affecting its size.
|
||||
* @param string $maximum_dimensions
|
||||
* An optional string in the form WIDTHxHEIGHT e.g. '640x480' or '85x85'. If
|
||||
* an image toolkit is installed the image will be resized down to these
|
||||
* dimensions. A value of 0 indicates no restriction on size, so resizing
|
||||
* will be attempted.
|
||||
* @param string $minimum_dimensions
|
||||
* An optional string in the form WIDTHxHEIGHT. This will check that the
|
||||
* image meets a minimum size. A value of 0 indicates no restriction.
|
||||
* @param string|int $maximum_dimensions
|
||||
* (optional) A string in the form WIDTHxHEIGHT; for example, '640x480' or
|
||||
* '85x85'. If an image toolkit is installed, the image will be resized down
|
||||
* to these dimensions. A value of zero (the default) indicates no restriction
|
||||
* on size, so no resizing will be attempted.
|
||||
* @param string|int $minimum_dimensions
|
||||
* (optional) A string in the form WIDTHxHEIGHT. This will check that the
|
||||
* image meets a minimum size. A value of zero (the default) indicates that
|
||||
* there is no restriction on size.
|
||||
*
|
||||
* @return
|
||||
* An array. If the file is an image and did not meet the requirements, it
|
||||
* will contain an error message.
|
||||
* @return array
|
||||
* An empty array if the file meets the specified dimensions, was resized
|
||||
* successfully to meet those requirements or is not an image. If the image
|
||||
* does not meet the requirements or an attempt to resize it fails, an array
|
||||
* containing the error message will be returned.
|
||||
*
|
||||
* @see hook_file_validate()
|
||||
*/
|
||||
|
@ -460,20 +470,22 @@ function file_validate_image_resolution(FileInterface $file, $maximum_dimensions
|
|||
*
|
||||
* @param string $data
|
||||
* A string containing the contents of the file.
|
||||
* @param string $destination
|
||||
* A string containing the destination URI. This must be a stream wrapper URI.
|
||||
* If no value is provided, a randomized name will be generated and the file
|
||||
* will be saved using Drupal's default files scheme, usually "public://".
|
||||
* @param string|null $destination
|
||||
* (optional) A string containing the destination URI. This must be a stream
|
||||
* wrapper URI. If no value or NULL is provided, a randomized name will be
|
||||
* generated and the file will be saved using Drupal's default files scheme,
|
||||
* usually "public://".
|
||||
* @param int $replace
|
||||
* Replace behavior when the destination file already exists:
|
||||
* - FILE_EXISTS_REPLACE - Replace the existing file. If a managed file with
|
||||
* the destination name exists then its database entry will be updated. If
|
||||
* no database entry is found then a new one will be created.
|
||||
* - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
|
||||
* unique.
|
||||
* - FILE_EXISTS_ERROR - Do nothing and return FALSE.
|
||||
* (optional) The replace behavior when the destination file already exists.
|
||||
* Possible values include:
|
||||
* - FILE_EXISTS_REPLACE: Replace the existing file. If a managed file with
|
||||
* the destination name exists, then its database entry will be updated. If
|
||||
* no database entry is found, then a new one will be created.
|
||||
* - FILE_EXISTS_RENAME: (default) Append _{incrementing number} until the
|
||||
* filename is unique.
|
||||
* - FILE_EXISTS_ERROR: Do nothing and return FALSE.
|
||||
*
|
||||
* @return \Drupal\file\FileInterface
|
||||
* @return \Drupal\file\FileInterface|false
|
||||
* A file entity, or FALSE on error.
|
||||
*
|
||||
* @see file_unmanaged_save_data()
|
||||
|
@ -666,39 +678,32 @@ function file_cron() {
|
|||
* A string that is the associative array key of the upload form element in
|
||||
* the form array.
|
||||
* @param array $validators
|
||||
* An optional, associative array of callback functions used to validate the
|
||||
* (optional) An associative array of callback functions used to validate the
|
||||
* file. See file_validate() for a full discussion of the array format.
|
||||
* If no extension validator is provided it will default to a limited safe
|
||||
* list of extensions which is as follows: "jpg jpeg gif png txt
|
||||
* doc xls pdf ppt pps odt ods odp". To allow all extensions you must
|
||||
* explicitly set the 'file_validate_extensions' validator to an empty array
|
||||
* (Beware: this is not safe and should only be allowed for trusted users, if
|
||||
* at all).
|
||||
* @param string $destination
|
||||
* A string containing the URI that the file should be copied to. This must
|
||||
* be a stream wrapper URI. If this value is omitted, Drupal's temporary
|
||||
* files scheme will be used ("temporary://").
|
||||
* @param int $delta
|
||||
* Delta of the file to save or NULL to save all files. Defaults to NULL.
|
||||
* If the array is empty, it will be set up to call file_validate_extensions()
|
||||
* with a safe list of extensions, as follows: "jpg jpeg gif png txt doc
|
||||
* xls pdf ppt pps odt ods odp". To allow all extensions, you must explicitly
|
||||
* set this array to ['file_validate_extensions' => '']. (Beware: this is not
|
||||
* safe and should only be allowed for trusted users, if at all.)
|
||||
* @param string|false $destination
|
||||
* (optional) A string containing the URI that the file should be copied to.
|
||||
* This must be a stream wrapper URI. If this value is omitted or set to
|
||||
* FALSE, Drupal's temporary files scheme will be used ("temporary://").
|
||||
* @param null|int $delta
|
||||
* (optional) The delta of the file to return the file entity.
|
||||
* Defaults to NULL.
|
||||
* @param int $replace
|
||||
* Replace behavior when the destination file already exists:
|
||||
* (optional) The replace behavior when the destination file already exists.
|
||||
* Possible values include:
|
||||
* - FILE_EXISTS_REPLACE: Replace the existing file.
|
||||
* - FILE_EXISTS_RENAME: Append _{incrementing number} until the filename is
|
||||
* unique.
|
||||
* - FILE_EXISTS_RENAME: (default) Append _{incrementing number} until the
|
||||
* filename is unique.
|
||||
* - FILE_EXISTS_ERROR: Do nothing and return FALSE.
|
||||
*
|
||||
* @return array
|
||||
* Function returns array of files or a single file object if $delta
|
||||
* != NULL. Each file object contains the file information if the
|
||||
* upload succeeded or FALSE in the event of an error. Function
|
||||
* returns NULL if no file was uploaded.
|
||||
*
|
||||
* The documentation for the "File interface" group, which you can find under
|
||||
* Related topics, or the header at the top of this file, documents the
|
||||
* components of a file entity. In addition to the standard components,
|
||||
* this function adds:
|
||||
* - source: Path to the file before it is moved.
|
||||
* - destination: Path to the file after it is moved (same as 'uri').
|
||||
* @return array|\Drupal\file\FileInterface|null|false
|
||||
* An array of file entities or a single file entity if $delta != NULL. Each
|
||||
* array element contains the file entity if the upload succeeded or FALSE if
|
||||
* there was an error. Function returns NULL if no file was uploaded.
|
||||
*/
|
||||
function file_save_upload($form_field_name, $validators = array(), $destination = FALSE, $delta = NULL, $replace = FILE_EXISTS_RENAME) {
|
||||
$user = \Drupal::currentUser();
|
||||
|
@ -903,7 +908,7 @@ function file_save_upload($form_field_name, $validators = array(), $destination
|
|||
/**
|
||||
* Determines the preferred upload progress implementation.
|
||||
*
|
||||
* @return string|FALSE
|
||||
* @return string|false
|
||||
* A string indicating which upload progress system is available. Either "apc"
|
||||
* or "uploadprogress". If neither are available, returns FALSE.
|
||||
*/
|
||||
|
@ -928,7 +933,7 @@ function file_progress_implementation() {
|
|||
* Implements hook_ENTITY_TYPE_predelete() for file entities.
|
||||
*/
|
||||
function file_file_predelete(File $file) {
|
||||
// TODO: Remove references to a file that is in-use.
|
||||
// @todo Remove references to a file that is in-use.
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1156,9 +1161,10 @@ function file_managed_file_submit($form, FormStateInterface $form_state) {
|
|||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
*
|
||||
* @return array
|
||||
* @return array|false
|
||||
* An array of file entities for each file that was saved, keyed by its file
|
||||
* ID, or FALSE if no files were saved.
|
||||
* ID. Each array element contains a file entity. Function returns FALSE if
|
||||
* upload directory could not be created or no files were uploaded.
|
||||
*/
|
||||
function file_managed_file_save_upload($element, FormStateInterface $form_state) {
|
||||
$upload_name = implode('_', $element['#parents']);
|
||||
|
@ -1437,20 +1443,20 @@ function file_icon_map($mime_type) {
|
|||
*
|
||||
* @param \Drupal\file\FileInterface $file
|
||||
* A file entity.
|
||||
* @param \Drupal\Core\Field\FieldDefinitionInterface $field
|
||||
* (optional) A field definition to be used for this check. If given, limits the
|
||||
* reference check to the given field.
|
||||
* @param \Drupal\Core\Field\FieldDefinitionInterface|null $field
|
||||
* (optional) A field definition to be used for this check. If given,
|
||||
* limits the reference check to the given field. Defaults to NULL.
|
||||
* @param int $age
|
||||
* (optional) A constant that specifies which references to count. Use
|
||||
* EntityStorageInterface::FIELD_LOAD_REVISION to retrieve all
|
||||
* EntityStorageInterface::FIELD_LOAD_REVISION (the default) to retrieve all
|
||||
* references within all revisions or
|
||||
* EntityStorageInterface::FIELD_LOAD_CURRENT to retrieve references
|
||||
* only in the current revisions.
|
||||
* EntityStorageInterface::FIELD_LOAD_CURRENT to retrieve references only in
|
||||
* the current revisions of all entities that have references to this file.
|
||||
* @param string $field_type
|
||||
* (optional) The name of a field type. If given, limits the reference check
|
||||
* to fields of the given type. If both $field and $field_type is given but
|
||||
* to fields of the given type. If both $field and $field_type are given but
|
||||
* $field is not the same type as $field_type, an empty array will be
|
||||
* returned.
|
||||
* returned. Defaults to 'file'.
|
||||
*
|
||||
* @return array
|
||||
* A multidimensional array. The keys are field_name, entity_type,
|
||||
|
@ -1526,10 +1532,12 @@ function file_get_file_references(FileInterface $file, FieldDefinitionInterface
|
|||
/**
|
||||
* Formats human-readable version of file status.
|
||||
*
|
||||
* @param int $choice
|
||||
* integer Status code.
|
||||
* @return string
|
||||
* string Text-represented file status.
|
||||
* @param int|null $choice
|
||||
* (optional) An integer status code. If not set, all statuses are returned.
|
||||
* Defaults to NULL.
|
||||
*
|
||||
* @return \Drupal\Core\StringTranslation\TranslatableMarkup|\Drupal\Core\StringTranslation\TranslatableMarkup[]
|
||||
* An array of file statuses or a specified status if $choice is set.
|
||||
*/
|
||||
function _views_file_status($choice = NULL) {
|
||||
$status = array(
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
file.ajax_progress:
|
||||
path: '/file/progress'
|
||||
path: '/file/progress/{key}'
|
||||
defaults:
|
||||
_controller: '\Drupal\file\Controller\FileWidgetAjaxController::progress'
|
||||
requirements:
|
||||
|
|
|
@ -283,7 +283,7 @@ class ManagedFile extends FormElement {
|
|||
}
|
||||
|
||||
// Add the upload progress callback.
|
||||
$element['upload_button']['#ajax']['progress']['url'] = Url::fromRoute('file.ajax_progress');
|
||||
$element['upload_button']['#ajax']['progress']['url'] = Url::fromRoute('file.ajax_progress', ['key' => $upload_progress_key]);
|
||||
}
|
||||
|
||||
// The file upload field itself.
|
||||
|
|
|
@ -23,7 +23,7 @@ class DownloadTest extends FileManagedTestBase {
|
|||
* Test the public file transfer system.
|
||||
*/
|
||||
function testPublicFileTransfer() {
|
||||
// Test generating an URL to a created file.
|
||||
// Test generating a URL to a created file.
|
||||
$file = $this->createFile();
|
||||
$url = file_create_url($file->getFileUri());
|
||||
// URLs can't contain characters outside the ASCII set so $filename has to be
|
||||
|
@ -33,7 +33,7 @@ class DownloadTest extends FileManagedTestBase {
|
|||
$this->drupalHead($url);
|
||||
$this->assertResponse(200, 'Confirmed that the generated URL is correct by downloading the created file.');
|
||||
|
||||
// Test generating an URL to a shipped file (i.e. a file that is part of
|
||||
// Test generating a URL to a shipped file (i.e. a file that is part of
|
||||
// Drupal core, a module or a theme, for example a JavaScript file).
|
||||
$filepath = 'core/assets/vendor/jquery/jquery.min.js';
|
||||
$url = file_create_url($filepath);
|
||||
|
|
|
@ -480,6 +480,12 @@ class FileFieldWidgetTest extends FileFieldTestBase {
|
|||
|
||||
// If the field has at least a item, the table should be visible.
|
||||
$this->assertIdentical(count($elements), 1);
|
||||
|
||||
// Test for AJAX error when using progress bar on file field widget
|
||||
$key = $this->randomMachineName();
|
||||
$this->drupalPost('file/progress/' . $key, 'application/json', []);
|
||||
$this->assertNoResponse(500, t('No AJAX error when using progress bar on file field widget'));
|
||||
$this->assertText('Starting upload...');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
/**
|
||||
* Perform alterations on filter definitions.
|
||||
*
|
||||
* @param $info
|
||||
* @param array $info
|
||||
* Array of information on filters exposed by filter plugins.
|
||||
*/
|
||||
function hook_filter_info_alter(&$info) {
|
||||
|
@ -48,7 +48,7 @@ function hook_filter_secure_image_alter(&$image) {
|
|||
/**
|
||||
* Perform actions when a text format has been disabled.
|
||||
*
|
||||
* @param $format
|
||||
* @param \Drupal\filter\FilterFormatInterface $format
|
||||
* The format object of the format being disabled.
|
||||
*/
|
||||
function hook_filter_format_disable($format) {
|
||||
|
|
|
@ -159,7 +159,7 @@ function filter_get_roles_by_format(FilterFormatInterface $format) {
|
|||
/**
|
||||
* Retrieves a list of text formats that are allowed for a given role.
|
||||
*
|
||||
* @param $rid
|
||||
* @param string $rid
|
||||
* The user role ID to retrieve text formats for.
|
||||
*
|
||||
* @return \Drupal\filter\FilterFormatInterface[]
|
||||
|
@ -236,7 +236,7 @@ function filter_default_format(AccountInterface $account = NULL) {
|
|||
* Any modules implementing a format deletion functionality must not delete this
|
||||
* format.
|
||||
*
|
||||
* @return
|
||||
* @return string|null
|
||||
* The ID of the fallback text format.
|
||||
*
|
||||
* @see hook_filter_format_disable()
|
||||
|
@ -635,10 +635,10 @@ function _filter_url_parse_partial_links($match) {
|
|||
*
|
||||
* Callback for preg_replace_callback() within _filter_url().
|
||||
*
|
||||
* @param $match
|
||||
* @param array $match
|
||||
* An array containing matches to replace from preg_replace_callback(),
|
||||
* whereas $match[1] is expected to contain the content to be filtered.
|
||||
* @param $escape
|
||||
* @param bool|null $escape
|
||||
* (optional) A Boolean indicating whether to escape (TRUE) or unescape
|
||||
* comments (FALSE). Defaults to NULL, indicating neither. If TRUE, statically
|
||||
* cached $comments are reset.
|
||||
|
|
|
@ -35,7 +35,7 @@ use Drupal\Core\Template\Attribute;
|
|||
* public function process($text, $langcode) {
|
||||
* // Modify $text.
|
||||
*
|
||||
* return new FilterProcess($text);
|
||||
* return new FilterProcessResult($text);
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
|
@ -44,7 +44,7 @@ use Drupal\Core\Template\Attribute;
|
|||
* public function process($text, $langcode) {
|
||||
* // Modify $text.
|
||||
*
|
||||
* $result = new FilterProcess($text);
|
||||
* $result = new FilterProcessResult($text);
|
||||
*
|
||||
* // Associate assets to be attached.
|
||||
* $result->setAttachments(array(
|
||||
|
|
|
@ -22,7 +22,7 @@ class FilterDefaultConfigTest extends KernelTestBase {
|
|||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Drupal\filter\FilterPermissions::permissions() builds an URL to output
|
||||
// Drupal\filter\FilterPermissions::permissions() builds a URL to output
|
||||
// a link in the description.
|
||||
$this->installSchema('system', 'url_alias');
|
||||
|
||||
|
|
|
@ -862,7 +862,7 @@ www.example.com with a newline in comments -->
|
|||
*
|
||||
* @param FilterInterface $filter
|
||||
* A input filter object.
|
||||
* @param $tests
|
||||
* @param array $tests
|
||||
* An associative array, whereas each key is an arbitrary input string and
|
||||
* each value is again an associative array whose keys are filter output
|
||||
* strings and whose values are Booleans indicating whether the output is
|
||||
|
@ -1141,13 +1141,13 @@ body {color:red}
|
|||
* Note that this does not remove nulls, new lines and other characters that
|
||||
* could be used to obscure a tag or an attribute name.
|
||||
*
|
||||
* @param $haystack
|
||||
* @param string $haystack
|
||||
* Text to look in.
|
||||
* @param $needle
|
||||
* @param string $needle
|
||||
* Lowercase, plain text to look for.
|
||||
* @param $message
|
||||
* @param string $message
|
||||
* (optional) Message to display if failed. Defaults to an empty string.
|
||||
* @param $group
|
||||
* @param string $group
|
||||
* (optional) The group this message belongs to. Defaults to 'Other'.
|
||||
*
|
||||
* @return bool
|
||||
|
@ -1166,13 +1166,13 @@ body {color:red}
|
|||
* Note that this does not remove nulls, new lines, and other character that
|
||||
* could be used to obscure a tag or an attribute name.
|
||||
*
|
||||
* @param $haystack
|
||||
* @param string $haystack
|
||||
* Text to look in.
|
||||
* @param $needle
|
||||
* @param string $needle
|
||||
* Lowercase, plain text to look for.
|
||||
* @param $message
|
||||
* @param string $message
|
||||
* (optional) Message to display if failed. Defaults to an empty string.
|
||||
* @param $group
|
||||
* @param string $group
|
||||
* (optional) The group this message belongs to. Defaults to 'Other'.
|
||||
*
|
||||
* @return bool
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies: { }
|
||||
dependencies:
|
||||
enforced:
|
||||
module:
|
||||
- forum
|
||||
id: comment_forum
|
||||
label: Comment_forum
|
||||
target_entity_type_id: node
|
||||
|
|
|
@ -119,6 +119,10 @@ class ForumUninstallTest extends WebTestBase {
|
|||
$this->drupalPostForm(NULL, array(), t('Delete'));
|
||||
$this->assertResponse(200);
|
||||
$this->assertFalse((bool) NodeType::load('forum'), 'Node type with machine forum deleted.');
|
||||
|
||||
// Double check everything by reinstalling the forum module again.
|
||||
$this->drupalPostForm('admin/modules', ['modules[Core][forum][enable]' => 1], 'Install');
|
||||
$this->assertText('Module Forum has been enabled.');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -31,7 +31,8 @@ use Drupal\file\Plugin\Field\FieldType\FileItem;
|
|||
* "label" = @Translation("File"),
|
||||
* "columns" = {
|
||||
* "target_id", "width", "height"
|
||||
* }
|
||||
* },
|
||||
* "require_all_groups_for_translation" = TRUE
|
||||
* },
|
||||
* "alt" = {
|
||||
* "label" = @Translation("Alt"),
|
||||
|
|
|
@ -244,7 +244,8 @@ class ImageAdminStylesTest extends ImageFieldTestBase {
|
|||
$this->assertNoLinkByHref($style_path . '/effects/' . $uuids['image_crop'] . '/delete');
|
||||
// Refresh the image style information and verify that the effect was
|
||||
// actually deleted.
|
||||
$style = entity_load_unchanged('image_style', $style->id());
|
||||
$entity_type_manager = $this->container->get('entity_type.manager');
|
||||
$style = $entity_type_manager->getStorage('image_style')->loadUnchanged($style->id());
|
||||
$this->assertFalse($style->getEffects()->has($uuids['image_crop']), format_string(
|
||||
'Effect with ID %uuid no longer found on image style %style',
|
||||
array(
|
||||
|
@ -260,7 +261,8 @@ class ImageAdminStylesTest extends ImageFieldTestBase {
|
|||
);
|
||||
$this->drupalPostForm($style_path, array('new' => 'image_rotate'), t('Add'));
|
||||
$this->drupalPostForm(NULL, $edit, t('Add effect'));
|
||||
$style = entity_load_unchanged('image_style', $style_name);
|
||||
$entity_type_manager = $this->container->get('entity_type.manager');
|
||||
$style = $entity_type_manager->getStorage('image_style')->loadUnchanged($style_name);
|
||||
$this->assertEqual(count($style->getEffects()), 6, 'Rotate effect with transparent background was added.');
|
||||
|
||||
// Style deletion form.
|
||||
|
@ -289,6 +291,47 @@ class ImageAdminStylesTest extends ImageFieldTestBase {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests editing Ajax-enabled image effect forms.
|
||||
*/
|
||||
public function testAjaxEnabledEffectForm() {
|
||||
$admin_path = 'admin/config/media/image-styles';
|
||||
|
||||
// Setup a style to be created and effects to add to it.
|
||||
$style_name = strtolower($this->randomMachineName(10));
|
||||
$style_label = $this->randomString();
|
||||
$style_path = $admin_path . '/manage/' . $style_name;
|
||||
$effect_edit = [
|
||||
'data[test_parameter]' => 100,
|
||||
];
|
||||
|
||||
// Add style form.
|
||||
$edit = [
|
||||
'name' => $style_name,
|
||||
'label' => $style_label,
|
||||
];
|
||||
$this->drupalPostForm($admin_path . '/add', $edit, t('Create new style'));
|
||||
$this->assertRaw(t('Style %name was created.', ['%name' => $style_label]));
|
||||
|
||||
// Add two Ajax-enabled test effects.
|
||||
$this->drupalPostForm($style_path, ['new' => 'image_module_test_ajax'], t('Add'));
|
||||
$this->drupalPostForm(NULL, $effect_edit, t('Add effect'));
|
||||
$this->drupalPostForm($style_path, ['new' => 'image_module_test_ajax'], t('Add'));
|
||||
$this->drupalPostForm(NULL, $effect_edit, t('Add effect'));
|
||||
|
||||
// Load the saved image style.
|
||||
$style = ImageStyle::load($style_name);
|
||||
|
||||
// Edit back the effects.
|
||||
foreach ($style->getEffects() as $uuid => $effect) {
|
||||
$effect_path = $admin_path . '/manage/' . $style_name . '/effects/' . $uuid;
|
||||
$this->drupalGet($effect_path);
|
||||
$this->drupalPostAjaxForm(NULL, $effect_edit, ['op' => t('Ajax refresh')]);
|
||||
$this->drupalPostForm(NULL, $effect_edit, t('Update effect'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test deleting a style and choosing a replacement style.
|
||||
*/
|
||||
|
|
|
@ -154,4 +154,11 @@ abstract class ImageFieldTestBase extends WebTestBase {
|
|||
return isset($matches[1]) ? $matches[1] : FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the fid of the last inserted file.
|
||||
*/
|
||||
protected function getLastFileId() {
|
||||
return (int) db_query('SELECT MAX(fid) FROM {file_managed}')->fetchField();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
232
core/modules/image/src/Tests/ImageOnTranslatedEntityTest.php
Normal file
232
core/modules/image/src/Tests/ImageOnTranslatedEntityTest.php
Normal file
|
@ -0,0 +1,232 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\image\Tests\ImageOnTranslatedEntityTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\image\Tests;
|
||||
|
||||
use Drupal\file\Entity\File;
|
||||
|
||||
/**
|
||||
* Uploads images to translated nodes.
|
||||
*
|
||||
* @group image
|
||||
*/
|
||||
class ImageOnTranslatedEntityTest extends ImageFieldTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = array('language', 'content_translation', 'field_ui');
|
||||
|
||||
/**
|
||||
* The name of the image field used in the test.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldName;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Create the "Basic page" node type.
|
||||
$this->drupalCreateContentType(array('type' => 'basicpage', 'name' => 'Basic page'));
|
||||
|
||||
// Create a image field on the "Basic page" node type.
|
||||
$this->fieldName = strtolower($this->randomMachineName());
|
||||
$this->createImageField($this->fieldName, 'basicpage', [], ['title_field' => 1]);
|
||||
|
||||
// Create and login user.
|
||||
$permissions = array(
|
||||
'access administration pages',
|
||||
'administer content translation',
|
||||
'administer content types',
|
||||
'administer languages',
|
||||
'administer node fields',
|
||||
'create content translations',
|
||||
'create basicpage content',
|
||||
'edit any basicpage content',
|
||||
'translate any entity',
|
||||
'delete any basicpage content',
|
||||
);
|
||||
$admin_user = $this->drupalCreateUser($permissions);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
// Add a second and third language.
|
||||
$edit = array();
|
||||
$edit['predefined_langcode'] = 'fr';
|
||||
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
|
||||
|
||||
$edit = array();
|
||||
$edit['predefined_langcode'] = 'nl';
|
||||
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests synced file fields on translated nodes.
|
||||
*/
|
||||
public function testSyncedImages() {
|
||||
// Enable translation for "Basic page" nodes.
|
||||
$edit = array(
|
||||
'entity_types[node]' => 1,
|
||||
'settings[node][basicpage][translatable]' => 1,
|
||||
"settings[node][basicpage][fields][$this->fieldName]" => 1,
|
||||
"settings[node][basicpage][columns][$this->fieldName][file]" => 1,
|
||||
// Explicitly disable alt and title since the javascript disables the
|
||||
// checkboxes on the form.
|
||||
"settings[node][basicpage][columns][$this->fieldName][alt]" => FALSE,
|
||||
"settings[node][basicpage][columns][$this->fieldName][title]" => FALSE,
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/content-language', $edit, 'Save configuration');
|
||||
|
||||
// Verify that the image field on the "Basic basic" node type is
|
||||
// translatable.
|
||||
$definitions = \Drupal::entityManager()->getFieldDefinitions('node', 'basicpage');
|
||||
$this->assertTrue($definitions[$this->fieldName]->isTranslatable(), 'Node image field is translatable.');
|
||||
|
||||
// Create a default language node.
|
||||
$default_language_node = $this->drupalCreateNode(array('type' => 'basicpage', 'title' => 'Lost in translation'));
|
||||
|
||||
// Edit the node to upload a file.
|
||||
$edit = array();
|
||||
$name = 'files[' . $this->fieldName . '_0]';
|
||||
$edit[$name] = drupal_realpath($this->drupalGetTestFiles('image')[0]->uri);
|
||||
$this->drupalPostForm('node/' . $default_language_node->id() . '/edit', $edit, t('Save'));
|
||||
$edit = [$this->fieldName . '[0][alt]' => 'Lost in translation image', $this->fieldName . '[0][title]' => 'Lost in translation image title'];
|
||||
$this->drupalPostForm(NULL, $edit, t('Save'));
|
||||
$first_fid = $this->getLastFileId();
|
||||
|
||||
// Translate the node into French: remove the existing file.
|
||||
$this->drupalPostForm('node/' . $default_language_node->id() . '/translations/add/en/fr', array(), t('Remove'));
|
||||
|
||||
// Upload a different file.
|
||||
$edit = array();
|
||||
$edit['title[0][value]'] = 'Scarlett Johansson';
|
||||
$name = 'files[' . $this->fieldName . '_0]';
|
||||
$edit[$name] = drupal_realpath($this->drupalGetTestFiles('image')[1]->uri);
|
||||
$this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
|
||||
$edit = [$this->fieldName . '[0][alt]' => 'Scarlett Johansson image', $this->fieldName . '[0][title]' => 'Scarlett Johansson image title'];
|
||||
$this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
|
||||
// This inspects the HTML after the post of the translation, the image
|
||||
// should be displayed on the original node.
|
||||
$this->assertRaw('alt="Lost in translation image"');
|
||||
$this->assertRaw('title="Lost in translation image title"');
|
||||
$second_fid = $this->getLastFileId();
|
||||
// View the translated node.
|
||||
$this->drupalGet('fr/node/' . $default_language_node->id());
|
||||
$this->assertRaw('alt="Scarlett Johansson image"');
|
||||
|
||||
\Drupal::entityTypeManager()->getStorage('file')->resetCache();
|
||||
|
||||
/* @var $file \Drupal\file\FileInterface */
|
||||
|
||||
// Ensure the file status of the first file permanent.
|
||||
$file = File::load($first_fid);
|
||||
$this->assertTrue($file->isPermanent());
|
||||
|
||||
// Ensure the file status of the second file is permanent.
|
||||
$file = File::load($second_fid);
|
||||
$this->assertTrue($file->isPermanent());
|
||||
|
||||
// Translate the node into dutch: remove the existing file.
|
||||
$this->drupalPostForm('node/' . $default_language_node->id() . '/translations/add/en/nl', array(), t('Remove'));
|
||||
|
||||
// Upload a different file.
|
||||
$edit = array();
|
||||
$edit['title[0][value]'] = 'Akiko Takeshita';
|
||||
$name = 'files[' . $this->fieldName . '_0]';
|
||||
$edit[$name] = drupal_realpath($this->drupalGetTestFiles('image')[2]->uri);
|
||||
$this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
|
||||
$edit = [$this->fieldName . '[0][alt]' => 'Akiko Takeshita image', $this->fieldName . '[0][title]' => 'Akiko Takeshita image title'];
|
||||
$this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
|
||||
$third_fid = $this->getLastFileId();
|
||||
|
||||
\Drupal::entityTypeManager()->getStorage('file')->resetCache();
|
||||
|
||||
// Ensure the first file is untouched.
|
||||
$file = File::load($first_fid);
|
||||
$this->assertTrue($file->isPermanent(), 'First file still exists and is permanent.');
|
||||
// This inspects the HTML after the post of the translation, the image
|
||||
// should be displayed on the original node.
|
||||
$this->assertRaw('alt="Lost in translation image"');
|
||||
$this->assertRaw('title="Lost in translation image title"');
|
||||
// View the translated node.
|
||||
$this->drupalGet('nl/node/' . $default_language_node->id());
|
||||
$this->assertRaw('alt="Akiko Takeshita image"');
|
||||
$this->assertRaw('title="Akiko Takeshita image title"');
|
||||
|
||||
// Ensure the file status of the second file is permanent.
|
||||
$file = File::load($second_fid);
|
||||
$this->assertTrue($file->isPermanent());
|
||||
|
||||
// Ensure the file status of the third file is permanent.
|
||||
$file = File::load($third_fid);
|
||||
$this->assertTrue($file->isPermanent());
|
||||
|
||||
// Edit the second translation: remove the existing file.
|
||||
$this->drupalPostForm('fr/node/' . $default_language_node->id() . '/edit', array(), t('Remove'));
|
||||
|
||||
// Upload a different file.
|
||||
$edit = array();
|
||||
$edit['title[0][value]'] = 'Giovanni Ribisi';
|
||||
$name = 'files[' . $this->fieldName . '_0]';
|
||||
$edit[$name] = drupal_realpath($this->drupalGetTestFiles('image')[3]->uri);
|
||||
$this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
|
||||
$name = $this->fieldName . '[0][alt]';
|
||||
|
||||
$edit = [$name => 'Giovanni Ribisi image'];
|
||||
$this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
|
||||
$replaced_second_fid = $this->getLastFileId();
|
||||
|
||||
\Drupal::entityTypeManager()->getStorage('file')->resetCache();
|
||||
|
||||
// Ensure the first and third files are untouched.
|
||||
$file = File::load($first_fid);
|
||||
$this->assertTrue($file->isPermanent(), 'First file still exists and is permanent.');
|
||||
|
||||
$file = File::load($third_fid);
|
||||
$this->assertTrue($file->isPermanent());
|
||||
|
||||
// Ensure the file status of the replaced second file is permanent.
|
||||
$file = File::load($replaced_second_fid);
|
||||
$this->assertTrue($file->isPermanent());
|
||||
|
||||
// Ensure the file status of the old second file is now temporary.
|
||||
$file = File::load($second_fid);
|
||||
$this->assertTrue($file->isTemporary());
|
||||
|
||||
// Delete the third translation.
|
||||
$this->drupalPostForm('nl/node/' . $default_language_node->id() . '/delete', array(), t('Delete Dutch translation'));
|
||||
|
||||
\Drupal::entityTypeManager()->getStorage('file')->resetCache();
|
||||
|
||||
// Ensure the first and replaced second files are untouched.
|
||||
$file = File::load($first_fid);
|
||||
$this->assertTrue($file->isPermanent(), 'First file still exists and is permanent.');
|
||||
|
||||
$file = File::load($replaced_second_fid);
|
||||
$this->assertTrue($file->isPermanent());
|
||||
|
||||
// Ensure the file status of the third file is now temporary.
|
||||
$file = File::load($third_fid);
|
||||
$this->assertTrue($file->isTemporary());
|
||||
|
||||
// Delete the all translations.
|
||||
$this->drupalPostForm('node/' . $default_language_node->id() . '/delete', array(), t('Delete all translations'));
|
||||
|
||||
\Drupal::entityTypeManager()->getStorage('file')->resetCache();
|
||||
|
||||
// Ensure the file status of the all files are now temporary.
|
||||
$file = File::load($first_fid);
|
||||
$this->assertTrue($file->isTemporary(), 'First file still exists and is temporary.');
|
||||
|
||||
$file = File::load($replaced_second_fid);
|
||||
$this->assertTrue($file->isTemporary());
|
||||
}
|
||||
|
||||
}
|
|
@ -1,3 +1,11 @@
|
|||
image.effect.image_module_test_ajax:
|
||||
type: mapping
|
||||
label: 'Ajax test'
|
||||
mapping:
|
||||
test_parameter:
|
||||
type: integer
|
||||
label: 'Test Parameter'
|
||||
|
||||
image.style.*.third_party.image_module_test:
|
||||
type: mapping
|
||||
label: 'Schema for image_module_test module additions to image_style entity'
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue