added config_ignore contrib module to ignore performance and devel config

This commit is contained in:
Bachir Soussi Chiadmi
2018-04-03 23:06:38 +02:00
parent cf1885a46b
commit 1a6f571e15
51 changed files with 3649 additions and 3 deletions
@@ -0,0 +1,2 @@
vendor
composer.lock
@@ -0,0 +1,41 @@
language: php
php:
- 5.6
- 7.0
env:
global:
- PATH=$PATH:/home/travis/.composer/vendor/bin
install:
- composer self-update
before_script:
# Set sendmail so drush doesn't throw an error during site install.
- echo "sendmail_path=`which true`" >> `php --ini | grep "Loaded Configuration" | awk '{print $4}'`
# Create database.
- mysql -e 'create database drupal'
# Install Drupal 8 target site.
- cd tests
- mkdir -p themes modules profiles
- composer install --prefer-dist
- ./vendor/bin/drush si standard -y --db-url=mysql://travis:@127.0.0.1/drupal
# Test latest commit on current branch.
- git clone --branch=$TRAVIS_BRANCH https://github.com/$TRAVIS_REPO_SLUG.git modules/config_filter
- ./vendor/bin/drush en config_filter -y
# Run Drush web server.
- ./vendor/bin/drush --debug runserver :8888 > ~/debug.txt 2>&1 &
- sleep 4s
- chmod -R ug+w sites
script:
- cd $TRAVIS_BUILD_DIR
- cd tests
- ./vendor/bin/phpcs --config-set installed_paths ../../drupal/coder/coder_sniffer
- ./vendor/bin/phpcs --config-set show_progress 1
- ./vendor/bin/phpcs --warning-severity=0 --standard=Drupal,DrupalPractice ../src
- ./vendor/bin/phpunit
notifications:
email: false
@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
@@ -0,0 +1,50 @@
# Config Filter
[![Build Status](https://travis-ci.org/nuvoleweb/config_filter.svg?branch=8.x-1.x)](https://travis-ci.org/nuvoleweb/config_filter)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/nuvoleweb/config_filter/badges/quality-score.png?b=8.x-1.x)](https://scrutinizer-ci.com/g/nuvoleweb/config_filter/?branch=8.x-1.x)
## Introduction
Modules such as Config Split want to modify the configuration when it is
synchronized between the database and the exported yaml files.
This module provides the API to do so but does not influence a sites operation.
## How it works
Configuration Filter swaps the config.storage.sync service from Drupal 8 core.
The new service wraps the file storage and applies filters to it.
This allows other modules to change the configuration as it gets imported or
exported both in the Drupal UI and with drush.
## What is a ConfigFilter
A ConfigFilter is a plugin. This module provides the plugin definition, the
plugin manager and the storage factory.
A ConfigFilter can have the following annotation:
```php
/**
* @ConfigFilter(
* id = "may_plugin_id",
* label = @Translation("An example configuration filter"),
* weight = 0,
* status = TRUE,
* storages = {"config.storage.sync"},
* )
*/
```
See `\Drupal\config_filter\Annotation\ConfigFilter`.
The weight allows the filters to be sorted. The status allows the filter to be
active or inactive, the `ConfigFilterManagerInterface::getFiltersForStorages`
will only take active filters into consideration. The weight, status and
storages are optional and the above values are the default.
## Alternative Config Filter Managers
Plugins are only available from enabled modules. If you want to provide a
config filter from a php library, all you have to do is implement the
`\Drupal\config_filter\ConfigFilterManagerInterface` and add it to the
service container with a `config.filter` tag.
Services with higher priority will have their filters added first.
@@ -0,0 +1,31 @@
{
"name": "drupal/config_filter",
"type": "drupal-module",
"description": "Config Filter allows other modules to interact with a ConfigStorage through filter plugins.",
"keywords": ["Drupal", "configuration", "configuration management"],
"authors": [
{
"name": "Fabian Bircher",
"email": "opensource@fabianbircher.com",
"homepage": "https://www.drupal.org/u/bircher",
"role": "Maintainer"
},
{
"name": "Nuvole Web",
"email": "info@nuvole.org",
"homepage": "http://nuvole.org",
"role": "Maintainer"
}
],
"homepage": "https://www.drupal.org/project/config_filter",
"support": {
"issues": "https://www.drupal.org/project/issues/config_filter",
"irc": "irc://irc.freenode.org/drupal-contribute",
"source": "http://cgit.drupalcode.org/config_filter"
},
"license": "GPL-2.0+",
"require": {},
"suggest": {
"drupal/config_split": "Split site configuration for different environments."
}
}
@@ -0,0 +1,11 @@
name: Config Filter
type: module
description: Config Filter allows other modules to interact with a ConfigStorage through filter plugins.
# core: 8.x
package: Config
# Information added by Drupal.org packaging script on 2017-11-27
version: '8.x-1.1'
core: '8.x'
project: 'config_filter'
datestamp: 1511779987
@@ -0,0 +1,16 @@
services:
plugin.manager.config_filter:
class: Drupal\config_filter\Plugin\ConfigFilterPluginManager
parent: default_plugin_manager
tags:
- { name: config.filter }
config_filter.storage.sync:
class: Drupal\config_filter\Config\FilteredStorage
factory: config_filter.storage_factory:getSync
decorates: config.storage.staging
public: false
config_filter.storage_factory:
class: Drupal\config_filter\ConfigFilterStorageFactory
arguments: ['@config_filter.storage.sync.inner']
tags:
- { name: service_collector, tag: 'config.filter', call: addConfigFilterManager }
@@ -0,0 +1,62 @@
<?php
namespace Drupal\config_filter\Annotation;
use Drupal\Component\Annotation\Plugin;
/**
* Defines a Config filter plugin item annotation object.
*
* @see \Drupal\config_filter\Plugin\ConfigFilterPluginManager
* @see plugin_api
*
* @Annotation
*/
class ConfigFilter extends Plugin {
/**
* The plugin ID.
*
* @var string
*/
public $id;
/**
* The label of the plugin.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $label;
/**
* The plugin weight.
*
* The higher the weight the later in the filter order the plugin will be.
*
* @var int
*/
public $weight = 0;
/**
* The status of the plugin.
*
* This is an easy way to turn off plugins.
*
* @var bool
*/
public $status = TRUE;
/**
* The storages the plugin filters on.
*
* If it is left empty ['config.storage.sync'] will be assumed.
* The only storage which is currently filtered is 'config.storage.sync'.
*
* @var string[]
*/
public $storages = [];
}
@@ -0,0 +1,248 @@
<?php
namespace Drupal\config_filter\Config;
use Drupal\Core\Config\StorageInterface;
/**
* Class FilteredStorage.
*
* This class wraps another storage.
* It filters the arguments before passing them on to the storage for write
* operations and filters the result of read operations before returning them.
*
* @package Drupal\config_filter\Config
*/
class FilteredStorage implements FilteredStorageInterface {
/**
* The storage container that we are wrapping.
*
* @var \Drupal\Core\Config\StorageInterface
*/
protected $storage;
/**
* The storage filters.
*
* @var \Drupal\config_filter\Config\StorageFilterInterface[]
*/
protected $filters;
/**
* Create a FilteredStorage with some storage and a filter.
*
* @param \Drupal\Core\Config\StorageInterface $storage
* The decorated storage.
* @param \Drupal\config_filter\Config\StorageFilterInterface[] $filters
* The filters to apply in the given order.
*/
public function __construct(StorageInterface $storage, array $filters) {
$this->storage = $storage;
$this->filters = $filters;
// Set the storage to all the filters.
foreach ($this->filters as $filter) {
$filter->setSourceStorage(new ReadOnlyStorage($storage));
$filter->setFilteredStorage($this);
}
}
/**
* {@inheritdoc}
*/
public function exists($name) {
$exists = $this->storage->exists($name);
foreach ($this->filters as $filter) {
$exists = $filter->filterExists($name, $exists);
}
return $exists;
}
/**
* {@inheritdoc}
*/
public function read($name) {
$data = $this->storage->read($name);
foreach ($this->filters as $filter) {
$data = $filter->filterRead($name, $data);
}
return $data;
}
/**
* {@inheritdoc}
*/
public function readMultiple(array $names) {
$data = $this->storage->readMultiple($names);
foreach ($this->filters as $filter) {
$data = $filter->filterReadMultiple($names, $data);
}
ksort($data);
return $data;
}
/**
* {@inheritdoc}
*/
public function write($name, array $data) {
foreach ($this->filters as $filter) {
if ($data) {
$data = $filter->filterWrite($name, $data);
}
else {
// The filterWrite has an array type hint in the interface.
$data = $filter->filterWrite($name, []);
}
}
if ($data) {
return $this->storage->write($name, $data);
}
// The data has been unset, check if it should be deleted.
if ($this->storage->exists($name)) {
foreach ($this->filters as $filter) {
if ($filter->filterWriteEmptyIsDelete($name)) {
return $this->storage->delete($name);
}
}
}
// The data was not written, but it is not an error.
return TRUE;
}
/**
* {@inheritdoc}
*/
public function delete($name) {
$success = TRUE;
foreach ($this->filters as $filter) {
$success = $filter->filterDelete($name, $success);
}
if ($success) {
$success = $this->storage->delete($name);
}
return $success;
}
/**
* {@inheritdoc}
*/
public function rename($name, $new_name) {
$success = TRUE;
foreach ($this->filters as $filter) {
$success = $filter->filterRename($name, $new_name, $success);
}
if ($success) {
$success = $this->storage->rename($name, $new_name);
}
return $success;
}
/**
* {@inheritdoc}
*/
public function encode($data) {
return $this->storage->encode($data);
}
/**
* {@inheritdoc}
*/
public function decode($raw) {
return $this->storage->decode($raw);
}
/**
* {@inheritdoc}
*/
public function listAll($prefix = '') {
$data = $this->storage->listAll($prefix);
foreach ($this->filters as $filter) {
$data = $filter->filterListAll($prefix, $data);
}
sort($data);
return $data;
}
/**
* {@inheritdoc}
*/
public function deleteAll($prefix = '') {
$delete = TRUE;
foreach ($this->filters as $filter) {
$delete = $filter->filterDeleteAll($prefix, $delete);
}
if ($delete) {
return $this->storage->deleteAll($prefix);
}
// The filters returned FALSE for $delete, so we delete the names
// individually and allow filters to prevent deleting the config.
foreach ($this->storage->listAll($prefix) as $name) {
$this->delete($name);
}
// The filters wanted to prevent deleting all and were called to delete the
// individual config name, is this a success? Let us say it is.
return TRUE;
}
/**
* {@inheritdoc}
*/
public function createCollection($collection) {
$filters = [];
foreach ($this->filters as $key => $filter) {
$filter = $filter->filterCreateCollection($collection);
if ($filter) {
$filters[$key] = $filter;
}
}
$storage = $this->storage->createCollection($collection);
$filtered = new static($storage, $filters);
// Set the storage to all the filters.
foreach ($filters as $filter) {
$filter->setSourceStorage(new ReadOnlyStorage($storage));
$filter->setFilteredStorage($filtered);
}
return $filtered;
}
/**
* {@inheritdoc}
*/
public function getAllCollectionNames() {
$collections = $this->storage->getAllCollectionNames();
foreach ($this->filters as $filter) {
$collections = $filter->filterGetAllCollectionNames($collections);
}
sort($collections);
return $collections;
}
/**
* {@inheritdoc}
*/
public function getCollectionName() {
$collection = $this->storage->getCollectionName();
foreach ($this->filters as $filter) {
$collection = $filter->filterGetCollectionName($collection);
}
return $collection;
}
}
@@ -0,0 +1,12 @@
<?php
namespace Drupal\config_filter\Config;
use Drupal\Core\Config\StorageInterface;
/**
* Essentially the StorageInterface, but knowing that config_filter is used.
*/
interface FilteredStorageInterface extends StorageInterface {
}
@@ -0,0 +1,125 @@
<?php
namespace Drupal\config_filter\Config;
use Drupal\Core\Config\StorageInterface;
use Drupal\config_filter\Exception\UnsupportedMethod;
/**
* Class ReadOnlyStorage.
*
* This is like any other StorageInterface, except it does not allow writing.
*
* @package Drupal\config_filter\Config
*/
class ReadOnlyStorage implements StorageInterface {
/**
* The config storage that we are decorating.
*
* @var \Drupal\Core\Config\StorageInterface
*/
protected $storage;
/**
* Create a ReadOnlyStorage decorating another storage.
*
* @param \Drupal\Core\Config\StorageInterface $storage
* The decorated storage.
*/
public function __construct(StorageInterface $storage) {
$this->storage = $storage;
}
/**
* {@inheritdoc}
*/
public function exists($name) {
return $this->storage->exists($name);
}
/**
* {@inheritdoc}
*/
public function read($name) {
return $this->storage->read($name);
}
/**
* {@inheritdoc}
*/
public function readMultiple(array $names) {
return $this->storage->readMultiple($names);
}
/**
* {@inheritdoc}
*/
public function write($name, array $data) {
throw new UnsupportedMethod(__METHOD__ . ' is not allowed on a ReadOnlyStorage');
}
/**
* {@inheritdoc}
*/
public function delete($name) {
throw new UnsupportedMethod(__METHOD__ . ' is not allowed on a ReadOnlyStorage');
}
/**
* {@inheritdoc}
*/
public function rename($name, $new_name) {
throw new UnsupportedMethod(__METHOD__ . ' is not allowed on a ReadOnlyStorage');
}
/**
* {@inheritdoc}
*/
public function encode($data) {
return $this->storage->encode($data);
}
/**
* {@inheritdoc}
*/
public function decode($raw) {
return $this->storage->decode($raw);
}
/**
* {@inheritdoc}
*/
public function listAll($prefix = '') {
return $this->storage->listAll($prefix);
}
/**
* {@inheritdoc}
*/
public function deleteAll($prefix = '') {
throw new UnsupportedMethod(__METHOD__ . ' is not allowed on a ReadOnlyStorage');
}
/**
* {@inheritdoc}
*/
public function createCollection($collection) {
return new static($this->storage->createCollection($collection));
}
/**
* {@inheritdoc}
*/
public function getAllCollectionNames() {
return $this->storage->getAllCollectionNames();
}
/**
* {@inheritdoc}
*/
public function getCollectionName() {
return $this->storage->getCollectionName();
}
}
@@ -0,0 +1,223 @@
<?php
namespace Drupal\config_filter\Config;
use Drupal\Core\Config\StorageInterface;
/**
* Interface StorageFilterInterface.
*
* This interface defines a config storage filter as the FilteredStorage expects
* to use when filtering the operations on the storage. The ConfigFilter plugin
* interface extends this interface, together with plugin related interfaces.
*
* A well-behaved filter does not perform any write operation in a read method.
*
* @package Drupal\config_split\Config
*/
interface StorageFilterInterface {
/**
* Sets the source config storage on which the operation is performed.
*
* The storage is given to the filter when the storage wrapper is set up,
* to avoid passing the storage to each of the filters so that they can read
* from it before writing filtered config. The storage is read-only, use the
* decorated storage to allow all filters to work for write operations.
*
* @param \Drupal\Core\Config\StorageInterface $storage
* The storage on which the operation is performed.
*/
public function setSourceStorage(StorageInterface $storage);
/**
* Sets the wrapped config storage which is using the filter.
*
* This storage is available to the filter in order to inspect how the end
* result looks like. This is useful for reading configuration from the
* storage as drupal will. Beware of recursive calls to the filter.
*
* @param \Drupal\Core\Config\StorageInterface $storage
* The storage which has the filters applied.
*/
public function setFilteredStorage(StorageInterface $storage);
/**
* Filters configuration data after it is read from the storage.
*
* @param string $name
* The name of a configuration object to load.
* @param array|bool $data
* The configuration data to filter.
*
* @return array|bool
* The filtered data.
*/
public function filterRead($name, $data);
/**
* Filter configuration data before it is written to the storage.
*
* @param string $name
* The name of a configuration object to save.
* @param array $data
* The configuration data to filter.
*
* @return array|null
* The filtered data.
*/
public function filterWrite($name, array $data);
/**
* Let the filter decide whether not-writing data should mean delete.
*
* Filters can return NULL for `filterWrite($name, $data)` which means to
* not write the data to the source storage, but it can also mean deleting it.
*
* @param string $name
* The name of a configuration object to save.
*
* @return bool|null
* True to delete at the end of a filtered write action.
*/
public function filterWriteEmptyIsDelete($name);
/**
* Filters whether a configuration object exists.
*
* @param string $name
* The name of a configuration object to test.
* @param bool $exists
* The previous result to alter.
*
* @return bool
* TRUE if the configuration object exists, FALSE otherwise.
*/
public function filterExists($name, $exists);
/**
* Deletes a configuration object from the storage.
*
* @param string $name
* The name of a configuration object to delete.
* @param bool $delete
* Whether the previous filter allows to delete.
*
* @return bool
* TRUE to allow deletion, FALSE otherwise.
*/
public function filterDelete($name, $delete);
/**
* Filters read configuration data from the storage.
*
* @param array $names
* List of names of the configuration objects to load.
* @param array $data
* A list of the configuration data stored for the configuration object name
* that could be loaded for the passed list of names.
*
* @return array
* A list of the configuration data stored for the configuration object name
* that could be loaded for the passed list of names.
*/
public function filterReadMultiple(array $names, array $data);
/**
* Filters renaming a configuration object in the storage.
*
* @param string $name
* The name of a configuration object to rename.
* @param string $new_name
* The new name of a configuration object.
* @param bool $rename
* Allowing renaming by previous filters.
*
* @return bool
* TRUE to allow renaming, FALSE otherwise.
*/
public function filterRename($name, $new_name, $rename);
/**
* Filters what listAll should return.
*
* @param string $prefix
* The prefix to search for. If omitted, all configuration object
* names that exist are returned.
* @param array $data
* The data returned by the storage.
*
* @return array
* The filtered configuration set.
*/
public function filterListAll($prefix, array $data);
/**
* Deletes configuration objects whose names start with a given prefix.
*
* Given the following configuration object names:
* - node.type.article
* - node.type.page.
*
* Passing the prefix 'node.type.' will delete the above configuration
* objects.
*
* @param string $prefix
* The prefix to search for. If omitted, all configuration
* objects that exist will be deleted.
* @param bool $delete
* Whether to delete all or not.
*
* @return bool
* TRUE to allow deleting all, FALSE to trigger individual deletion.
*/
public function filterDeleteAll($prefix, $delete);
/**
* Allows the filter to react on creating a collection on the storage.
*
* A configuration storage can contain multiple sets of configuration objects
* in partitioned collections. The collection name identifies the current
* collection used.
*
* @param string $collection
* The collection name. Valid collection names conform to the following
* regex [a-zA-Z_.]. A storage does not need to have a collection set.
* However, if a collection is set, then the storage should use it to store
* configuration in a way that allows retrieval of configuration for a
* particular collection.
*
* @return \Drupal\config_filter\Config\StorageFilterInterface|null
* Return a filter that should participate in the collection. This allows
* filters to act on different collections. Note that a new instance of the
* filter should be created rather than returning $this directly.
*/
public function filterCreateCollection($collection);
/**
* Filter getting the existing collections.
*
* A configuration storage can contain multiple sets of configuration objects
* in partitioned collections. The collection key name identifies the current
* collection used.
*
* @param string[] $collections
* The array of existing collection names.
*
* @return array
* An array of existing collection names.
*/
public function filterGetAllCollectionNames(array $collections);
/**
* Filter the name of the current collection the storage is using.
*
* @param string $collection
* The collection found by the storage.
*
* @return string
* The current collection name.
*/
public function filterGetCollectionName($collection);
}
@@ -0,0 +1,34 @@
<?php
namespace Drupal\config_filter;
/**
* Interface ConfigFilterManagerInterface.
*/
interface ConfigFilterManagerInterface {
/**
* Get the applicable filters for given storage names.
*
* @param string[] $storage_names
* The names of the storage plugins apply to.
* @param string[] $excluded
* The ids of filters to exclude.
*
* @return \Drupal\config_filter\Config\StorageFilterInterface[]
* The configured filter instances, keyed by filter id.
*/
public function getFiltersForStorages(array $storage_names, array $excluded = []);
/**
* Get a configured filter instance by (plugin) id.
*
* @param string $id
* The plugin id of the filter to load.
*
* @return \Drupal\config_filter\Config\StorageFilterInterface
* The ConfigFilter.
*/
public function getFilterInstance($id);
}
@@ -0,0 +1,93 @@
<?php
namespace Drupal\config_filter;
use Drupal\config_filter\Config\FilteredStorage;
use Drupal\Core\Config\StorageInterface;
/**
* Class ConfigFilterFactory.
*/
class ConfigFilterStorageFactory {
/**
* The decorated sync config storage.
*
* @var \Drupal\Core\Config\StorageInterface
*/
protected $sync;
/**
* The filter managers to load the filters from.
*
* @var \Drupal\config_filter\ConfigFilterManagerInterface[]
*/
protected $managers = [];
/**
* ConfigFilterFactory constructor.
*
* @param \Drupal\Core\Config\StorageInterface $sync
* The original sync storage which is decorated by our filtered storage.
*/
public function __construct(StorageInterface $sync) {
$this->sync = $sync;
}
/**
* Add a config filter manager.
*
* @param \Drupal\config_filter\ConfigFilterManagerInterface $manager
* The ConfigFilter plugin manager.
*/
public function addConfigFilterManager(ConfigFilterManagerInterface $manager) {
$this->managers[] = $manager;
}
/**
* Get the sync storage Drupal uses.
*
* @return \Drupal\config_filter\Config\FilteredStorageInterface
* The decorated sync config storage.
*/
public function getSync() {
return $this->getFilteredStorage($this->sync, ['config.storage.sync']);
}
/**
* Get the sync storage Drupal uses and exclude some plugins.
*
* @param string[] $excluded
* The ids of filters to exclude.
*
* @return \Drupal\config_filter\Config\FilteredStorageInterface
* The decorated sync config storage.
*/
public function getSyncWithoutExcluded(array $excluded) {
return $this->getFilteredStorage($this->sync, ['config.storage.sync'], $excluded);
}
/**
* Get a decorated storage with filters applied.
*
* @param \Drupal\Core\Config\StorageInterface $storage
* The storage to decorate.
* @param string[] $storage_names
* The names of the storage, so the correct filters can be applied.
* @param string[] $excluded
* The ids of filters to exclude.
*
* @return \Drupal\config_filter\Config\FilteredStorageInterface
* The decorated storage with the filters applied.
*/
public function getFilteredStorage(StorageInterface $storage, array $storage_names, array $excluded = []) {
$filters = [];
foreach ($this->managers as $manager) {
// Filters from managers that come first will not be overwritten by
// filters from lower priority managers.
$filters = $filters + $manager->getFiltersForStorages($storage_names, $excluded);
}
return new FilteredStorage($storage, $filters);
}
}
@@ -0,0 +1,9 @@
<?php
namespace Drupal\config_filter\Exception;
/**
* Thrown when calling an unsupported method on a ConfigStorage object.
*/
class UnsupportedMethod extends \BadMethodCallException {
}
@@ -0,0 +1,145 @@
<?php
namespace Drupal\config_filter\Plugin;
use Drupal\Component\Plugin\PluginBase;
use Drupal\Core\Config\StorageInterface;
/**
* Base class for Config filter plugin plugins.
*/
abstract class ConfigFilterBase extends PluginBase implements ConfigFilterInterface {
/**
* The read-only source storage on which the filter operations are performed.
*
* @var \Drupal\Core\Config\StorageInterface
*/
protected $source;
/**
* The wrapped storage which calls the filter.
*
* @var \Drupal\Core\Config\StorageInterface
*/
protected $filtered;
/**
* {@inheritdoc}
*/
public function setSourceStorage(StorageInterface $storage) {
$this->source = $storage;
}
/**
* {@inheritdoc}
*/
public function setFilteredStorage(StorageInterface $storage) {
$this->filtered = $storage;
}
/**
* Get the read-only source Storage.
*
* @return \Drupal\Core\Config\StorageInterface
* The source storage.
*/
protected function getSourceStorage() {
return $this->source;
}
/**
* Get the decorator storage which applies the filters.
*
* @return \Drupal\Core\Config\StorageInterface
* The filtered decorator storage.
*/
protected function getFilteredStorage() {
return $this->filtered;
}
/**
* {@inheritdoc}
*/
public function filterRead($name, $data) {
return $data;
}
/**
* {@inheritdoc}
*/
public function filterWrite($name, array $data) {
return $data;
}
/**
* {@inheritdoc}
*/
public function filterWriteEmptyIsDelete($name) {
return NULL;
}
/**
* {@inheritdoc}
*/
public function filterExists($name, $exists) {
return $exists;
}
/**
* {@inheritdoc}
*/
public function filterDelete($name, $delete) {
return $delete;
}
/**
* {@inheritdoc}
*/
public function filterReadMultiple(array $names, array $data) {
return $data;
}
/**
* {@inheritdoc}
*/
public function filterRename($name, $new_name, $rename) {
return $rename;
}
/**
* {@inheritdoc}
*/
public function filterListAll($prefix, array $data) {
return $data;
}
/**
* {@inheritdoc}
*/
public function filterDeleteAll($prefix, $delete) {
return $delete;
}
/**
* {@inheritdoc}
*/
public function filterCreateCollection($collection) {
return clone $this;
}
/**
* {@inheritdoc}
*/
public function filterGetAllCollectionNames(array $collections) {
return $collections;
}
/**
* {@inheritdoc}
*/
public function filterGetCollectionName($collection) {
return $collection;
}
}
@@ -0,0 +1,13 @@
<?php
namespace Drupal\config_filter\Plugin;
use Drupal\Component\Plugin\PluginInspectionInterface;
use Drupal\config_filter\Config\StorageFilterInterface;
/**
* Defines an interface for Config filter plugin plugins.
*/
interface ConfigFilterInterface extends PluginInspectionInterface, StorageFilterInterface {
}
@@ -0,0 +1,80 @@
<?php
namespace Drupal\config_filter\Plugin;
use Drupal\config_filter\ConfigFilterManagerInterface;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
/**
* Provides the Config filter plugin plugin manager.
*/
class ConfigFilterPluginManager extends DefaultPluginManager implements ConfigFilterManagerInterface {
/**
* Constructor for ConfigFilterPluginManager objects.
*
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
* Cache backend instance to use.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler to invoke the alter hook with.
*/
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
parent::__construct('Plugin/ConfigFilter', $namespaces, $module_handler, 'Drupal\config_filter\Plugin\ConfigFilterInterface', 'Drupal\config_filter\Annotation\ConfigFilter');
$this->alterInfo('config_filter_info');
$this->setCacheBackend($cache_backend, 'config_filter_plugins');
}
/**
* {@inheritdoc}
*/
public function getFiltersForStorages(array $storage_names, array $excluded = []) {
$definitions = $this->getDefinitions();
$filters = [];
foreach ($definitions as $id => $definition) {
if ($definition['status'] && array_intersect($storage_names, $definition['storages']) && !in_array($id, $excluded)) {
$filters[$id] = $this->createInstance($id, $definition);
}
}
return $filters;
}
/**
* {@inheritdoc}
*/
public function getFilterInstance($id) {
$definitions = $this->getDefinitions();
if (array_key_exists($id, $definitions)) {
return $this->createInstance($id, $definitions[$id]);
}
return NULL;
}
/**
* {@inheritdoc}
*/
protected function findDefinitions() {
$definitions = array_map(function ($definition) {
if (empty($definition['storages'])) {
// The sync storage is the default.
$definition['storages'] = ['config.storage.sync'];
}
return $definition;
}, parent::findDefinitions());
// Sort the definitions by weight.
uasort($definitions, function ($a, $b) {
return strcmp($a['weight'], $b['weight']);
});
return $definitions;
}
}
@@ -0,0 +1,298 @@
<?php
namespace Drupal\config_filter\Tests;
use Drupal\config_filter\Config\FilteredStorage;
use Drupal\config_filter\Config\ReadOnlyStorage;
use Drupal\config_filter\Config\StorageFilterInterface;
use Drupal\Core\Config\CachedStorage;
use Drupal\Core\Config\StorageInterface;
use Drupal\KernelTests\Core\Config\Storage\CachedStorageTest;
use Prophecy\Argument;
/**
* Tests StorageWrapper operations using the CachedStorage.
*
* @group config_filter
*/
class FilteredStorageTest extends CachedStorageTest {
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// The storage is a wrapper with a transparent filter.
// So all inherited tests should still pass.
$this->storage = new FilteredStorage($this->storage, [new TransparentFilter()]);
}
/**
* Test that the storage is set on the filters.
*/
public function testSettingStorages() {
$filterReflection = new \ReflectionClass(FilteredStorage::class);
$filtersProperty = $filterReflection->getProperty('filters');
$filtersProperty->setAccessible(TRUE);
/** @var \Drupal\config_filter\Tests\TransparentFilter[] $filters */
$filters = $filtersProperty->getValue($this->storage);
foreach ($filters as $filter) {
// Test that the source storage is a ReadonlyStorage and wraps the cached
// storage from the inherited test.
$readonly = $filter->getPrivateSourceStorage();
$this->assertInstanceOf(ReadOnlyStorage::class, $readonly);
$readonlyReflection = new \ReflectionClass(ReadOnlyStorage::class);
$storageProperty = $readonlyReflection->getProperty('storage');
$storageProperty->setAccessible(TRUE);
$source = $storageProperty->getValue($readonly);
$this->assertInstanceOf(CachedStorage::class, $source);
// Assert that the filter gets the storage.
$this->assertEquals($this->storage, $filter->getPrivateFilteredStorage());
}
}
/**
* Test the read methods invokes the correct filter methods.
*
* @dataProvider readFilterProvider
*/
public function testReadFilter($name, $storageMethod, $filterMethod, $data, $expected) {
$source = $this->prophesize(StorageInterface::class);
$filterA = $this->prophesizeFilter();
$filterB = $this->prophesizeFilter();
$source->$storageMethod($name)->willReturn($data);
$interim = $this->randomArray();
$filterA->$filterMethod($name, $data)->willReturn($interim);
$filterB->$filterMethod($name, $interim)->willReturn($expected);
$storage = new FilteredStorage($source->reveal(), [$filterA->reveal(), $filterB->reveal()]);
$this->assertEquals($expected, $storage->$storageMethod($name));
}
/**
* Data provider for testReadFilter.
*/
public function readFilterProvider() {
// @codingStandardsIgnoreStart
return [
[$this->randomString(), 'exists', 'filterExists', TRUE, TRUE],
[$this->randomString(), 'exists', 'filterExists', TRUE, FALSE],
[$this->randomString(), 'exists', 'filterExists', FALSE, TRUE],
[$this->randomString(), 'exists', 'filterExists', FALSE, FALSE],
[$this->randomString(), 'read', 'filterRead', $this->randomArray(), $this->randomArray()],
[$this->randomString(), 'read', 'filterRead', NULL, $this->randomArray()],
[$this->randomString(), 'read', 'filterRead', $this->randomArray(), NULL],
[
[$this->randomString(), $this->randomString()],
'readMultiple',
'filterReadMultiple',
[$this->randomArray(), $this->randomArray()],
[$this->randomArray(), $this->randomArray()],
],
[
[$this->randomString(), $this->randomString()],
'readMultiple',
'filterReadMultiple',
[$this->randomArray(), FALSE],
[$this->randomArray(), $this->randomArray()],
],
[
$this->randomString(),
'listAll',
'filterListAll',
['a' . $this->randomString(), 'b' . $this->randomString()],
['a' . $this->randomString(), 'b' . $this->randomString()],
],
];
// @codingStandardsIgnoreEnd
}
/**
* Test the write method invokes the filterWrite in filters.
*
* @dataProvider writeFilterProvider
*/
public function testWriteFilter($interim, $expected, $exists = TRUE) {
$name = $this->randomString();
$data = $this->randomArray();
$source = $this->prophesize(StorageInterface::class);
$filterA = $this->prophesizeFilter();
$filterB = $this->prophesizeFilter();
$filterA->filterWrite($name, $data)->willReturn($interim);
$interim = is_array($interim) ? $interim : [];
$filterB->filterWrite($name, $interim)->willReturn($expected);
if ($expected) {
$source->write($name, $expected)->willReturn(TRUE);
}
else {
$source->write(Argument::any())->shouldNotBeCalled();
$source->exists($name)->willReturn($exists);
if ($exists) {
$filterA->filterWriteEmptyIsDelete($name)->willReturn(TRUE);
$source->delete($name)->willReturn(TRUE);
}
}
$storage = new FilteredStorage($source->reveal(), [$filterA->reveal(), $filterB->reveal()]);
$this->assertTrue($storage->write($name, $data));
}
/**
* Data provider for testWriteFilter.
*/
public function writeFilterProvider() {
return [
[$this->randomArray(), $this->randomArray()],
[NULL, $this->randomArray()],
[[], $this->randomArray()],
[$this->randomArray(), NULL, FALSE],
[$this->randomArray(), [], FALSE],
[$this->randomArray(), NULL, TRUE],
];
}
/**
* Test the delete method invokes the filterDelete in filters.
*
* @dataProvider deleteFilterProvider
*/
public function testDeleteFilter($interim, $expected) {
$name = $this->randomString();
$source = $this->prophesize(StorageInterface::class);
$filterA = $this->prophesizeFilter();
$filterB = $this->prophesizeFilter();
$filterA->filterDelete($name, TRUE)->willReturn($interim);
$filterB->filterDelete($name, $interim)->willReturn($expected);
if ($expected) {
$source->delete($name)->willReturn(TRUE);
}
else {
$source->delete(Argument::any())->shouldNotBeCalled();
}
$storage = new FilteredStorage($source->reveal(), [$filterA->reveal(), $filterB->reveal()]);
$this->assertEquals($expected, $storage->delete($name));
}
/**
* Data provider for testDeleteFilter.
*/
public function deleteFilterProvider() {
return [
[TRUE, TRUE],
[FALSE, TRUE],
[TRUE, FALSE],
[FALSE, FALSE],
];
}
/**
* Test the rename method invokes the filterRename in filters.
*
* @dataProvider renameFilterProvider
*/
public function testRenameFilter($interim, $expected) {
$name = $this->randomString();
$name2 = $this->randomString();
$source = $this->prophesize(StorageInterface::class);
$filterA = $this->prophesizeFilter();
$filterB = $this->prophesizeFilter();
$filterA->filterRename($name, $name2, TRUE)->willReturn($interim);
$filterB->filterRename($name, $name2, $interim)->willReturn($expected);
if ($expected) {
$source->rename($name, $name2)->willReturn(TRUE);
}
else {
$source->rename(Argument::any())->shouldNotBeCalled();
}
$storage = new FilteredStorage($source->reveal(), [$filterA->reveal(), $filterB->reveal()]);
$this->assertEquals($expected, $storage->rename($name, $name2));
}
/**
* Data provider for testRenameFilter.
*/
public function renameFilterProvider() {
return [
[TRUE, TRUE],
[FALSE, TRUE],
[TRUE, FALSE],
[FALSE, FALSE],
];
}
/**
* Test the deleteAll method invokes the filterDeleteAll in filters.
*
* @dataProvider deleteAllFilterProvider
*/
public function testDeleteAllFilter($interim, $expected) {
$name = $this->randomString();
$source = $this->prophesize(StorageInterface::class);
$filterA = $this->prophesizeFilter();
$filterB = $this->prophesizeFilter();
$filterA->filterDeleteAll($name, TRUE)->willReturn($interim);
$filterB->filterDeleteAll($name, $interim)->willReturn($expected);
if ($expected) {
$source->deleteAll($name)->willReturn(TRUE);
}
else {
$source->deleteAll(Argument::any())->shouldNotBeCalled();
$all = [$this->randomString(), $this->randomString()];
$source->listAll($name)->willReturn($all);
foreach ($all as $item) {
$filterA->filterDelete($item, TRUE)->willReturn(TRUE);
$filterB->filterDelete($item, TRUE)->willReturn(FALSE);
}
}
$storage = new FilteredStorage($source->reveal(), [$filterA->reveal(), $filterB->reveal()]);
$this->assertTrue($storage->deleteAll($name));
}
/**
* Data provider for testDeleteAllFilter.
*/
public function deleteAllFilterProvider() {
return [
[TRUE, TRUE],
[FALSE, TRUE],
[TRUE, FALSE],
[FALSE, FALSE],
];
}
/**
* Prophesize a StorageFilter.
*/
protected function prophesizeFilter() {
$filter = $this->prophesize(StorageFilterInterface::class);
$filter->setSourceStorage(Argument::type(ReadOnlyStorage::class))->shouldBeCalledTimes(1);
$filter->setFilteredStorage(Argument::type(FilteredStorage::class))->shouldBeCalledTimes(1);
return $filter;
}
/**
* Create a random array.
*/
protected function randomArray($size = 4) {
return (array) $this->randomObject($size);
}
}
@@ -0,0 +1,138 @@
<?php
namespace Drupal\config_filter\Tests;
use Drupal\config_filter\Config\ReadOnlyStorage;
use Drupal\config_filter\Exception\UnsupportedMethod;
use Drupal\Core\Config\StorageInterface;
use Drupal\Tests\UnitTestCase;
use Prophecy\Argument;
use Prophecy\Prophecy\MethodProphecy;
/**
* Tests ReadonlyStorage operations.
*
* @group config_filter
*/
class ReadonlyStorageTest extends UnitTestCase {
/**
* Wrap a given storage.
*
* This is useful when testing a subclass of ReadonlyStorage.
*
* @param \Drupal\Core\Config\StorageInterface $source
* The storage to decorate.
*
* @return \Drupal\Core\Config\StorageInterface
* The storage wrapping the source.
*/
protected function getStorage(StorageInterface $source) {
return new ReadOnlyStorage($source);
}
/**
* Test methods that should be transparent.
*
* @dataProvider readMethodsProvider
*/
public function testReadOperations($method, $arguments, $returnValue) {
$source = $this->prophesize(StorageInterface::class);
$methodProhecy = new MethodProphecy($source, $method, $arguments);
$methodProhecy->shouldBeCalledTimes(1);
$methodProhecy->willReturn($returnValue);
$source->addMethodProphecy($methodProhecy);
$storage = $this->getStorage($source->reveal());
$actual = call_user_func_array([$storage, $method], $arguments);
$this->assertEquals($actual, $returnValue);
}
/**
* Provide the methods that should continue to work.
*
* @return array
* The data.
*/
public function readMethodsProvider() {
return [
['exists', [$this->randomMachineName()], $this->randomMachineName()],
['read', [$this->randomMachineName()], $this->randomArray()],
['readMultiple', [$this->randomArray()], $this->randomArray()],
['encode', [$this->randomArray()], $this->randomMachineName()],
['decode', [$this->randomMachineName()], $this->randomArray()],
['listAll', [$this->randomMachineName()], $this->randomArray()],
['getAllCollectionNames', [], $this->randomArray()],
['getCollectionName', [], $this->randomMachineName()],
];
}
/**
* Test creating a collection.
*
* Creating collections returns a new instance, make sure it decorates the
* new instance of the source.
*/
public function testCreateCollection() {
$name = $this->randomMachineName();
$source = $this->prophesize(StorageInterface::class);
$collectionSource = $this->prophesize(StorageInterface::class)->reveal();
$source->createCollection($name)->willReturn($collectionSource);
$storage = $this->getStorage($source->reveal());
$collectionStorage = $storage->createCollection($name);
$this->assertInstanceOf(ReadOnlyStorage::class, $collectionStorage);
$readonlyReflection = new \ReflectionClass(ReadOnlyStorage::class);
$storageProperty = $readonlyReflection->getProperty('storage');
$storageProperty->setAccessible(TRUE);
$actualSource = $storageProperty->getValue($collectionStorage);
$this->assertEquals($collectionSource, $actualSource);
}
/**
* Test the operations that should throw an error.
*
* @dataProvider writeMethodsProvider
*/
public function testWriteOperations($method, $arguments) {
$source = $this->prophesize(StorageInterface::class);
$source->$method(Argument::any())->shouldNotBeCalled();
$storage = $this->getStorage($source->reveal());
try {
call_user_func_array([$storage, $method], $arguments);
$this->fail();
}
catch (UnsupportedMethod $exception) {
$this->assertEquals(ReadOnlyStorage::class . '::' . $method . ' is not allowed on a ReadOnlyStorage', $exception->getMessage());
}
}
/**
* Provide the methods that should throw an exception.
*
* @return array
* The data
*/
public function writeMethodsProvider() {
return [
['write', [$this->randomMachineName(), $this->randomArray()]],
['delete', [$this->randomMachineName()]],
['rename', [$this->randomMachineName(), $this->randomMachineName()]],
['deleteAll', [$this->randomMachineName()]],
];
}
/**
* Get a random array.
*
* @return array
* A random array used for data testing.
*/
protected function randomArray() {
return (array) $this->getRandomGenerator()->object();
}
}
@@ -0,0 +1,40 @@
<?php
namespace Drupal\config_filter\Tests;
use Drupal\config_filter\Config\StorageFilterInterface;
use Drupal\config_filter\Plugin\ConfigFilterBase;
/**
* Class TransparentFilter.
*/
class TransparentFilter extends ConfigFilterBase implements StorageFilterInterface {
/**
* TransparentFilter constructor.
*/
public function __construct() {
parent::__construct([], 'transparent_test', []);
}
/**
* Get the read-only source Storage.
*
* @return \Drupal\Core\Config\StorageInterface
* The source storage.
*/
public function getPrivateSourceStorage() {
return $this->getSourceStorage();
}
/**
* Get the decorator storage which applies the filters.
*
* @return \Drupal\Core\Config\StorageInterface
* The filtered decorator storage.
*/
public function getPrivateFilteredStorage() {
return $this->getFilteredStorage();
}
}
@@ -0,0 +1,24 @@
{
"name": "nuvoleweb/config-filter-test-site",
"description": "Config Filter test site.",
"type": "project",
"require": {
"composer/installers": "^1.2",
"drupal-composer/drupal-scaffold": "^2.2",
"drupal/core": "~8",
"drush/drush": "~8.0",
"drupal/coder": "^8.2",
"squizlabs/php_codesniffer": "^2.8",
"phpunit/phpunit": "5.5.*",
"bovigo/assert": "~1.7",
"mikey179/vfsStream": "*"
},
"minimum-stability": "dev",
"prefer-stable": true,
"repositories": [
{"type": "composer", "url": "https://packages.drupal.org/8"}
],
"conflict": {
"drupal/drupal": "*"
}
}
@@ -0,0 +1,40 @@
# Config Filter Split test
This is a module for testing the proper functioning of config_filter.
But it also demonstrates how a filter can be used to read and write to a
different config storage. As such it is an instructive example.
But it also has a practical purpose: We can use it to read migrate config
directly from files. Of course it is in general a bad idea to directly
edit the active configuration since Drupal does many checks and database
alterations when synchronizing configuration.
This is just here for the brave. You have been warned.
## Developing with migrate configuration read from the files.
Migrations are configurations, by default active configuration is stored in the
database. So when developing, the configuration has to be synced all the time.
To avoid this we can split out the migrate configuration to read directly
from the files.
Enable the config_filter_split_test testing module from config_filter.
To swap out the active storage, add the following to your services.local.yml:
```yaml
services:
config.storage:
class: Drupal\config_filter_split_test\Config\ActiveMigrateStorage
arguments:
- '@config.storage.active'
- '@cache.config'
- '../config/migrate_active' # The path to the folder.
```
Migrations are plugins, and plugin definitions are cached. The configuration
storage is also cached.
So either you have to clear the caches after you edit the migration or you add
the following to your settings.php (assuming you have the null cache set too):
```php
$settings['cache']['bins']['config'] = 'cache.backend.null';
$settings['cache']['bins']['discovery'] = 'cache.backend.null';
```
This has an obvious performance implication.
@@ -0,0 +1,12 @@
name: 'Simple split filter'
type: module
# core: 8.x
package: Testing
dependencies:
- drupal:config_filter
# Information added by Drupal.org packaging script on 2017-11-27
version: '8.x-1.1'
core: '8.x'
project: 'config_filter'
datestamp: 1511779987
@@ -0,0 +1,34 @@
<?php
namespace Drupal\config_filter_migrate_test\Config;
use Drupal\config_filter\Config\FilteredStorage;
use Drupal\config_filter_split_test\Plugin\ConfigFilter\TestSplitFilter;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Config\CachedStorage;
use Drupal\Core\Config\FileStorage;
use Drupal\Core\Config\StorageInterface;
/**
* Class ActiveMigrateStorage.
*/
class ActiveMigrateStorage extends CachedStorage {
/**
* Create an ActiveMigrateStorage.
*
* @param \Drupal\Core\Config\StorageInterface $storage
* The decorated storage.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache
* A cache backend used to store configuration.
* @param string $migrate_folder
* The migrate directory.
*/
public function __construct(StorageInterface $storage, CacheBackendInterface $cache, $migrate_folder) {
// Create the filter directly, the plugin manager is not yet available.
$filter = new TestSplitFilter(new FileStorage($migrate_folder), 'migrate_plus.migration');
// Wrap the storage with the the filtered storage.
parent::__construct(new FilteredStorage($storage, [$filter]), $cache);
}
}
@@ -0,0 +1,167 @@
<?php
namespace Drupal\config_filter_split_test\Plugin\ConfigFilter;
use Drupal\config_filter\Plugin\ConfigFilterBase;
use Drupal\Core\Config\DatabaseStorage;
use Drupal\Core\Config\StorageInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a TestSplitFilter.
*
* This is a very basic split filter to test that config_filter applies the
* filters correctly. For more advanced and configurable split filters use the
* Configuration Split (config_split) module.
*
* @ConfigFilter(
* id = "config_filter_split_test",
* label = @Translation("Filter Split test"),
* storages = {"test_storage"},
* )
*/
class TestSplitFilter extends ConfigFilterBase implements ContainerFactoryPluginInterface {
/**
* The File storage to read the migrations from.
*
* @var \Drupal\Core\Config\StorageInterface
*/
protected $storage;
/**
* The name prefix to split.
*
* @var string
*/
protected $name;
/**
* Constructs a new TestSplitFilter.
*
* @param \Drupal\Core\Config\StorageInterface $storage
* The migrate storage.
* @param string $name
* The config name prefix to split.
*/
public function __construct(StorageInterface $storage, $name) {
parent::__construct([], 'config_filter_split_test', []);
$this->storage = $storage;
$this->name = $name;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(new DatabaseStorage($container->get('database'), 'config_filter_split_test'), 'core.');
}
/**
* Decide to split the config off or not.
*
* @param string $name
* The name of the configuration to check.
*
* @return bool
* Whether the configuration is supposed to be split.
*/
protected function isSplitConfig($name) {
return (strpos($name, $this->name) === 0);
}
/**
* {@inheritdoc}
*/
public function filterRead($name, $data) {
if ($this->isSplitConfig($name)) {
if ($this->storage->exists($name)) {
$data = $this->storage->read($name);
}
}
return $data;
}
/**
* {@inheritdoc}
*/
public function filterExists($name, $exists) {
if ($this->isSplitConfig($name) && !$exists) {
$exists = $this->storage->exists($name);
}
return $exists;
}
/**
* {@inheritdoc}
*/
public function filterReadMultiple(array $names, array $data) {
return array_merge($data, $this->storage->readMultiple($names));
}
/**
* {@inheritdoc}
*/
public function filterListAll($prefix, array $data) {
return array_unique(array_merge($data, $this->storage->listAll($prefix)));
}
/**
* {@inheritdoc}
*/
public function filterWrite($name, array $data) {
if ($this->isSplitConfig($name)) {
$this->storage->write($name, $data);
return NULL;
}
return $data;
}
/**
* {@inheritdoc}
*/
public function filterWriteEmptyIsDelete($name) {
return ($this->isSplitConfig($name) ? TRUE : NULL);
}
/**
* {@inheritdoc}
*/
public function filterDelete($name, $delete) {
if ($delete && $this->storage->exists($name)) {
// Call delete on the secondary storage anyway.
$this->storage->delete($name);
}
return $delete;
}
/**
* {@inheritdoc}
*/
public function filterDeleteAll($prefix, $delete) {
if ($delete && $this->storage) {
try {
$this->storage->deleteAll($prefix);
}
catch (\UnexpectedValueException $exception) {
// The file storage tries to remove directories of collections. But this
// fails if the directory doesn't exist. So everything is actually fine.
}
}
return $delete;
}
/**
* {@inheritdoc}
*/
public function filterCreateCollection($collection) {
return new static($this->storage->createCollection($collection), $this->name);
}
}
@@ -0,0 +1,12 @@
name: Config Filter Test
type: module
# core: 8.x
package: Testing
dependencies:
- drupal:config_filter
# Information added by Drupal.org packaging script on 2017-11-27
version: '8.x-1.1'
core: '8.x'
project: 'config_filter'
datestamp: 1511779987
@@ -0,0 +1,40 @@
<?php
namespace Drupal\config_filter_test\plugin\ConfigFilter;
use Drupal\config_filter\Plugin\ConfigFilterBase;
/**
* Provides a pirate filter that adds "Arrr" to the site name.
*
* @ConfigFilter(
* id = "pirate_filter",
* label = "More pirates! Arrr",
* weight = 10
* )
*/
class PirateFilter extends ConfigFilterBase {
/**
* {@inheritdoc}
*/
public function filterRead($name, $data) {
if ($name == 'system.site') {
$data['name'] = $data['name'] . ' Arrr';
}
return $data;
}
/**
* {@inheritdoc}
*/
public function filterReadMultiple(array $names, array $data) {
if (in_array('system.site', $names)) {
$data['system.site'] = $this->filterRead('system.site', $data['system.site']);
}
return $data;
}
}
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="core/tests/bootstrap.php" backupGlobals="false" colors="true" >
<php>
<ini name="error_reporting" value="32767"/>
<ini name="memory_limit" value="-1"/>
<env name="SIMPLETEST_DB" value="mysql://travis:@127.0.0.1/drupal"/>
</php>
<testsuites>
<testsuite name="tests">
<directory>./modules/config_filter/</directory>
</testsuite>
</testsuites>
</phpunit>
@@ -0,0 +1,88 @@
<?php
namespace Drupal\Tests\config_filter\Kernel;
use Drupal\Core\Config\DatabaseStorage;
use Drupal\KernelTests\KernelTestBase;
/**
* Class ConfigFilterStorageFactoryTest.
*
* @group config_filter
*/
class ConfigFilterStorageFactoryTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = [
'system',
'config_filter',
'config_filter_test',
'config_filter_split_test',
];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installConfig(['system']);
}
/**
* Test that the config.storage.sync is decorated with the filtering version.
*/
public function testServiceProvider() {
// Export the configuration.
$this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.sync'));
// The pirate filter changes the system.site when importing.
$this->assertEquals(['system.site'], $this->configImporter()->getStorageComparer()->getChangelist('update'));
$this->assertEmpty($this->configImporter()->getStorageComparer()->getChangelist('create'));
$this->assertEmpty($this->configImporter()->getStorageComparer()->getChangelist('delete'));
$this->assertEmpty($this->configImporter()->getStorageComparer()->getChangelist('rename'));
$config = $this->config('system.site')->getRawData();
$config['name'] .= ' Arrr';
$this->assertEquals($config, $this->container->get('config.storage.sync')->read('system.site'));
}
/**
* Test the storage factory decorating properly.
*/
public function testStorageFactory() {
/** @var \Drupal\Core\Database\Connection $database */
$database = $this->container->get('database');
$destination = new DatabaseStorage($database, 'config_filter_source_test');
// The $filtered storage will have the simple split applied to the
// destination storage, but is the unified storage.
$filtered = $this->container->get('config_filter.storage_factory')->getFilteredStorage($destination, ['test_storage']);
/** @var \Drupal\Core\Config\StorageInterface $active */
$active = $this->container->get('config.storage');
// Export the configuration to the filtered storage.
$this->copyConfig($active, $filtered);
// Get the storage of the test split plugin.
$splitStorage = new DatabaseStorage($database, 'config_filter_split_test');
// Assert that the storage is properly split.
$this->assertTrue(count($destination->listAll()) > 0);
$this->assertTrue(count($splitStorage->listAll()) > 0);
$this->assertEquals(count($active->listAll()), count($destination->listAll()) + count($splitStorage->listAll()));
$this->assertEquals($active->listAll('core'), $splitStorage->listAll());
$this->assertEquals($active->listAll('system'), $destination->listAll('system'));
$this->assertEquals($active->readMultiple($active->listAll('core')), $splitStorage->readMultiple($splitStorage->listAll()));
$this->assertEquals($active->readMultiple($active->listAll('system')), $destination->readMultiple($destination->listAll('system')));
// Reading from the $filtered storage returns the merged config.
$this->assertEquals($active->listAll(), $filtered->listAll());
$this->assertEquals($active->readMultiple($active->listAll()), $filtered->readMultiple($filtered->listAll()));
}
}
@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
@@ -0,0 +1,41 @@
Config Ignore
=============
INTRODUCTION
------------
Ever experienced that your sites configuration was overridden by the configuration on the filesystem, when doing a
`drush cim`?
Not anymore!
This modules is a tool to let you keep the configuration you want, in place.
Lets say that you do would like the `system.site` configuration (which contains that sites name, slogan, email, etc) to
remain untouched, on your live site, no matter what the configuration, in the export folder says.
Or maybe you are getting tired of having the `devel.settings` changed every time you import configuration?
Then this module is what you are looking for.
REQUIREMENTS
------------
You will need the `config_filter` module to be enabled.
INSTALLATION
------------
Consult https://www.drupal.org/docs/8/extending-drupal-8/installing-contributed-modules-find-import-enable-configure-drupal-8
to see how to install and manage modules in Drupal 8.
CONFIGURATION
-------------
Go to `admin/config/development/configuration/ignore` to set what configuration you want to ignore upon import.
Do not ignore the `core.extension` configuration as it will prevent you from enabling new modules with a config import.
Use the `config_split` module for environment specific modules.
MAINTAINERS
-----------
Current maintainers:
* Tommy Lynge Jørgensen (TLyngeJ) - https://www.drupal.org/u/tlyngej
* Fabian Bircher (bircher) - https://www.drupal.org/u/bircher
@@ -0,0 +1,29 @@
{
"name": "drupal/config_ignore",
"description": "Ignore certain configuration during import.",
"type": "drupal-module",
"homepage": "http://drupal.org/project/config_ignore",
"authors": [
{
"name": "Tommy Lynge Jørgensen",
"email": "tlyngej@gmail.com",
"homepage": "https://www.drupal.org/u/tlyngej",
"role": "Maintainer"
},
{
"name": "Fabian Bircher",
"homepage": "https://www.drupal.org/u/bircher",
"role": "Maintainer"
}
],
"support": {
"issues": "http://drupal.org/project/config_ignore",
"irc": "irc://irc.freenode.org/drupal-contribute",
"source": "http://cgit.drupalcode.org/config_ignore"
},
"license": "GPL-2.0+",
"minimum-stability": "dev",
"require": {
"drupal/config_filter": "1.*"
}
}
@@ -0,0 +1 @@
ignored_config_entities: { }
@@ -0,0 +1,9 @@
config_ignore.settings:
type: config_object
label: 'Config Ignore Settings'
mapping:
ignored_config_entities:
type: sequence
label: 'List of ignored configurations'
sequence:
type: string
@@ -0,0 +1,23 @@
<?php
/**
* @file
* Hooks specific to the Config Ignore module.
*/
/**
* @addtogroup hooks
* @{
*/
/**
* Alter the list of config entities that should be ignored.
*/
function hook_config_ignore_settings_alter(array &$settings) {
$settings[] = 'system.site';
$settings[] = 'field.*';
}
/**
* @} End of "addtogroup hooks".
*/
@@ -0,0 +1,14 @@
name: Config Ignore
type: module
description: Ignore certain configuration during import
# core: 8.x
package: Config
configure: config_ignore.settings
dependencies:
- config_filter
# Information added by Drupal.org packaging script on 2017-10-11
version: '8.x-2.1'
core: '8.x'
project: 'config_ignore'
datestamp: 1507706047
@@ -0,0 +1,13 @@
<?php
/**
* @file
* Install, update and uninstall functions for the config_ignore module.
*/
/**
* Enable the config_filter module.
*/
function config_ignore_update_8201() {
\Drupal::getContainer()->get('module_installer')->install(['config_filter']);
}
@@ -0,0 +1,5 @@
config_ignore.settings:
route_name: 'config_ignore.settings'
title: 'Ignore'
base_route: config.sync
weight: 99
@@ -0,0 +1,70 @@
<?php
/**
* @file
* Hooks implemented by the config_ignore module.
*/
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Config\StorageInterface;
use Drupal\Core\Config\StorageComparer;
use Drupal\Core\Config\FileStorage;
use Drupal\Core\Url;
/**
* Implements hook_form_FORM_ID_alter().
*/
function config_ignore_form_config_admin_import_form_alter(&$form, FormStateInterface $form_state, $form_id) {
// Load Services that we need.
$stock_storage_sync = \Drupal::service('config_filter.storage_factory')->getSyncWithoutExcluded(['config_ignore']);
$active_storage_sync = \Drupal::service('config.storage.sync');
$storage = \Drupal::service('config.storage');
$config_manager = \Drupal::service('config.manager');
// Create two StorageComparer objects, one with the filter enabled and one
// as without. We will compare them later to see what changes that has been
// ignored.
$unfiltered_storage_compare = new StorageComparer($stock_storage_sync, $storage, $config_manager);
$filtered_storage_compare = new StorageComparer($active_storage_sync, $storage, $config_manager);
$unfiltered_storage_compare->createChangelist();
$filtered_storage_compare->createChangelist();
// Create an array of the changes with the filter on.
$config_changes = [];
foreach ($filtered_storage_compare->getChangelist() as $config_names) {
foreach ($config_names as $config_name) {
$config_changes[] = $config_name;
}
}
foreach ($unfiltered_storage_compare->getAllCollectionNames() as $collection) {
foreach ($unfiltered_storage_compare->getChangelist(NULL, $collection) as $config_change_type => $config_names) {
foreach ($config_names as $config_name) {
// If the config name exists here, but not in the $config_changes array
// the it's because it's getting ignored.
if (!in_array($config_name, $config_changes)) {
$ignored_config_entities[] = [
$config_name,
$config_change_type,
];
}
}
}
}
// Build a table of changes that are not going to happen, due to the ignored
// config entities.
if (!empty($ignored_config_entities)) {
$form['ignored'] = [
'#type' => 'table',
'#header' => ['Config name', 'Action'],
'#caption' => t('<h3>The following configuration entities are ignored due to the <a href="@url">Config Ignore Settings</a> and therefore not displayed in the list above</h3>', [
'@url' => Url::fromRoute('config_ignore.settings')
->toString()
]),
'#rows' => $ignored_config_entities,
];
}
}
@@ -0,0 +1,7 @@
config_ignore.settings:
path: '/admin/config/development/configuration/ignore'
defaults:
_form: '\Drupal\config_ignore\Form\Settings'
_title: 'Ignore'
requirements:
_permission: 'import configuration'
@@ -0,0 +1,76 @@
<?php
namespace Drupal\config_ignore\Form;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Provides a setting UI for Config Ignore.
*
* @package Drupal\config_ignore\Form
*/
class Settings extends ConfigFormBase {
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return [
'config_ignore.settings',
];
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'config_ignore_settings';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, Request $request = NULL) {
$description = $this->t('One configuration name per line.<br />
Examples: <ul>
<li>user.settings</li>
<li>views.settings</li>
<li>contact.settings</li>
<li>webform.webform.* (will ignore all config entities that starts with <em>webform.webform</em>)</li>
<li>*.contact_message.custom_contact_form.* (will ignore all config entities that starts with <em>.contact_message.custom_contact_form.</em> like fields attached to a custom contact form)</li>
<li>* (will ignore everything)</li>
<li>~webform.webform.contact (will force import for this configuration, even if ignored by a wildcard)</li>
<li>user.mail:register_no_approval_required.body (will ignore the body of the no approval required email setting, but will not ignore other user.mail configuration.)</li>
</ul>');
$config_ignore_settings = $this->config('config_ignore.settings');
$form['ignored_config_entities'] = [
'#type' => 'textarea',
'#rows' => 25,
'#title' => $this->t('Configuration entity names to ignore'),
'#description' => $description,
'#default_value' => implode(PHP_EOL, $config_ignore_settings->get('ignored_config_entities')),
'#size' => 60,
];
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$values = $form_state->getValues();
$config_ignore_settings = $this->config('config_ignore.settings');
$config_ignore_settings_array = preg_split("[\n|\r]", $values['ignored_config_entities']);
$config_ignore_settings_array = array_filter($config_ignore_settings_array);
$config_ignore_settings->set('ignored_config_entities', $config_ignore_settings_array);
$config_ignore_settings->save();
parent::submitForm($form, $form_state);
// Clear the config_filter plugin cache.
\Drupal::service('plugin.manager.config_filter')->clearCachedDefinitions();
}
}
@@ -0,0 +1,236 @@
<?php
namespace Drupal\config_ignore\Plugin\ConfigFilter;
use Drupal\Component\Utility\NestedArray;
use Drupal\config_filter\Plugin\ConfigFilterBase;
use Drupal\Core\Config\StorageInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Site\Settings;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a ignore filter that reads partly from the active storage.
*
* @ConfigFilter(
* id = "config_ignore",
* label = "Config Ignore",
* weight = 100
* )
*/
class IgnoreFilter extends ConfigFilterBase implements ContainerFactoryPluginInterface {
const FORCE_EXCLUSION_PREFIX = '~';
const INCLUDE_SUFFIX = '*';
/**
* The active configuration storage.
*
* @var \Drupal\Core\Config\StorageInterface
*/
protected $active;
/**
* Constructs a new SplitFilter.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param array $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Config\StorageInterface $active
* The active configuration store with the configuration on the site.
*/
public function __construct(array $configuration, $plugin_id, array $plugin_definition, StorageInterface $active) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->active = $active;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
// Get the list of ignored config.
$ignored = $container->get('config.factory')->get('config_ignore.settings')->get('ignored_config_entities');
// Allow hooks to alter the list.
$container->get('module_handler')->invokeAll('config_ignore_settings_alter', [&$ignored]);
// Set the list in the plugin configuration.
$configuration['ignored'] = $ignored;
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('config.storage')
);
}
/**
* Match a config entity name against the list of ignored config entities.
*
* @param string $config_name
* The name of the config entity to match against all ignored entities.
*
* @return bool
* True, if the config entity is to be ignored, false otherwise.
*/
protected function matchConfigName($config_name) {
if (Settings::get('config_ignore_deactivate')) {
// Allow deactivating config_ignore in settings.php. Do not match any name
// in that case and allow a normal configuration import to happen.
return FALSE;
}
// If the string is an excluded config, don't ignore it.
if (in_array(static::FORCE_EXCLUSION_PREFIX . $config_name, $this->configuration['ignored'], TRUE)) {
return FALSE;
}
foreach ($this->configuration['ignored'] as $config_ignore_setting) {
// Split the ignore settings so that we can ignore individual keys.
$ignore = explode(':', $config_ignore_setting);
if (fnmatch($ignore[0], $config_name)) {
return TRUE;
}
}
return FALSE;
}
/**
* Read from the active configuration.
*
* This method will read the configuration from the active config store.
* But rather than just straight up returning the value it will check if
* a nested config key is set to be ignored and set only that value on the
* data to be filtered.
*
* @param string $name
* The name of the configuration to read.
* @param mixed $data
* The data to be filtered.
*
* @return mixed
* The data filtered or read from the active storage.
*/
protected function activeRead($name, $data) {
$keys = [];
foreach ($this->configuration['ignored'] as $ignored) {
// Split the ignore settings so that we can ignore individual keys.
$ignored = explode(':', $ignored);
if (fnmatch($ignored[0], $name)) {
if (count($ignored) == 1) {
// If one of the definitions does not have keys ignore the
// whole config.
return $this->active->read($name);
}
else {
// Add the sub parts to ignore to the keys.
$keys[] = $ignored[1];
}
}
}
$active = $this->active->read($name);
foreach ($keys as $key) {
$parts = explode('.', $key);
if (count($parts) == 1) {
if (isset($active[$key])) {
$data[$key] = $active[$key];
}
}
else {
$value = NestedArray::getValue($active, $parts, $key_exists);
if ($key_exists) {
// Enforce the value if it existed in the active config.
NestedArray::setValue($data, $parts, $value, TRUE);
}
}
}
return $data;
}
/**
* Read multiple from the active storage.
*
* @param array $names
* The names of the configuration to read.
* @param array $data
* The data to filter.
*
* @return array
* The new data.
*/
protected function activeReadMultiple(array $names, array $data) {
$filtered_data = [];
foreach ($names as $name) {
$filtered_data[$name] = $this->activeRead($name, $data[$name]);
}
return $filtered_data;
}
/**
* {@inheritdoc}
*/
public function filterRead($name, $data) {
// Read from the active storage when the name is in the ignored list.
if ($this->matchConfigName($name)) {
return $this->activeRead($name, $data);
}
return $data;
}
/**
* {@inheritdoc}
*/
public function filterExists($name, $exists) {
// A name exists if it is ignored and exists in the active storage.
return $exists || ($this->matchConfigName($name) && $this->active->exists($name));
}
/**
* {@inheritdoc}
*/
public function filterReadMultiple(array $names, array $data) {
// Limit the names which are read from the active storage.
$names = array_filter($names, [$this, 'matchConfigName']);
$active_data = $this->activeReadMultiple($names, $data);
// Return the data with merged in active data.
return array_merge($data, $active_data);
}
/**
* {@inheritdoc}
*/
public function filterListAll($prefix, array $data) {
$active_names = $this->active->listAll($prefix);
// Filter out only ignored config names.
$active_names = array_filter($active_names, [$this, 'matchConfigName']);
// Return the data with the active names which are ignored merged in.
return array_unique(array_merge($data, $active_names));
}
/**
* {@inheritdoc}
*/
public function filterCreateCollection($collection) {
return new static($this->configuration, $this->pluginId, $this->pluginDefinition, $this->active->createCollection($collection));
}
/**
* {@inheritdoc}
*/
public function filterGetAllCollectionNames(array $collections) {
// Add active collection names as there could be ignored config in them.
return array_merge($collections, $this->active->getAllCollectionNames());
}
}
@@ -0,0 +1,13 @@
name: Config Ignore Hook Test
type: module
description: Module that implements all the hook from Config Ignore for testing purposes
# core: 8.x
package: Configuration
dependencies:
- config_ignore
# Information added by Drupal.org packaging script on 2017-10-11
version: '8.x-2.1'
core: '8.x'
project: 'config_ignore'
datestamp: 1507706047
@@ -0,0 +1,13 @@
<?php
/**
* @file
* Module that implements all the hook from Config Ignore for testing purposes.
*/
/**
* Implements hook_config_ignore_settings_alter().
*/
function config_ignore_hook_test_config_ignore_settings_alter(array &$settings) {
$settings[] = 'system.site';
}
@@ -0,0 +1,66 @@
<?php
namespace Drupal\Tests\config_ignore\Functional;
use Drupal\Core\Config\ConfigImporter;
use Drupal\Core\Config\FileStorage;
use Drupal\Core\Config\StorageComparer;
use Drupal\Tests\BrowserTestBase;
/**
* Class ConfigIgnoreBrowserTestBase.
*
* @package Drupal\Tests\config_ignore
*/
abstract class ConfigIgnoreBrowserTestBase extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['config_ignore', 'config', 'config_filter'];
/**
* Perform a config import from sync. folder.
*/
public function doImport() {
// Set up the ConfigImporter object for testing.
$storage_comparer = new StorageComparer(
$this->container->get('config.storage.sync'),
$this->container->get('config.storage'),
$this->container->get('config.manager')
);
$config_importer = new ConfigImporter(
$storage_comparer->createChangelist(),
$this->container->get('event_dispatcher'),
$this->container->get('config.manager'),
$this->container->get('lock'),
$this->container->get('config.typed'),
$this->container->get('module_handler'),
$this->container->get('module_installer'),
$this->container->get('theme_handler'),
$this->container->get('string_translation')
);
$config_importer->reset()->import();
}
/**
* Perform a config export to sync. folder.
*/
public function doExport() {
// Setup a config sync. dir with a, more or less, know set of config
// entities. This is a full blown export of yaml files, written to the disk.
$destination = CONFIG_SYNC_DIRECTORY;
$destination_dir = config_get_config_directory($destination);
/** @var \Drupal\Core\Config\CachedStorage $source_storage */
$source_storage = \Drupal::service('config.storage');
$destination_storage = new FileStorage($destination_dir);
foreach ($source_storage->listAll() as $name) {
$destination_storage->write($name, $source_storage->read($name));
}
}
}
@@ -0,0 +1,47 @@
<?php
namespace Drupal\Tests\config_ignore\Functional;
/**
* Test hook implementation of another module.
*
* @package Drupal\Tests\config_ignore\Functional
*
* @group config_ignore
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
*/
class ConfigIgnoreHookTest extends ConfigIgnoreBrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = [
'config_ignore',
'config',
'config_filter',
'config_ignore_hook_test'
];
/**
* Test hook implementation of another module.
*/
public function testSettingsAlterHook() {
$this->config('system.site')->set('name', 'Test import')->save();
$this->doExport();
$this->config('system.site')->set('name', 'Changed title')->save();
$this->doImport();
// Test if the `config_ignore_hook_test` module got to ignore the site name
// config.
$this->assertEquals('Changed title', $this->config('system.site')->get('name'));
}
}
@@ -0,0 +1,196 @@
<?php
namespace Drupal\Tests\config_ignore\Functional;
use Drupal\config_ignore\Plugin\ConfigFilter\IgnoreFilter;
/**
* Test functionality of config_ignore module.
*
* @package Drupal\Tests\config_ignore\Functional
*
* @group config_ignore
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
*/
class ConfigIgnoreTest extends ConfigIgnoreBrowserTestBase {
/**
* Verify that the Sync. table gets update with appropriate ignore actions.
*/
public function testSyncTableUpdate() {
$this->config('system.site')->set('name', 'Test import')->save();
$this->config('system.date')->set('first_day', '0')->save();
$this->config('config_ignore.settings')->set('ignored_config_entities', ['system.site'])->save();
$this->doExport();
// Login with a user that has permission to sync. config.
$this->drupalLogin($this->drupalCreateUser(['synchronize configuration']));
// Change the site name, which is supposed to look as an ignored change
// in on the sync. page.
$this->config('system.site')->set('name', 'Test import with changed title')->save();
$this->config('system.date')->set('first_day', '1')->save();
// Validate that the sync. table informs the user that the config will be
// ignored.
$this->drupalGet('admin/config/development/configuration');
$this->assertSession()->linkExists('Config Ignore Settings');
/** @var \Behat\Mink\Element\NodeElement[] $table_content */
$table_content = $this->xpath('//table[@id="edit-ignored"]//td');
$table_values = [];
foreach ($table_content as $item) {
$table_values[] = $item->getHtml();
}
$this->assertTrue(in_array('system.site', $table_values));
$this->assertFalse(in_array('system.date', $table_values));
}
/**
* Verify that the settings form works.
*/
public function testSettingsForm() {
// Login with a user that has permission to import config.
$this->drupalLogin($this->drupalCreateUser(['import configuration']));
$edit = [
'ignored_config_entities' => 'config.test',
];
$this->drupalGet('admin/config/development/configuration/ignore');
$this->submitForm($edit, t('Save configuration'));
$settings = $this->config('config_ignore.settings')->get('ignored_config_entities');
$this->assertEqual($settings, ['config.test']);
}
/**
* Verify that config can get ignored.
*/
public function testValidateIgnoring() {
// Set the site name to a known value that we later will try and overwrite.
$this->config('system.site')->set('name', 'Test import')->save();
// Set the system.site:name to be ignored upon config import.
$this->config('config_ignore.settings')->set('ignored_config_entities', ['system.site'])->save();
$this->doExport();
// Change the site name, perform an import and see if the site name remains
// the same, as it should.
$this->config('system.site')->set('name', 'Changed title')->save();
$this->doImport();
$this->assertEquals('Changed title', $this->config('system.site')->get('name'));
}
/**
* Verify all wildcard asterisk is working.
*/
public function testValidateIgnoringWithWildcard() {
// Set the site name to a known value that we later will try and overwrite.
$this->config('system.site')->set('name', 'Test import')->save();
// Set the system.site:name to be ignored upon config import.
$this->config('config_ignore.settings')->set('ignored_config_entities', ['system.' . IgnoreFilter::INCLUDE_SUFFIX])->save();
$this->doExport();
// Change the site name, perform an import and see if the site name remains
// the same, as it should.
$this->config('system.site')->set('name', 'Changed title')->save();
$this->doImport();
$this->assertEquals('Changed title', $this->config('system.site')->get('name'));
}
/**
* Verify Force Import syntax is working.
*
* This test makes sure we avoid regression issues.
*/
public function testValidateForceImporting() {
// Set the site name to a known value that we later will try and overwrite.
$this->config('system.site')->set('name', 'Test import')->save();
// Set the system.site:name to be (force-) imported upon config import.
$settings = [IgnoreFilter::FORCE_EXCLUSION_PREFIX . 'system.site'];
$this->config('config_ignore.settings')->set('ignored_config_entities', $settings)->save();
$this->doExport();
// Change the site name, perform an import and see if the site name remains
// the same, as it should.
$this->config('system.site')->set('name', 'Changed title')->save();
$this->doImport();
$this->assertEquals('Test import', $this->config('system.site')->get('name'));
}
/**
* Verify excluded configuration works with wildcards.
*
* This test cover the scenario where a wildcard matches a specific
* configuration, but that's still imported due exclusion.
*/
public function testValidateForceImportingWithWildcard() {
// Set the site name to a known value that we later will try and overwrite.
$this->config('system.site')->set('name', 'Test import')->save();
// Set the system.site:name to be (force-) imported upon config import.
$settings = ['system.' . IgnoreFilter::INCLUDE_SUFFIX, IgnoreFilter::FORCE_EXCLUSION_PREFIX . 'system.site'];
$this->config('config_ignore.settings')->set('ignored_config_entities', $settings)->save();
$this->doExport();
// Change the site name, perform an import and see if the site name remains
// the same, as it should.
$this->config('system.site')->set('name', 'Changed title')->save();
$this->doImport();
$this->assertEquals('Test import', $this->config('system.site')->get('name'));
}
/**
* Verify ignoring only some config keys.
*
* This test covers the scenario when not the whole config is to be ignored
* but only a certain subset of it.
*/
public function testValidateImportingWithIgnoredSubKeys() {
// Set the site name to a known value that we later will try and overwrite.
$this->config('system.site')
->set('name', 'Test name')
->set('slogan', 'Test slogan')
->set('page.front', '/ignore')
->save();
// Set the system.site:name to be (force-) imported upon config import.
$settings = ['system.site:name', 'system.site:page.front'];
$this->config('config_ignore.settings')->set('ignored_config_entities', $settings)->save();
$this->doExport();
// Change the site name, perform an import and see if the site name remains
// the same, as it should.
$this->config('system.site')
->set('name', 'Changed title')
->set('slogan', 'Changed slogan')
->set('page.front', '/new-ignore')
->save();
$this->doImport();
$this->assertEquals('Changed title', $this->config('system.site')->get('name'));
$this->assertEquals('Test slogan', $this->config('system.site')->get('slogan'));
$this->assertEquals('/new-ignore', $this->config('system.site')->get('page.front'));
}
}
@@ -0,0 +1,6 @@
ignored_config_entities:
0: system.performance
2: devel.settings
_core:
default_config_hash: UVH1aJ4b44UM-VdPVN7hNNuuVqfReJxwfVeDQH1Hvsk
langcode: fr
@@ -12,6 +12,8 @@ module:
color_field: 0
config: 0
config_devel: 0
config_filter: 0
config_ignore: 0
config_update: 0
context: 0
context_ui: 0
@@ -1,8 +1,8 @@
cache:
page:
max_age: 0
max_age: 300
css:
preprocess: false
preprocess: true
gzip: true
fast_404:
enabled: true
@@ -10,7 +10,7 @@ fast_404:
exclude_paths: '/\/(?:styles|imagecache)\//'
html: '<!DOCTYPE html><html><head><title>404 Not Found</title></head><body><h1>Not Found</h1><p>The requested URL "@path" was not found on this server.</p></body></html>'
js:
preprocess: false
preprocess: true
gzip: true
stale_file_threshold: 2592000
_core: