Update to Drupal 8.2.2. For more information, see https://www.drupal.org/project/drupal/releases/8.2.2

This commit is contained in:
Pantheon Automation 2016-11-02 11:43:31 -07:00 committed by Greg Anderson
parent 23ffed3665
commit 507b45a0ed
378 changed files with 11434 additions and 5542 deletions
core
.eslintrcMAINTAINERS.txt
includes
lib
misc
modules
action/tests/src
Kernel/Plugin/migrate/source
Unit/Plugin/migrate/source
aggregator/tests/src
ban/tests/src
Kernel/Plugin/migrate/source/d7
Unit/Plugin/migrate/source/d7
block
src
tests/src
Kernel/Plugin/migrate/source
Unit/Plugin/migrate/source
block_content/tests/src
Kernel/Plugin/migrate/source
Unit/Plugin/migrate/source
block_place
book
src
tests/src
FunctionalJavascript
Kernel/Plugin/migrate/source/d6
Unit/Plugin/migrate/source/d6
ckeditor
src/Plugin/CKEditorPlugin
tests
modules
src/Kernel/Plugin/CKEditorPlugin
contact
content_moderation
datetime/src
datetime_range
editor
field

View file

@ -80,7 +80,7 @@
"space-infix-ops": 2,
"space-unary-ops": [2, { "words": true, "nonwords": false }],
"spaced-comment": [2, "always"],
"strict": 2,
"strict": [2, "function"],
"yoda": [2, "never"],
// Warnings.
"max-nested-callbacks": [1, 3],

View file

@ -206,6 +206,8 @@ Entity API
- Wolfgang Ziegler 'fago' https://www.drupal.org/u/fago
- Nathaniel Catchpole 'catch' https://www.drupal.org/u/catch
- Sascha Grossenbacher 'Berdir' https://www.drupal.org/u/berdir
- Francesco Placella 'plach' https://www.drupal.org/u/plach
- Tobias Stöckler 'tstoeckler' https://www.drupal.org/u/tstoeckler
Extension API
- ?
@ -277,11 +279,13 @@ Migrate
- Ben Dougherty 'benjy' https://www.drupal.org/u/benjy
- Michael Anello 'ultimike' https://www.drupal.org/u/ultimike
- Mike Ryan 'mikeryan' https://www.drupal.org/u/mikeryan
- Vicki Spagnolo 'quietone' https://www.drupal.org/u/quietone
Migrate (Drupal)
- Ben Dougherty 'benjy' https://www.drupal.org/u/benjy
- Michael Anello 'ultimike' https://www.drupal.org/u/ultimike
- Mike Ryan 'mikeryan' https://www.drupal.org/u/mikeryan
- Vicki Spagnolo 'quietone' https://www.drupal.org/u/quietone
Menu
- Daniel Wehner 'dawehner' https://www.drupal.org/u/dawehner
@ -366,11 +370,6 @@ Shortcut
- Tobias Stöckler 'tstoeckler' https://www.drupal.org/u/tstoeckler
- Jibran Ijaz 'jibran' https://www.drupal.org/u/jibran
Simpletest
- Daniel F. Kudwien 'sun' https://www.drupal.org/u/sun
- Sascha Grossenbacher 'Berdir' https://www.drupal.org/u/berdir
- Alex Pott 'alexpott' https://www.drupal.org/u/alexpott
Stable
- Scott Reeves 'Cottser' https://www.drupal.org/u/cottser
@ -394,12 +393,17 @@ Taxonomy
Telephone
- Dave Reid 'dave-reid' https://www.drupal.org/u/dave-reid
Testing framework
- Alex Pott 'alexpott' https://www.drupal.org/u/alexpott
- Sascha Grossenbacher 'Berdir' https://www.drupal.org/u/berdir
- Klaus Purer 'klausi' https://www.drupal.org/u/klausi
- Daniel Wehner 'dawehner' https://www.drupal.org/u/dawehner
Text Field
- ?
Theme API
- Alex Bronstein 'effulgentsia' https://www.drupal.org/u/effulgentsia
- Scott Reeves 'Cottser' https://www.drupal.org/u/cottser
- Fabian Franz 'Fabianx' https://www.drupal.org/u/fabianx
- Joël Pittet 'joelpittet' https://www.drupal.org/u/joelpittet
- Lauri Eskola 'lauriii' https://www.drupal.org/u/lauriii
@ -523,7 +527,6 @@ participate in mentoring.
- Lucas Hedding 'heddn' https://www.drupal.org/u/heddn
- Valery Lourie 'valthebald' https://www.drupal.org/u/valthebald
- Alina Mackenzie 'alimac' https://www.drupal.org/u/alimac
- Chris McCafferty 'cilefen' https://www.drupal.org/u/cilefen
- Jess Myrbo 'xjm' https://www.drupal.org/u/xjm
- Andrea Soper 'ZenDoodles' https://www.drupal.org/u/zendoodles
- Cathy Theys 'YesCT' https://www.drupal.org/u/yesct

View file

@ -511,8 +511,6 @@ function template_preprocess_form_element_label(&$variables) {
$variables['title'] = ['#markup' => $element['#title']];
}
$variables['attributes'] = array();
// Pass elements title_display to template.
$variables['title_display'] = $element['#title_display'];

View file

@ -930,27 +930,35 @@ function drupal_requirements_url($severity) {
*/
function drupal_check_profile($profile) {
$info = install_profile_info($profile);
// Collect requirement testing results.
$requirements = array();
// Performs an ExtensionDiscovery scan as the system module is unavailable and
// we don't yet know where all the modules are located.
// @todo Remove as part of https://www.drupal.org/node/2186491
$listing = new ExtensionDiscovery(\Drupal::root());
$module_list = $listing->scan('module');
foreach ($info['dependencies'] as $module) {
$file = \Drupal::root() . '/' . $module_list[$module]->getPath() . "/$module.install";
if (is_file($file)) {
require_once $file;
}
$function = $module . '_requirements';
$drupal_root = \Drupal::root();
$module_list = (new ExtensionDiscovery($drupal_root))->scan('module');
drupal_classloader_register($module, $module_list[$module]->getPath());
if (function_exists($function)) {
$requirements = array_merge($requirements, $function('install'));
foreach ($info['dependencies'] as $module) {
// If the module is in the module list we know it exists and we can continue
// including and registering it.
// @see \Drupal\Core\Extension\ExtensionDiscovery::scanDirectory()
if (isset($module_list[$module])) {
$function = $module . '_requirements';
$module_path = $module_list[$module]->getPath();
$install_file = "$drupal_root/$module_path/$module.install";
if (is_file($install_file)) {
require_once $install_file;
}
drupal_classloader_register($module, $module_path);
if (function_exists($function)) {
$requirements = array_merge($requirements, $function('install'));
}
}
}
return $requirements;
}

View file

@ -81,7 +81,7 @@ class Drupal {
/**
* The current system version.
*/
const VERSION = '8.2.1';
const VERSION = '8.2.2';
/**
* Core API compatibility.

View file

@ -288,7 +288,6 @@ class Random {
// Make a perfect circle in the image middle.
$color = imagecolorallocate($im, rand(0, 255), rand(0, 255), rand(0, 255));
$smaller_dimension = min($width, $height);
$smaller_dimension = ($smaller_dimension % 2) ? $smaller_dimension : $smaller_dimension;
imageellipse($im, $width / 2, $height / 2, $smaller_dimension, $smaller_dimension, $color);
$save_function = 'image' . ($extension == 'jpg' ? 'jpeg' : $extension);

View file

@ -97,12 +97,15 @@ class CsrfRequestHeaderAccessCheck implements AccessCheckInterface {
&& $account->isAuthenticated()
&& $this->sessionConfiguration->hasSession($request)
) {
if (!$request->headers->has('X-CSRF-Token')) {
return AccessResult::forbidden()->setReason('X-CSRF-Token request header is missing')->setCacheMaxAge(0);
}
$csrf_token = $request->headers->get('X-CSRF-Token');
// @todo Remove validate call using 'rest' in 8.3.
// Kept here for sessions active during update.
if (!$this->csrfToken->validate($csrf_token, self::TOKEN_KEY)
&& !$this->csrfToken->validate($csrf_token, 'rest')) {
return AccessResult::forbidden()->setReason('X-CSRF-Token request header is missing')->setCacheMaxAge(0);
return AccessResult::forbidden()->setReason('X-CSRF-Token request header is invalid')->setCacheMaxAge(0);
}
}
// Let other access checkers decide if the request is legit.

View file

@ -2,6 +2,8 @@
namespace Drupal\Core\Ajax;
use Drupal\Component\Render\PlainTextOutput;
/**
* Defines an AJAX command to open certain content in a dialog.
*
@ -69,6 +71,7 @@ class OpenDialogCommand implements CommandInterface, CommandWithAttachedAssetsIn
* populated automatically from the current request.
*/
public function __construct($selector, $title, $content, array $dialog_options = array(), $settings = NULL) {
$title = PlainTextOutput::renderFromHtml($title);
$dialog_options += array('title' => $title);
$this->selector = $selector;
$this->content = $content;

View file

@ -214,7 +214,7 @@ class AssetResolver implements AssetResolverInterface {
// hook_library_info_alter(). Additionally add the current language to
// support translation of JavaScript files via hook_js_alter().
$libraries_to_load = $this->getLibrariesToLoad($assets);
$cid = 'js:' . $theme_info->getName() . ':' . $this->languageManager->getCurrentLanguage()->getId() . ':' . Crypt::hashBase64(serialize($libraries_to_load) . serialize($assets->getLibraries())) . (int) (count($assets->getSettings()) > 0) . (int) $optimize;
$cid = 'js:' . $theme_info->getName() . ':' . $this->languageManager->getCurrentLanguage()->getId() . ':' . Crypt::hashBase64(serialize($libraries_to_load)) . (int) (count($assets->getSettings()) > 0) . (int) $optimize;
if ($cached = $this->cache->get($cid)) {
list($js_assets_header, $js_assets_footer, $settings, $settings_in_header) = $cached->data;

View file

@ -2,6 +2,8 @@
namespace Drupal\Core\Cache\Context;
use Drupal\Component\Utility\Crypt;
/**
* Defines the SessionCacheContext service, for "per session" caching.
*
@ -20,7 +22,8 @@ class SessionCacheContext extends RequestStackCacheContextBase {
* {@inheritdoc}
*/
public function getContext() {
return $this->requestStack->getCurrentRequest()->getSession()->getId();
$sid = $this->requestStack->getCurrentRequest()->getSession()->getId();
return Crypt::hashBase64($sid);
}
}

View file

@ -120,7 +120,7 @@ class Schema extends DatabaseSchema {
// By default, MySQL uses the default collation for new tables, which is
// 'utf8mb4_general_ci' for utf8mb4. If an alternate collation has been
// set, it needs to be explicitly specified.
// @see DatabaseConnection_mysql
// @see \Drupal\Core\Database\Driver\mysql\Schema
if (!empty($info['collation'])) {
$sql .= ' COLLATE ' . $info['collation'];
}

View file

@ -20,8 +20,8 @@ class Schema extends DatabaseSchema {
/**
* A cache of information about blob columns and sequences of tables.
*
* This is collected by DatabaseConnection_pgsql->queryTableInformation(),
* by introspecting the database.
* This is collected by Schema::queryTableInformation(), by introspecting the
* database.
*
* @see \Drupal\Core\Database\Driver\pgsql\Schema::queryTableInformation()
* @var array

View file

@ -306,7 +306,7 @@ class StatementPrefetch implements \Iterator, StatementInterface {
return $this->fetchOptions['object'];
case \PDO::FETCH_COLUMN:
if (isset($this->columnNames[$this->fetchOptions['column']])) {
return $this->currentRow[$k][$this->columnNames[$this->fetchOptions['column']]];
return $this->currentRow[$this->columnNames[$this->fetchOptions['column']]];
}
else {
return;

View file

@ -301,7 +301,7 @@ class EntityController implements ContainerInjectionInterface {
* @param array $bundles
* An array of bundle information.
* @param \Drupal\Core\Entity\EntityTypeInterface $bundle_entity_type
* The ID of the bundle entity type.
* The bundle entity type definition.
*
* @return array
* The expanded array of bundle information.

View file

@ -236,7 +236,7 @@ class EntityFormDisplay extends EntityDisplayBase implements EntityFormDisplayIn
// Flag entity level violations.
foreach ($violations->getEntityViolations() as $violation) {
/** @var \Symfony\Component\Validator\ConstraintViolationInterface $violation */
$form_state->setErrorByName('', $violation->getMessage());
$form_state->setError($form, $violation->getMessage());
}
$this->flagWidgetsErrorsFromViolations($violations, $form, $form_state);

View file

@ -166,7 +166,10 @@ class EntityResolverManager {
list($entity_type) = explode('.', $entity_form, 2);
}
if (isset($entity_type) && isset($this->getEntityTypes()[$entity_type])) {
// Do not add parameter information if the route does not declare a
// parameter in the first place. This is the case for add forms, for
// example.
if (isset($entity_type) && isset($this->getEntityTypes()[$entity_type]) && (strpos($route->getPath(), '{' . $entity_type . '}') !== FALSE)) {
$parameter_definitions = $route->getOption('parameters') ?: array();
// First try to figure out whether there is already a parameter upcasting

View file

@ -358,11 +358,18 @@ use Drupal\node\Entity\NodeType;
* \Drupal\Core\Entity\EntityType.
*
* @section sec_routes Entity routes
* Entity routes, like other routes, are defined in *.routing.yml files; see
* the @link routing Routing API @endlink topic for more information. Entities
* may alternatively use an auto route provider class; there is an example of
* this at the end of this section. If providing routes directly, here is a
* typical entry, for the block configure form:
* Entity routes can be defined in *.routing.yml files, like any other route:
* see the @link routing Routing API @endlink topic for more information.
* Another option for entity routes is to use a route provider class, and
* reference it in the annotations on the entity class: see the end of this
* section for an example.
*
* It's possible to use both a YAML file and a provider class for entity
* routes, at the same time. Avoid duplicating route names between the two:
* if a duplicate route name is found in both locations, the one in the YAML
* file takes precedence; regardless, such duplication can be confusing.
*
* Here's an example YAML route specification, for the block configure form:
* @code
* entity.block.edit_form:
* path: '/admin/structure/block/manage/{block}'
@ -372,7 +379,7 @@ use Drupal\node\Entity\NodeType;
* requirements:
* _entity_access: 'block.update'
* @endcode
* Some notes:
* Some notes on this example:
* - path: The {block} in the path is a placeholder, which (for an entity) must
* always take the form of {machine_name_of_entity_type}. In the URL, the
* placeholder value will be the ID of an entity item. When the route is used,
@ -389,19 +396,21 @@ use Drupal\node\Entity\NodeType;
* "form" = {
* "default" = "Drupal\block\BlockForm",
* @endcode
* - Instead of putting the routes for your entity in a *.routing.yml file, you
* can instead use a route provider class.
* \Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider provides canonical,
* edit-form, and delete-form routes;
* \Drupal\Core\Entity\Routing\AdminHtmlRouteProvider provides the same
* If instead of YAML you want to use a route provider class:
* - \Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider provides canonical,
* edit-form, and delete-form routes.
* - \Drupal\Core\Entity\Routing\AdminHtmlRouteProvider provides the same
* routes, set up to use the administrative theme for edit and delete pages.
* You can also create your own class. To use a route provider class, add
* lines like the following to your entity annotation:
* @code
* handlers = {
* "route_provider" = {
* "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
* @endcode
* - You can also create your own class, extending one of these two classes if
* you only want to modify their behaviour slightly.
*
* To register any route provider class, add lines like the following to your
* entity class annotation:
* @code
* handlers = {
* "route_provider" = {
* "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
* @endcode
*
* @section bundle Defining a content entity bundle
* For entity types that use bundles, such as Node (bundles are content types)

View file

@ -20,7 +20,7 @@ class PathRootsSubscriber implements EventSubscriberInterface {
*
* @var array
*/
protected $pathRoots;
protected $pathRoots = [];
/**
* The state key value store.
@ -58,7 +58,7 @@ class PathRootsSubscriber implements EventSubscriberInterface {
*/
public function onRouteFinished() {
$this->state->set('router.path_roots', array_keys($this->pathRoots));
unset($this->pathRoots);
$this->pathRoots = [];
}
/**

View file

@ -22,6 +22,8 @@ use Drupal\Core\TypedData\ListInterface;
*
* When implementing this interface which extends Traversable, make sure to list
* IteratorAggregate or Iterator before this interface in the implements clause.
*
* @see \Drupal\Core\Field\FieldItemInterface
*/
interface FieldItemListInterface extends ListInterface, AccessibleInterface {

View file

@ -268,7 +268,26 @@ class EntityReferenceItem extends FieldItemBase implements OptionsProviderInterf
*/
public static function generateSampleValue(FieldDefinitionInterface $field_definition) {
$manager = \Drupal::service('plugin.manager.entity_reference_selection');
if ($referenceable = $manager->getSelectionHandler($field_definition)->getReferenceableEntities()) {
// Instead of calling $manager->getSelectionHandler($field_definition)
// replicate the behavior to be able to override the sorting settings.
$options = array(
'target_type' => $field_definition->getFieldStorageDefinition()->getSetting('target_type'),
'handler' => $field_definition->getSetting('handler'),
'handler_settings' => $field_definition->getSetting('handler_settings') ?: array(),
'entity' => NULL,
);
$entity_type = \Drupal::entityManager()->getDefinition($options['target_type']);
$options['handler_settings']['sort'] = [
'field' => $entity_type->getKey('id'),
'direction' => 'DESC',
];
$selection_handler = $manager->getInstance($options);
// Select a random number of references between the last 50 referenceable
// entities created.
if ($referenceable = $selection_handler->getReferenceableEntities(NULL, 'CONTAINS', 50)) {
$group = array_rand($referenceable);
$values['target_id'] = array_rand($referenceable[$group]);
return $values;

View file

@ -87,11 +87,14 @@ class EntityReferenceAutocompleteWidget extends WidgetBase {
$entity = $items->getEntity();
$referenced_entities = $items->referencedEntities();
// Append the match operation to the selection settings.
$selection_settings = $this->getFieldSetting('handler_settings') + ['match_operator' => $this->getSetting('match_operator')];
$element += array(
'#type' => 'entity_autocomplete',
'#target_type' => $this->getFieldSetting('target_type'),
'#selection_handler' => $this->getFieldSetting('handler'),
'#selection_settings' => $this->getFieldSetting('handler_settings'),
'#selection_settings' => $selection_settings,
// Entity reference field items are handling validation themselves via
// the 'ValidReference' constraint.
'#validate_reference' => FALSE,

View file

@ -181,8 +181,10 @@ class FileSystem implements FileSystemInterface {
// If recursive, create each missing component of the parent directory
// individually and set the mode explicitly to override the umask.
if ($recursive) {
// Ensure the path is using DIRECTORY_SEPARATOR.
$uri = str_replace('/', DIRECTORY_SEPARATOR, $uri);
// Ensure the path is using DIRECTORY_SEPARATOR, and trim off any trailing
// slashes because they can throw off the loop when creating the parent
// directories.
$uri = rtrim(str_replace('/', DIRECTORY_SEPARATOR, $uri), DIRECTORY_SEPARATOR);
// Determine the components of the path.
$components = explode(DIRECTORY_SEPARATOR, $uri);
// If the filepath is absolute the first component will be empty as there

View file

@ -152,7 +152,7 @@ class ExtensionMimeTypeGuesser implements MimeTypeGuesserInterface {
129 => 'application/x-iphone',
130 => 'application/x-iso9660-image',
131 => 'application/x-java-jnlp-file',
132 => 'application/x-javascript',
132 => 'application/javascript',
133 => 'application/x-jmol',
134 => 'application/x-kchart',
135 => 'application/x-killustrator',

View file

@ -316,7 +316,7 @@ class FormBuilder implements FormBuilderInterface, FormValidatorInterface, FormS
// In case the post request exceeds the configured allowed size
// (post_max_size), the post request is potentially broken. Add some
// protection against that and at the same time have a nice error message.
if ($ajax_form_request && !isset($form_state->getUserInput()['form_id'])) {
if ($ajax_form_request && !$request->request->has('form_id')) {
throw new BrokenPostRequestException($this->getFileUploadMaxSize());
}
@ -327,7 +327,9 @@ class FormBuilder implements FormBuilderInterface, FormValidatorInterface, FormS
// then passed through
// \Drupal\Core\Form\FormAjaxResponseBuilderInterface::buildResponse() to
// build a proper AJAX response.
if ($ajax_form_request && $form_state->isProcessingInput()) {
// Only do this when the form ID matches, since there is no guarantee from
// $ajax_form_request that it's an AJAX request for this particular form.
if ($ajax_form_request && $form_state->isProcessingInput() && $request->request->get('form_id') == $form_id) {
throw new FormAjaxException($form, $form_state);
}

View file

@ -92,7 +92,7 @@ class Link extends RenderElement {
/** @var \Drupal\Core\Utility\LinkGenerator $link_generator */
$link_generator = \Drupal::service('link_generator');
$generated_link = $link_generator->generate($element['#title'], $element['#url']->setOptions($options));
$element['#markup'] = $generated_link->getGeneratedLink();
$element['#markup'] = $generated_link;
$generated_link->merge(BubbleableMetadata::createFromRenderArray($element))
->applyTo($element);
}

View file

@ -33,7 +33,8 @@ use Drupal\Component\Utility\Html as HtmlUtility;
* '#header' => array($this->t('Name'), $this->t('Phone')),
* );
*
* for ($i=1; $i<=4; $i++) {
* for ($i = 1; $i <= 4; $i++) {
* $form['contacts'][$i]['#attributes'] = array('class' => array('foo', 'baz'));
* $form['contacts'][$i]['name'] = array(
* '#type' => 'textfield',
* '#title' => $this->t('Name'),
@ -46,6 +47,11 @@ use Drupal\Component\Utility\Html as HtmlUtility;
* '#title_display' => 'invisible',
* );
* }
*
* $form['contacts'][]['colspan_example'] = array(
* '#plain_text' => 'Colspan Example',
* '#wrapper_attributes' => array('colspan' => 2, 'class' => array('foo', 'bar')),
* );
* @endcode
* @see \Drupal\Core\Render\Element\Tableselect
*

View file

@ -42,7 +42,12 @@ class ContentTypeHeaderMatcher implements RouteFilterInterface {
// We do not throw a
// \Symfony\Component\Routing\Exception\ResourceNotFoundException here
// because we don't want to return a 404 status code, but rather a 415.
throw new UnsupportedMediaTypeHttpException('No route found that matches "Content-Type: ' . $request->headers->get('Content-Type') . '"');
if (!$request->headers->has('Content-Type')) {
throw new UnsupportedMediaTypeHttpException('No "Content-Type" request header specified');
}
else {
throw new UnsupportedMediaTypeHttpException('No route found that matches "Content-Type: ' . $request->headers->get('Content-Type') . '"');
}
}
/**

View file

@ -257,7 +257,8 @@ class SessionManager extends NativeSessionStorage implements SessionManagerInter
// Unset the session cookies.
$session_name = $this->getName();
$cookies = $this->requestStack->getCurrentRequest()->cookies;
if ($cookies->has($session_name)) {
// setcookie() can only be called when headers are not yet sent.
if ($cookies->has($session_name) && !headers_sent()) {
$params = session_get_cookie_params();
setcookie($session_name, '', REQUEST_TIME - 3600, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
$cookies->remove($session_name);

View file

@ -8,6 +8,7 @@ use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Render\AttachmentsInterface;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Render\Markup;
use Drupal\Core\Render\RenderableInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Routing\UrlGeneratorInterface;
@ -273,6 +274,11 @@ class TwigExtension extends \Twig_Extension {
}
$url->setOption('attributes', $attributes);
}
// The text has been processed by twig already, convert it to a safe object
// for the render system.
if ($text instanceof \Twig_Markup) {
$text = Markup::create($text);
}
$build = [
'#type' => 'link',
'#title' => $text,

View file

@ -0,0 +1,63 @@
<?php
namespace Drupal\Core\Test;
/**
* Consolidates test result status information.
*
* For our test runners, a $status of 0 = passed test, 1 = failed test,
* 2 = exception, >2 indicates segfault timeout, or other type of system
* failure.
*/
class TestStatus {
/**
* Signify that the test result was a passed test.
*/
const PASS = 0;
/**
* Signify that the test result was a failed test.
*/
const FAIL = 1;
/**
* Signify that the test result was an exception or code error.
*
* This means that the test runner was able to exit and report an error.
*/
const EXCEPTION = 2;
/**
* Signify a system error where the test runner was unable to complete.
*
* Note that SYSTEM actually represents the lowest value of system errors, and
* the returned value could be as high as 127. Since that's the case, this
* constant should be used for range comparisons, and not just for equality.
*
* @see http://php.net/manual/en/pcntl.constants.php
*/
const SYSTEM = 3;
/**
* Turns a status code into a human-readable string.
*
* @param int $status
* A test runner return code.
*
* @return string
* The human-readable version of the status code.
*/
public static function label($status) {
$statusMap = [
static::PASS => 'pass',
static::FAIL => 'fail',
static::EXCEPTION => 'exception',
static::SYSTEM => 'error',
];
// For status 3 and higher, we want 'error.'
$label = $statusMap[$status > static::SYSTEM ? static::SYSTEM : $status];
return $label;
}
}

View file

@ -31,7 +31,7 @@ interface LinkGeneratorInterface {
* This keeps the context of the link title ('settings' in the example) for
* translators.
*
* @param string|array $text
* @param string|array|\Drupal\Component\Render\MarkupInterface $text
* The link text for the anchor tag as a translated string or render array.
* Strings will be sanitized automatically. If you need to output HTML in
* the link text, use a render array or an already sanitized string such as

View file

@ -165,7 +165,7 @@
var placement = $el.offset()[horizontal ? 'left' : 'top'];
// Subtract scroll distance from placement to get the distance
// to the edge of the viewport.
placement -= window['scroll' + (horizontal ? 'X' : 'Y')] || document.documentElement['scroll' + (horizontal) ? 'Left' : 'Top'] || 0;
placement -= window['scroll' + (horizontal ? 'X' : 'Y')] || document.documentElement['scroll' + (horizontal ? 'Left' : 'Top')] || 0;
// Find the displacement value according to the edge.
switch (edge) {
// Left and top elements displace as a sum of their own offset value

View file

@ -0,0 +1,62 @@
<?php
namespace Drupal\Tests\action\Kernel\Plugin\migrate\source;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests actions source plugin.
*
* @covers \Drupal\action\Plugin\migrate\source\Action
* @group action
*/
class ActionTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['action', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public function providerSource() {
$tests = [];
$tests[0][0]['actions'] = [
[
'aid' => 'Redirect to node list page',
'type' => 'system',
'callback' => 'system_goto_action',
'parameters' => 'a:1:{s:3:"url";s:4:"node";}',
'description' => 'Redirect to node list page',
],
[
'aid' => 'Test notice email',
'type' => 'system',
'callback' => 'system_send_email_action',
'parameters' => 'a:3:{s:9:"recipient";s:7:"%author";s:7:"subject";s:4:"Test";s:7:"message";s:4:"Test',
'description' => 'Test notice email',
],
[
'aid' => 'comment_publish_action',
'type' => 'comment',
'callback' => 'comment_publish_action',
'parameters' => NULL,
'description' => NULL,
],
[
'aid' => 'node_publish_action',
'type' => 'comment',
'callback' => 'node_publish_action',
'parameters' => NULL,
'description' => NULL,
],
];
// The expected results are identical to the source data.
$tests[0][1] = $tests[0][0]['actions'];
return $tests;
}
}

View file

@ -1,68 +0,0 @@
<?php
namespace Drupal\Tests\action\Unit\Plugin\migrate\source;
use Drupal\Tests\migrate\Unit\MigrateSqlSourceTestCase;
/**
* Tests actions source plugin.
*
* @group action
*/
class ActionTest extends MigrateSqlSourceTestCase {
// The plugin system is not working during unit testing so the source plugin
// class needs to be manually specified.
const PLUGIN_CLASS = 'Drupal\action\Plugin\migrate\source\Action';
// The fake Migration configuration entity.
protected $migrationConfiguration = array(
// The ID of the entity, can be any string.
'id' => 'test',
'source' => array(
'plugin' => 'action',
),
);
// We need to set up the database contents; it's easier to do that below.
protected $expectedResults = array(
array(
'aid' => 'Redirect to node list page',
'type' => 'system',
'callback' => 'system_goto_action',
'parameters' => 'a:1:{s:3:"url";s:4:"node";}',
'description' => 'Redirect to node list page',
),
array(
'aid' => 'Test notice email',
'type' => 'system',
'callback' => 'system_send_email_action',
'parameters' => 'a:3:{s:9:"recipient";s:7:"%author";s:7:"subject";s:4:"Test";s:7:"message";s:4:"Test',
'description' => 'Test notice email',
),
array(
'aid' => 'comment_publish_action',
'type' => 'comment',
'callback' => 'comment_publish_action',
'parameters' => NULL,
'description' => NULL,
),
array(
'aid' => 'node_publish_action',
'type' => 'comment',
'callback' => 'node_publish_action',
'parameters' => NULL,
'description' => NULL,
),
);
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->databaseContents['actions'] = $this->expectedResults;
parent::setUp();
}
}

View file

@ -0,0 +1,60 @@
<?php
namespace Drupal\Tests\aggregator\Kernel\Plugin\migrate\source;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests D6 aggregator feed source plugin.
*
* @covers \Drupal\aggregator\Plugin\migrate\source\AggregatorFeed
* @group aggregator
*/
class AggregatorFeedTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['aggregator', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public function providerSource() {
$tests = [];
$tests[0]['database']['aggregator_feed'] = [
[
'fid' => 1,
'title' => 'feed title 1',
'url' => 'http://example.com/feed.rss',
'refresh' => 900,
'checked' => 0,
'link' => 'http://example.com',
'description' => 'A vague description',
'image' => '',
'etag' => '',
'modified' => 0,
'block' => 5,
],
[
'fid' => 2,
'title' => 'feed title 2',
'url' => 'http://example.net/news.rss',
'refresh' => 1800,
'checked' => 0,
'link' => 'http://example.net',
'description' => 'An even more vague description',
'image' => '',
'etag' => '',
'modified' => 0,
'block' => 5,
],
];
// The expected results are identical to the source data.
$tests[0]['expected_results'] = $tests[0]['database']['aggregator_feed'];
return $tests;
}
}

View file

@ -0,0 +1,44 @@
<?php
namespace Drupal\Tests\aggregator\Kernel\Plugin\migrate\source;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests aggregator item source plugin.
*
* @covers \Drupal\aggregator\Plugin\migrate\source\AggregatorItem
* @group aggregator
*/
class AggregatorItemTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['aggregator', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public function providerSource() {
$tests = [];
$tests[0]['database']['aggregator_item'] = [
[
'iid' => 1,
'fid' => 1,
'title' => 'This (three) weeks in Drupal Core - January 10th 2014',
'link' => 'https://groups.drupal.org/node/395218',
'author' => 'larowlan',
'description' => "<h2 id='new'>What's new with Drupal 8?</h2>",
'timestamp' => 1389297196,
'guid' => '395218 at https://groups.drupal.org',
],
];
// The expected results are identical to the source data.
$tests[0]['expected_results'] = $tests[0]['database']['aggregator_item'];
return $tests;
}
}

View file

@ -1,44 +0,0 @@
<?php
namespace Drupal\Tests\aggregator\Unit\Plugin\migrate\source;
use Drupal\Tests\migrate\Unit\MigrateSqlSourceTestCase;
/**
* Tests aggregator item source plugin.
*
* @group aggregator
*/
class AggregatorItemTest extends MigrateSqlSourceTestCase {
const PLUGIN_CLASS = 'Drupal\aggregator\Plugin\migrate\source\AggregatorItem';
protected $migrationConfiguration = array(
'id' => 'test',
'source' => array(
'plugin' => 'aggregator_item',
),
);
protected $expectedResults = array(
array(
'iid' => 1,
'fid' => 1,
'title' => 'This (three) weeks in Drupal Core - January 10th 2014',
'link' => 'https://groups.drupal.org/node/395218',
'author' => 'larowlan',
'description' => "<h2 id='new'>What's new with Drupal 8?</h2>",
'timestamp' => 1389297196,
'guid' => '395218 at https://groups.drupal.org',
),
);
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->databaseContents['aggregator_item'] = $this->expectedResults;
parent::setUp();
}
}

View file

@ -1,60 +0,0 @@
<?php
namespace Drupal\Tests\aggregator\Unit\Plugin\migrate\source\d6;
use Drupal\Tests\migrate\Unit\MigrateSqlSourceTestCase;
/**
* Tests D6 aggregator feed source plugin.
*
* @group aggregator
*/
class AggregatorFeedTest extends MigrateSqlSourceTestCase {
const PLUGIN_CLASS = 'Drupal\aggregator\Plugin\migrate\source\AggregatorFeed';
protected $migrationConfiguration = array(
'id' => 'test',
'source' => array(
'plugin' => 'd6_aggregator_feed',
),
);
protected $expectedResults = array(
array(
'fid' => 1,
'title' => 'feed title 1',
'url' => 'http://example.com/feed.rss',
'refresh' => 900,
'checked' => 0,
'link' => 'http://example.com',
'description' => 'A vague description',
'image' => '',
'etag' => '',
'modified' => 0,
'block' => 5,
),
array(
'fid' => 2,
'title' => 'feed title 2',
'url' => 'http://example.net/news.rss',
'refresh' => 1800,
'checked' => 0,
'link' => 'http://example.net',
'description' => 'An even more vague description',
'image' => '',
'etag' => '',
'modified' => 0,
'block' => 5,
),
);
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->databaseContents['aggregator_feed'] = $this->expectedResults;
parent::setUp();
}
}

View file

@ -1,62 +0,0 @@
<?php
namespace Drupal\Tests\aggregator\Unit\Plugin\migrate\source\d7;
use Drupal\Tests\migrate\Unit\MigrateSqlSourceTestCase;
/**
* Tests D7 aggregator feed source plugin.
*
* @group aggregator
*/
class AggregatorFeedTest extends MigrateSqlSourceTestCase {
const PLUGIN_CLASS = 'Drupal\aggregator\Plugin\migrate\source\AggregatorFeed';
protected $migrationConfiguration = array(
'id' => 'test',
'source' => array(
'plugin' => 'd7_aggregator_feed',
),
);
protected $expectedResults = array(
array(
'fid' => 1,
'title' => 'feed title 1',
'url' => 'http://example.com/feed.rss',
'refresh' => 900,
'checked' => 0,
'queued' => 0,
'link' => 'http://example.com',
'description' => 'A vague description',
'image' => '',
'etag' => '',
'modified' => 0,
'block' => 5,
),
array(
'fid' => 2,
'title' => 'feed title 2',
'url' => 'http://example.net/news.rss',
'refresh' => 1800,
'checked' => 0,
'queued' => 0,
'link' => 'http://example.net',
'description' => 'An even more vague description',
'image' => '',
'etag' => '',
'modified' => 0,
'block' => 5,
),
);
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->databaseContents['aggregator_feed'] = $this->expectedResults;
parent::setUp();
}
}

View file

@ -0,0 +1,40 @@
<?php
namespace Drupal\Tests\ban\Kernel\Plugin\migrate\source\d7;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests D7 blocked_ip source plugin.
*
* @covers \Drupal\ban\Plugin\migrate\source\d7\BlockedIps
* @group ban
*/
class BlockedIpsTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['ban', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public function providerSource() {
$tests = [];
$tests[0]['source_data']['blocked_ips'] = [
[
'iid' => 1,
'ip' => '127.0.0.1',
]
];
$tests[0]['expected_data'] = [
[
'ip' => '127.0.0.1',
],
];
return $tests;
}
}

View file

@ -1,43 +0,0 @@
<?php
namespace Drupal\Tests\ban\Unit\Plugin\migrate\source\d7;
use Drupal\Tests\migrate\Unit\MigrateSqlSourceTestCase;
/**
* Tests D7 blocked_ip source plugin.
*
* @coversDefaultClass \Drupal\ban\Plugin\migrate\source\d7\BlockedIps
* @group ban
*/
class BlockedIpsTest extends MigrateSqlSourceTestCase {
const PLUGIN_CLASS = 'Drupal\ban\Plugin\migrate\source\d7\BlockedIps';
protected $migrationConfiguration = [
'id' => 'test',
'source' => [
'plugin' => 'd7_blocked_ips',
],
];
protected $expectedResults = [
[
'ip' => '127.0.0.1',
],
];
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->databaseContents['blocked_ips'] = [
[
'iid' => 1,
'ip' => '127.0.0.1',
]
];
parent::setUp();
}
}

View file

@ -237,7 +237,7 @@ class BlockForm extends EntityForm {
// @todo Allow list of conditions to be configured in
// https://www.drupal.org/node/2284687.
$visibility = $this->entity->getVisibility();
foreach ($this->manager->getDefinitions() as $condition_id => $definition) {
foreach ($this->manager->getDefinitionsForContexts($form_state->getTemporaryValue('gathered_contexts')) as $condition_id => $definition) {
// Don't display the current theme condition.
if ($condition_id == 'current_theme') {
continue;

View file

@ -8,7 +8,6 @@ use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\Core\Menu\LocalActionManagerInterface;
use Drupal\Core\Plugin\Context\LazyContextRepository;
use Drupal\Core\Routing\RedirectDestinationInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
@ -47,13 +46,6 @@ class BlockLibraryController extends ControllerBase {
*/
protected $localActionManager;
/**
* The redirect destination.
*
* @var \Drupal\Core\Routing\RedirectDestinationInterface
*/
protected $redirectDestination;
/**
* Constructs a BlockLibraryController object.
*
@ -65,15 +57,12 @@ class BlockLibraryController extends ControllerBase {
* The current route match.
* @param \Drupal\Core\Menu\LocalActionManagerInterface $local_action_manager
* The local action manager.
* @param \Drupal\Core\Routing\RedirectDestinationInterface $redirect_destination
* The redirect destination.
*/
public function __construct(BlockManagerInterface $block_manager, LazyContextRepository $context_repository, RouteMatchInterface $route_match, LocalActionManagerInterface $local_action_manager, RedirectDestinationInterface $redirect_destination) {
public function __construct(BlockManagerInterface $block_manager, LazyContextRepository $context_repository, RouteMatchInterface $route_match, LocalActionManagerInterface $local_action_manager) {
$this->blockManager = $block_manager;
$this->routeMatch = $route_match;
$this->localActionManager = $local_action_manager;
$this->contextRepository = $context_repository;
$this->redirectDestination = $redirect_destination;
}
/**
@ -84,8 +73,7 @@ class BlockLibraryController extends ControllerBase {
$container->get('plugin.manager.block'),
$container->get('context.repository'),
$container->get('current_route_match'),
$container->get('plugin.manager.menu.local_action'),
$container->get('redirect.destination')
$container->get('plugin.manager.menu.local_action')
);
}
@ -148,10 +136,6 @@ class BlockLibraryController extends ControllerBase {
if (isset($weight)) {
$links['add']['query']['weight'] = $weight;
}
$destination = $this->redirectDestination->get();
if ($destination) {
$links['add']['query']['destination'] = $destination;
}
$row['operations']['data'] = [
'#type' => 'operations',
'#links' => $links,

View file

@ -128,49 +128,6 @@ class BlockTest extends BlockTestBase {
$this->assertNoText($title, 'Block was not displayed to anonymous users on the front page.');
}
/**
* Tests adding a block from the library page with a destination query string.
*/
public function testAddBlockFromLibrary() {
$default_theme = $this->config('system.theme')->get('default');
$help_url = Url::fromRoute('help.page', ['name' => 'block']);
// Set up the request so we land on the block help page after creation.
$options = [
'query' => [
'region' => 'sidebar_first',
'destination' => $help_url->toString(),
],
];
$this->drupalGet(Url::fromRoute('block.admin_library', ['theme' => $default_theme], $options));
$block_name = 'system_powered_by_block';
$add_url = Url::fromRoute('block.admin_add', ['plugin_id' => $block_name, 'theme' => $default_theme]);
$links = $this->xpath('//a[contains(@href, :href)]', [':href' => $add_url->toString()]);
$this->assertEqual(1, count($links), 'Found one matching link');
list($path, $query_string) = explode('?', $links[0]['href'], 2);
parse_str($query_string, $query_parts);
$this->assertEqual(t('Place block'), (string) $links[0]);
$this->assertEqual($help_url->toString(), $query_parts['destination'], 'Expected destination query string is in href');
// Create a random title for the block.
$title = $this->randomMachineName(8);
$block_id = strtolower($this->randomMachineName(8));
$edit = [
'id' => $block_id,
'settings[label]' => $title,
];
// Create the block using the link parsed from the library page.
$this->drupalPostForm($this->getAbsoluteUrl($links[0]['href']), $edit, t('Save block'));
// Verify that we are redirected according to the original request.
$this->assertUrl($help_url);
// Ensure that the block was created.
/** @var \Drupal\block\BlockInterface $block */
$block = Block::load($block_id);
$this->assertEqual($title, $block->label(), 'Found the block with expected title.');
}
/**
* Tests adding a block from the library page with a weight query string.
*/

View file

@ -17,7 +17,7 @@ class BlockUiTest extends WebTestBase {
*
* @var array
*/
public static $modules = array('block', 'block_test', 'help');
public static $modules = array('block', 'block_test', 'help', 'condition_test');
protected $regions;
@ -248,6 +248,11 @@ class BlockUiTest extends WebTestBase {
$this->drupalGet('');
$this->assertText('No context mapping selected.');
$this->assertNoText('User context found.');
// Tests that conditions with missing context are not displayed.
$this->drupalGet('admin/structure/block/manage/testcontextawareblock');
$this->assertNoRaw('No existing type');
$this->assertNoFieldByXPath('//*[@name="visibility[condition_test_no_existing_type][negate]"]');
}
/**

View file

@ -0,0 +1,127 @@
<?php
namespace Drupal\Tests\block\Kernel\Plugin\migrate\source;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests block source plugin.
*
* @covers \Drupal\block\Plugin\migrate\source\Block
* @group block
*/
class BlockTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['block', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public function providerSource() {
$tests = [];
// The source data.
$tests[0]['source_data']['blocks'] = [
[
'bid' => 1,
'module' => 'block',
'delta' => '1',
'theme' => 'garland',
'status' => 1,
'weight' => 0,
'region' => 'left',
'visibility' => 0,
'pages' => '',
'title' => 'Test Title 01',
'cache' => -1,
],
[
'bid' => 2,
'module' => 'block',
'delta' => '2',
'theme' => 'garland',
'status' => 1,
'weight' => 5,
'region' => 'right',
'visibility' => 0,
'pages' => '<front>',
'title' => 'Test Title 02',
'cache' => -1,
],
];
$tests[0]['source_data']['blocks_roles'] = [
[
'module' => 'block',
'delta' => 1,
'rid' => 2,
],
[
'module' => 'block',
'delta' => 2,
'rid' => 2,
],
[
'module' => 'block',
'delta' => 2,
'rid' => 100,
],
];
$tests[0]['source_data']['role'] = [
[
'rid' => 2,
'name' => 'authenticated user',
],
];
$tests[0]['source_data']['system'] = [
[
'filename' => 'modules/system/system.module',
'name' => 'system',
'type' => 'module',
'owner' => '',
'status' => '1',
'throttle' => '0',
'bootstrap' => '0',
'schema_version' => '6055',
'weight' => '0',
'info' => 'a:0:{}',
]
];
// The expected results.
$tests[0]['expected_data'] = [
[
'bid' => 1,
'module' => 'block',
'delta' => '1',
'theme' => 'garland',
'status' => 1,
'weight' => 0,
'region' => 'left',
'visibility' => 0,
'pages' => '',
'title' => 'Test Title 01',
'cache' => -1,
'roles' => [2]
],
[
'bid' => 2,
'module' => 'block',
'delta' => '2',
'theme' => 'garland',
'status' => 1,
'weight' => 5,
'region' => 'right',
'visibility' => 0,
'pages' => '<front>',
'title' => 'Test Title 02',
'cache' => -1,
'roles' => [2]
],
];
return $tests;
}
}

View file

@ -1,144 +0,0 @@
<?php
namespace Drupal\Tests\block\Unit\Plugin\migrate\source;
use Drupal\Tests\migrate\Unit\MigrateSqlSourceTestCase;
/**
* Tests block source plugin.
*
* @coversDefaultClass \Drupal\block\Plugin\migrate\source\Block
* @group block
*/
class BlockTest extends MigrateSqlSourceTestCase {
const PLUGIN_CLASS = 'Drupal\block\Plugin\migrate\source\Block';
protected $migrationConfiguration = array(
'id' => 'test',
'source' => array(
'plugin' => 'block',
),
);
/**
* Sample block instance query results from the source.
*/
protected $expectedResults = array(
array(
'bid' => 1,
'module' => 'block',
'delta' => '1',
'theme' => 'garland',
'status' => 1,
'weight' => 0,
'region' => 'left',
'visibility' => 0,
'pages' => '',
'title' => 'Test Title 01',
'cache' => -1,
'roles' => [2]
),
array(
'bid' => 2,
'module' => 'block',
'delta' => '2',
'theme' => 'garland',
'status' => 1,
'weight' => 5,
'region' => 'right',
'visibility' => 0,
'pages' => '<front>',
'title' => 'Test Title 02',
'cache' => -1,
'roles' => [2]
),
);
/**
* Sample block table.
*/
protected $expectedBlocks = array(
array(
'bid' => 1,
'module' => 'block',
'delta' => '1',
'theme' => 'garland',
'status' => 1,
'weight' => 0,
'region' => 'left',
'visibility' => 0,
'pages' => '',
'title' => 'Test Title 01',
'cache' => -1,
),
array(
'bid' => 2,
'module' => 'block',
'delta' => '2',
'theme' => 'garland',
'status' => 1,
'weight' => 5,
'region' => 'right',
'visibility' => 0,
'pages' => '<front>',
'title' => 'Test Title 02',
'cache' => -1,
),
);
/**
* Sample block roles table.
*/
protected $expectedBlocksRoles = array(
array(
'module' => 'block',
'delta' => 1,
'rid' => 2,
),
array(
'module' => 'block',
'delta' => 2,
'rid' => 2,
),
array(
'module' => 'block',
'delta' => 2,
'rid' => 100,
),
);
/**
* Sample role table.
*/
protected $expectedRole = array(
array(
'rid' => 2,
'name' => 'authenticated user',
),
);
/**
* Prepopulate database contents.
*/
protected function setUp() {
$this->databaseContents['blocks'] = $this->expectedBlocks;
$this->databaseContents['blocks_roles'] = $this->expectedBlocksRoles;
$this->databaseContents['role'] = $this->expectedRole;
$this->databaseContents['system'] = array(
array(
'filename' => 'modules/system/system.module',
'name' => 'system',
'type' => 'module',
'owner' => '',
'status' => '1',
'throttle' => '0',
'bootstrap' => '0',
'schema_version' => '6055',
'weight' => '0',
'info' => 'a:0:{}',
)
);
parent::setUp();
}
}

View file

@ -0,0 +1,46 @@
<?php
namespace Drupal\Tests\block_content\Kernel\Plugin\migrate\source\d6;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests D6 block boxes source plugin.
*
* @covers \Drupal\block_content\Plugin\migrate\source\d6\Box
* @group block_content
*/
class BoxTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['block_content', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public function providerSource() {
$tests = [];
$tests[0]['source_data']['boxes'] = [
[
'bid' => 1,
'body' => '<p>I made some custom content.</p>',
'info' => 'Static Block',
'format' => 1,
],
[
'bid' => 2,
'body' => '<p>I made some more custom content.</p>',
'info' => 'Test Content',
'format' => 1,
],
];
// The expected results are identical to the source data.
$tests[0]['expected_data'] = $tests[0]['source_data']['boxes'];
return $tests;
}
}

View file

@ -0,0 +1,40 @@
<?php
namespace Drupal\Tests\block_content\Kernel\Plugin\migrate\source\d7;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests d7_block_custom source plugin.
*
* @covers \Drupal\block_content\Plugin\migrate\source\d7\BlockCustom
* @group block_content
*/
class BlockCustomTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['block_content', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public function providerSource() {
$tests = [];
$tests[0]['source_data']['block_custom'] = [
[
'bid' => '1',
'body' => "I don't feel creative enough to write anything clever here.",
'info' => 'Meh',
'format' => 'filtered_html',
],
];
// The expected results are identical to the source data.
$tests[0]['expected_data'] = $tests[0]['source_data']['block_custom'];
return $tests;
}
}

View file

@ -1,46 +0,0 @@
<?php
namespace Drupal\Tests\block_content\Unit\Plugin\migrate\source\d6;
use Drupal\Tests\migrate\Unit\MigrateSqlSourceTestCase;
/**
* Tests D6 block boxes source plugin.
*
* @group block_content
*/
class BoxTest extends MigrateSqlSourceTestCase {
const PLUGIN_CLASS = 'Drupal\block_content\Plugin\migrate\source\d6\Box';
protected $migrationConfiguration = array(
'id' => 'test',
'source' => array(
'plugin' => 'd6_boxes',
),
);
protected $expectedResults = array(
array(
'bid' => 1,
'body' => '<p>I made some custom content.</p>',
'info' => 'Static Block',
'format' => 1,
),
array(
'bid' => 2,
'body' => '<p>I made some more custom content.</p>',
'info' => 'Test Content',
'format' => 1,
),
);
/**
* Prepopulate contents with results.
*/
protected function setUp() {
$this->databaseContents['boxes'] = $this->expectedResults;
parent::setUp();
}
}

View file

@ -1,40 +0,0 @@
<?php
namespace Drupal\Tests\block_content\Unit\Plugin\migrate\source\d7;
use Drupal\block_content\Plugin\migrate\source\d7\BlockCustom;
use Drupal\Tests\migrate\Unit\MigrateSqlSourceTestCase;
/**
* @coversDefaultClass \Drupal\block_content\Plugin\migrate\source\d7\BlockCustom
* @group block_content
*/
class BlockCustomTest extends MigrateSqlSourceTestCase {
const PLUGIN_CLASS = BlockCustom::class;
protected $migrationConfiguration = array(
'id' => 'test',
'source' => array(
'plugin' => 'd7_block_custom',
),
);
protected $expectedResults = array(
array(
'bid' => '1',
'body' => "I don't feel creative enough to write anything clever here.",
'info' => 'Meh',
'format' => 'filtered_html',
),
);
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->databaseContents['block_custom'] = $this->expectedResults;
parent::setUp();
}
}

View file

@ -17,7 +17,7 @@ function block_place_help($route_name, RouteMatchInterface $route_match) {
$output = '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('The Place Blocks module allows you to place blocks from every page. For more information, see the <a href=":blocks-documentation">online documentation for the Place Blocks module</a>.', [':blocks-documentation' => 'https://www.drupal.org/documentation/modules/block_place/']) . '</p>';
$output .= '<h3>' . t('Uses') . '</h3>';
$output .= '<p>' . t('Block placement is specific to each theme on your site. This module allows you to place blocks in the context of your content pages') . '</p>';
$output .= '<p>' . t('Block placement is specific to each theme on your site. This module allows you to place blocks in the context of your content pages.') . '</p>';
return $output;
}
}

View file

@ -156,9 +156,12 @@ class BookNavigationBlock extends BlockBase implements ContainerFactoryPluginInt
}
}
elseif ($current_bid) {
// Only display this block when the user is browsing a book.
$query = \Drupal::entityQuery('node');
$nid = $query->condition('nid', $node->book['bid'], '=')->execute();
// Only display this block when the user is browsing a book and do
// not show unpublished books.
$nid = \Drupal::entityQuery('node')
->condition('nid', $node->book['bid'], '=')
->condition('status', NODE_PUBLISHED)
->execute();
// Only show the block if the user has view access for the top-level node.
if ($nid) {

View file

@ -570,34 +570,6 @@ class BookTest extends WebTestBase {
$this->assertEqual($child->id(), $second->book['bid'], '3rd-level child node is now second level when top-level node is deleted.');
}
/**
* Tests re-ordering of books.
*/
public function testBookOrdering() {
// Create new book.
$this->createBook();
$book = $this->book;
$this->drupalLogin($this->adminUser);
$node1 = $this->createBookNode($book->id());
$node2 = $this->createBookNode($book->id());
$pid = $node1->book['nid'];
// Head to admin screen and attempt to re-order.
$this->drupalGet('admin/structure/book/' . $book->id());
$edit = array(
"table[book-admin-{$node1->id()}][weight]" => 1,
"table[book-admin-{$node2->id()}][weight]" => 2,
// Put node 2 under node 1.
"table[book-admin-{$node2->id()}][pid]" => $pid,
);
$this->drupalPostForm(NULL, $edit, t('Save book pages'));
// Verify weight was updated.
$this->assertFieldByName("table[book-admin-{$node1->id()}][weight]", 1);
$this->assertFieldByName("table[book-admin-{$node2->id()}][weight]", 2);
$this->assertFieldByName("table[book-admin-{$node2->id()}][pid]", $pid);
}
/**
* Tests outline of a book.
*/
@ -752,4 +724,29 @@ class BookTest extends WebTestBase {
$this->assertEqual($book_node->book['bid'], $this->book->id());
}
/**
* Tests the book navigation block when book is unpublished.
*
* There was a fatal error with "Show block only on book pages" block mode.
*/
public function testBookNavigationBlockOnUnpublishedBook() {
// Create a new book.
$this->createBook();
// Create administrator user.
$administratorUser = $this->drupalCreateUser(['administer blocks', 'administer nodes', 'bypass node access']);
$this->drupalLogin($administratorUser);
// Enable the block with "Show block only on book pages" mode.
$this->drupalPlaceBlock('book_navigation', ['block_mode' => 'book pages']);
// Unpublish book node.
$edit = [];
$this->drupalPostForm('node/' . $this->book->id() . '/edit', $edit, t('Save and unpublish'));
// Test node page.
$this->drupalGet('node/' . $this->book->id());
$this->assertText($this->book->label(), 'Unpublished book with "Show block only on book pages" book navigation settings.');
}
}

View file

@ -0,0 +1,160 @@
<?php
namespace Drupal\Tests\book\FunctionalJavascript;
use Behat\Mink\Exception\ExpectationException;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
use Drupal\node\Entity\Node;
/**
* Tests Book javascript functionality.
*
* @group book
*/
class BookJavascriptTest extends JavascriptTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['book'];
/**
* Tests re-ordering of books.
*/
public function testBookOrdering() {
$book = Node::create([
'type' => 'book',
'title' => 'Book',
'book' => ['bid' => 'new'],
]);
$book->save();
$page1 = Node::create([
'type' => 'book',
'title' => '1st page',
'book' => ['bid' => $book->id(), 'pid' => $book->id(), 'weight' => 0],
]);
$page1->save();
$page2 = Node::create([
'type' => 'book',
'title' => '2nd page',
'book' => ['bid' => $book->id(), 'pid' => $book->id(), 'weight' => 1],
]);
$page2->save();
// Head to admin screen and attempt to re-order.
$this->drupalLogin($this->drupalCreateUser(['administer book outlines']));
$this->drupalGet('admin/structure/book/' . $book->id());
$page = $this->getSession()->getPage();
$weight_select1 = $page->findField("table[book-admin-{$page1->id()}][weight]");
$weight_select2 = $page->findField("table[book-admin-{$page2->id()}][weight]");
// Check that rows weight selects are hidden.
$this->assertFalse($weight_select1->isVisible());
$this->assertFalse($weight_select2->isVisible());
// Check that '2nd page' row is heavier than '1st page' row.
$this->assertGreaterThan($weight_select1->getValue(), $weight_select2->getValue());
// Check that '1st page' precedes the '2nd page'.
$this->assertOrderInPage(['1st page', '2nd page']);
// Check that the 'unsaved changes' text is not present in the message area.
$this->assertSession()->pageTextNotContains('You have unsaved changes.');
// Drag and drop the '1st page' row over the '2nd page' row.
// @todo: Test also the reverse, '2nd page' over '1st page', when
// https://www.drupal.org/node/2769825 is fixed.
// @see https://www.drupal.org/node/2769825
$dragged = $this->xpath("//tr[@data-drupal-selector='edit-table-book-admin-{$page1->id()}']//a[@class='tabledrag-handle']")[0];
$target = $this->xpath("//tr[@data-drupal-selector='edit-table-book-admin-{$page2->id()}']//a[@class='tabledrag-handle']")[0];
$dragged->dragTo($target);
// Give javascript some time to manipulate the DOM.
$this->getSession()->wait(1000, 'jQuery(".tabledrag-changed-warning").is(":visible")');
// Check that the 'unsaved changes' text appeared in the message area.
$this->assertSession()->pageTextContains('You have unsaved changes.');
// Check that '2nd page' page precedes the '1st page'.
$this->assertOrderInPage(['2nd page', '1st page']);
$this->submitForm([], 'Save book pages');
$this->assertSession()->pageTextContains(new FormattableMarkup('Updated book @book.', ['@book' => $book->getTitle()]));
// Check that page reordering was done in the backend for drag-n-drop.
$page1 = Node::load($page1->id());
$page2 = Node::load($page2->id());
$this->assertGreaterThan($page2->book['weight'], $page1->book['weight']);
// Check again that '2nd page' is on top after form submit in the UI.
$this->assertOrderInPage(['2nd page', '1st page']);
// Toggle row weight selects as visible.
$page->findButton('Show row weights')->click();
// Check that rows weight selects are visible.
$this->assertTrue($weight_select1->isVisible());
$this->assertTrue($weight_select2->isVisible());
// Check that '1st page' row became heavier than '2nd page' row.
$this->assertGreaterThan($weight_select2->getValue(), $weight_select1->getValue());
// Reverse again using the weight fields. Use the current values so the test
// doesn't rely on knowing the values in the select boxes.
$value1 = $weight_select1->getValue();
$value2 = $weight_select2->getValue();
$weight_select1->setValue($value2);
$weight_select2->setValue($value1);
// Toggle row weight selects back to hidden.
$page->findButton('Hide row weights')->click();
// Check that rows weight selects are hidden again.
$this->assertFalse($weight_select1->isVisible());
$this->assertFalse($weight_select2->isVisible());
$this->submitForm([], 'Save book pages');
$this->assertSession()->pageTextContains(new FormattableMarkup('Updated book @book.', ['@book' => $book->getTitle()]));
// Check that the '1st page' is first again.
$this->assertOrderInPage(['1st page', '2nd page']);
// Check that page reordering was done in the backend for manual weight
// field usage.
$page1 = Node::load($page1->id());
$page2 = Node::load($page2->id());
$this->assertGreaterThan($page2->book['weight'], $page1->book['weight']);
}
/**
* Asserts that several pieces of markup are in a given order in the page.
*
* @param string[] $items
* An ordered list of strings.
*
* @throws \Behat\Mink\Exception\ExpectationException
* When any of the given string is not found.
*
* @todo Remove this once https://www.drupal.org/node/2817657 is committed.
*/
protected function assertOrderInPage(array $items) {
$session = $this->getSession();
$text = $session->getPage()->getHtml();
$strings = [];
foreach ($items as $item) {
if (($pos = strpos($text, $item)) === FALSE) {
throw new ExpectationException("Cannot find '$item' in the page", $session->getDriver());
}
$strings[$pos] = $item;
}
ksort($strings);
$ordered = implode(', ', array_map(function ($item) {
return "'$item'";
}, $items));
$this->assertSame($items, array_values($strings), "Found strings, ordered as: $ordered.");
}
}

View file

@ -0,0 +1,84 @@
<?php
namespace Drupal\Tests\book\Kernel\Plugin\migrate\source\d6;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* @covers \Drupal\book\Plugin\migrate\source\d6\Book
* @group book
*/
class BookTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['book', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public function providerSource() {
$tests = [];
// The source data.
$tests[0]['source_data']['book'] = [
[
'mlid' => '1',
'nid' => '4',
'bid' => '4',
],
];
$tests[0]['source_data']['menu_links'] = [
[
'menu_name' => 'book-toc-1',
'mlid' => '1',
'plid' => '0',
'link_path' => 'node/4',
'router_path' => 'node/%',
'link_title' => 'Test top book title',
'options' => 'a:0:{}',
'module' => 'book',
'hidden' => '0',
'external' => '0',
'has_children' => '1',
'expanded' => '0',
'weight' => '-10',
'depth' => '1',
'customized' => '0',
'p1' => '1',
'p2' => '0',
'p3' => '0',
'p4' => '0',
'p5' => '0',
'p6' => '0',
'p7' => '0',
'p8' => '0',
'p9' => '0',
'updated' => '0',
],
];
// The expected results.
$tests[0]['expected_data'] = [
[
'nid' => '4',
'bid' => '4',
'mlid' => '1',
'plid' => '0',
'weight' => '-10',
'p1' => '1',
'p2' => '0',
'p3' => '0',
'p4' => '0',
'p5' => '0',
'p6' => '0',
'p7' => '0',
'p8' => '0',
'p9' => '0',
],
];
return $tests;
}
}

View file

@ -1,85 +0,0 @@
<?php
namespace Drupal\Tests\book\Unit\Plugin\migrate\source\d6;
use Drupal\book\Plugin\migrate\source\d6\Book;
use Drupal\Tests\migrate\Unit\MigrateSqlSourceTestCase;
/**
* @coversDefaultClass \Drupal\book\Plugin\migrate\source\d6\Book
* @group book
*/
class BookTest extends MigrateSqlSourceTestCase {
const PLUGIN_CLASS = Book::class;
protected $migrationConfiguration = array(
'id' => 'test',
'source' => array(
'plugin' => 'd6_book',
),
);
protected $expectedResults = array(
array(
'nid' => '4',
'bid' => '4',
'mlid' => '1',
'plid' => '0',
'weight' => '-10',
'p1' => '1',
'p2' => '0',
'p3' => '0',
'p4' => '0',
'p5' => '0',
'p6' => '0',
'p7' => '0',
'p8' => '0',
'p9' => '0',
),
);
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->databaseContents['book'] = array(
array(
'mlid' => '1',
'nid' => '4',
'bid' => '4',
),
);
$this->databaseContents['menu_links'] = array(
array(
'menu_name' => 'book-toc-1',
'mlid' => '1',
'plid' => '0',
'link_path' => 'node/4',
'router_path' => 'node/%',
'link_title' => 'Test top book title',
'options' => 'a:0:{}',
'module' => 'book',
'hidden' => '0',
'external' => '0',
'has_children' => '1',
'expanded' => '0',
'weight' => '-10',
'depth' => '1',
'customized' => '0',
'p1' => '1',
'p2' => '0',
'p3' => '0',
'p4' => '0',
'p5' => '0',
'p6' => '0',
'p7' => '0',
'p8' => '0',
'p9' => '0',
'updated' => '0',
),
);
parent::setUp();
}
}

View file

@ -5,6 +5,7 @@ namespace Drupal\ckeditor\Plugin\CKEditorPlugin;
use Drupal\ckeditor\CKEditorPluginBase;
use Drupal\ckeditor\CKEditorPluginContextualInterface;
use Drupal\ckeditor\CKEditorPluginManager;
use Drupal\Component\Utility\Html;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
@ -369,7 +370,7 @@ class Internal extends CKEditorPluginBase implements ContainerFactoryPluginInter
foreach ($possible_format_tags as $tag) {
$input = '<' . $tag . '>TEST</' . $tag . '>';
$output = trim(check_markup($input, $editor->id()));
if ($input == $output) {
if (Html::load($output)->getElementsByTagName($tag)->length !== 0) {
$format_tags[] = $tag;
}
}

View file

@ -5,3 +5,12 @@ ckeditor.plugin.llama_contextual_and_button:
ultra_llama_mode:
type: boolean
label: 'Ultra llama mode'
filter_settings.test_attribute_filter:
type: filter
label: 'Test Attribute Filter'
mapping:
tags:
type: sequence
sequence:
type: string

View file

@ -0,0 +1,38 @@
<?php
namespace Drupal\ckeditor_test\Plugin\Filter;
use Drupal\Component\Utility\Html;
use Drupal\filter\FilterProcessResult;
use Drupal\filter\Plugin\FilterBase;
/**
* A filter that adds a test attribute to any configured HTML tags.
*
* @Filter(
* id = "test_attribute_filter",
* title = @Translation("Test Attribute Filter"),
* type = Drupal\filter\Plugin\FilterInterface::TYPE_TRANSFORM_REVERSIBLE,
* settings = {
* "tags" = {},
* },
* weight = -10
* )
*/
class TestAttributeFilter extends FilterBase {
/**
* {@inheritdoc}
*/
public function process($text, $langcode) {
$document = Html::load($text);
foreach ($this->settings['tags'] as $tag) {
$tag_elements = $document->getElementsByTagName($tag);
foreach ($tag_elements as $tag_element) {
$tag_element->setAttribute('test_attribute', 'test attribute value');
}
}
return new FilterProcessResult(Html::serialize($document));
}
}

View file

@ -0,0 +1,140 @@
<?php
namespace Drupal\Tests\ckeditor\Kernel\Plugin\CKEditorPlugin;
use Drupal\editor\Entity\Editor;
use Drupal\filter\Entity\FilterFormat;
use Drupal\KernelTests\KernelTestBase;
/**
* @coversDefaultClass \Drupal\ckeditor\Plugin\CKEditorPlugin\Internal
*
* @group ckeditor
*/
class InternalTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = [
'ckeditor',
'ckeditor_test',
'filter',
'editor',
];
/**
* A testing text format.
*
* @var \Drupal\filter\Entity\FilterFormat
*/
protected $format;
/**
* A testing text editor.
*
* @var \Drupal\editor\Entity\Editor
*/
protected $editor;
/**
* The CKEditor plugin manager.
*
* @var \Drupal\Component\Plugin\PluginManagerInterface
*/
protected $ckeditorPluginManager;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('editor');
$this->installEntitySchema('filter_format');
$this->format = FilterFormat::create([
'format' => 'test_format',
'name' => $this->randomMachineName(),
]);
$this->format->save();
$this->editor = Editor::create([
'editor' => 'ckeditor',
'format' => 'test_format',
'settings' => [
'toolbar' => [
'rows' => [
[
[
'name' => 'Enabled Buttons',
'items' => [
'Format',
],
],
],
],
],
],
]);
$this->editor->save();
$this->ckeditorPluginManager = $this->container->get('plugin.manager.ckeditor.plugin');
}
/**
* Test the format tags settings.
*
* @dataProvider formatTagsSettingsTestCases
*/
public function testFormatTagsSettings($filter_plugins, $expected_format_tags) {
foreach ($filter_plugins as $filter_plugin_id => $filter_plugin_settings) {
$this->format->setFilterConfig($filter_plugin_id, $filter_plugin_settings);
}
$this->format->save();
$internal_plugin = $this->ckeditorPluginManager->createInstance('internal', []);
$plugin_config = $internal_plugin->getConfig($this->editor);
$this->assertEquals($expected_format_tags, explode(';', $plugin_config['format_tags']));
}
/**
* A data provider for testFormatTagsSettings.
*/
public function formatTagsSettingsTestCases() {
$all_tags = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'pre'];
return [
'No filter plugins enabled (all tags allowed)' => [
[],
$all_tags,
],
'HTML filter plugin enabled (some tags filtered out)' => [
[
'filter_html' => [
'status' => 1,
'settings' => [
'allowed_html' => '<h1> <h2>',
'filter_html_help' => 1,
'filter_html_nofollow' => 0,
],
],
],
['p', 'h1', 'h2'],
],
'Test attribute filter enabled (all tags allowed)' => [
[
'test_attribute_filter' => [
'status' => 1,
'settings' => [
'tags' => ['h1', 'h2'],
],
],
],
$all_tags,
],
];
}
}

View file

@ -12,8 +12,8 @@ process:
source: category
-
plugin: dedupe_entity
entity_type: user_role
field: cid
entity_type: contact_form
field: id
length: 32
label: category
recipients: recipients

View file

@ -122,7 +122,14 @@ class MailHandler implements MailHandlerInterface {
if (!$message->isPersonal() && $contact_form->getReply()) {
// User contact forms do not support an auto-reply message, so this
// message always originates from the site.
$this->mailManager->mail('contact', 'page_autoreply', $sender_cloned->getEmail(), $current_langcode, $params);
if (!$sender_cloned->getEmail()) {
$this->logger->error('Error sending auto-reply, missing sender e-mail address in %contact_form', [
'%contact_form' => $contact_form->label(),
]);
}
else {
$this->mailManager->mail('contact', 'page_autoreply', $sender_cloned->getEmail(), $current_langcode, $params);
}
}
if (!$message->isPersonal()) {

View file

@ -27,7 +27,7 @@ class ContactSitewideTest extends WebTestBase {
*
* @var array
*/
public static $modules = array('text', 'contact', 'field_ui', 'contact_test', 'block');
public static $modules = ['text', 'contact', 'field_ui', 'contact_test', 'block', 'error_service_test', 'dblog'];
/**
* {@inheritdoc}
@ -44,13 +44,13 @@ class ContactSitewideTest extends WebTestBase {
*/
function testSiteWideContact() {
// Create and log in administrative user.
$admin_user = $this->drupalCreateUser(array(
$admin_user = $this->drupalCreateUser([
'access site-wide contact form',
'administer contact forms',
'administer users',
'administer account settings',
'administer contact_message fields',
));
]);
$this->drupalLogin($admin_user);
// Check the presence of expected cache tags.
@ -346,7 +346,13 @@ class ContactSitewideTest extends WebTestBase {
*/
function testAutoReply() {
// Create and log in administrative user.
$admin_user = $this->drupalCreateUser(array('access site-wide contact form', 'administer contact forms', 'administer permissions', 'administer users'));
$admin_user = $this->drupalCreateUser([
'access site-wide contact form',
'administer contact forms',
'administer permissions',
'administer users',
'access site reports'
]);
$this->drupalLogin($admin_user);
// Set up three forms, 2 with an auto-reply and one without.
@ -384,6 +390,20 @@ class ContactSitewideTest extends WebTestBase {
$this->submitContact($this->randomMachineName(16), $email, $this->randomString(64), 'no_autoreply', $this->randomString(128));
$captured_emails = $this->drupalGetMails(array('id' => 'contact_page_autoreply', 'to' => $email));
$this->assertEqual(count($captured_emails), 0);
// Verify that the current error message doesn't show, that the auto-reply
// doesn't get sent and the correct silent error gets logged.
$email = '';
entity_get_form_display('contact_message', 'foo', 'default')
->removeComponent('mail')
->save();
$this->submitContact($this->randomMachineName(16), $email, $this->randomString(64), 'foo', $this->randomString(128));
$this->assertNoText('Unable to send email. Contact the site administrator if the problem persists.');
$captured_emails = $this->drupalGetMails(['id' => 'contact_page_autoreply', 'to' => $email]);
$this->assertEqual(count($captured_emails), 0);
$this->drupalLogin($admin_user);
$this->drupalGet('admin/reports/dblog');
$this->assertRaw('Error sending auto-reply, missing sender e-mail address in foo');
}
/**

View file

@ -47,6 +47,29 @@ class MigrateContactCategoryTest extends MigrateDrupal6TestBase {
$this->assertIdentical(array('fortyninechars@example.com'), $contact_form->getRecipients());
$this->assertIdentical('', $contact_form->getReply());
$this->assertIdentical(2, $contact_form->getWeight());
// Test there are no duplicated roles.
$contact_forms = [
'website_feedback1',
'some_other_category1',
'a_category_much_longer_than_thir1',
];
$this->assertEmpty(ContactForm::loadMultiple($contact_forms));
/*
* Remove the map row for the Website feedback contact form so that it
* can be migrated again.
*/
$id_map = $this->getMigration('contact_category')->getIdMap();
$id_map->delete(['cid' => '1']);
$this->executeMigration('contact_category');
// Test there is a duplicate Website feedback form.
$contact_form = ContactForm::load('website_feedback1');
$this->assertSame('Website feedback', $contact_form->label());
$this->assertSame(array('admin@example.com'), $contact_form->getRecipients());
$this->assertSame('', $contact_form->getReply());
$this->assertSame(0, $contact_form->getWeight());
}
}

View file

@ -0,0 +1,57 @@
<?php
namespace Drupal\Tests\contact\Kernel\Plugin\migrate\source;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests D6 contact category source plugin.
*
* @covers \Drupal\contact\Plugin\migrate\source\ContactCategory
* @group contact
*/
class ContactCategoryTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['contact', 'migrate_drupal', 'user'];
/**
* {@inheritdoc}
*/
public function providerSource() {
$tests = [
[
'source_data' => [],
'expected_data' => [],
],
];
$tests[0]['expected_data'] = [
[
'cid' => 1,
'category' => 'contact category value 1',
'recipients' => ['admin@example.com', 'user@example.com'],
'reply' => 'auto reply value 1',
'weight' => 0,
'selected' => 0,
],
[
'cid' => 2,
'category' => 'contact category value 2',
'recipients' => ['admin@example.com', 'user@example.com'],
'reply' => 'auto reply value 2',
'weight' => 0,
'selected' => 0,
],
];
foreach ($tests[0]['expected_data'] as $k => $row) {
$row['recipients'] = implode(',', $row['recipients']);
$tests[0]['source_data']['contact'][$k] = $row;
}
return $tests;
}
}

View file

@ -0,0 +1,54 @@
<?php
namespace Drupal\Tests\contact\Kernel\Plugin\migrate\source\d6;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests D6 contact settings source plugin.
*
* @covers \Drupal\contact\Plugin\migrate\source\ContactSettings
* @group contact
*/
class ContactSettingsTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['contact', 'migrate_drupal', 'user'];
/**
* {@inheritdoc}
*/
public function providerSource() {
$tests = [];
$tests[0]['source_data']['variable'] = [
[
'name' => 'site_name',
'value' => serialize('Blorf!'),
],
];
$tests[0]['source_data']['contact'] = [
[
'cid' => '1',
'category' => 'Website feedback',
'recipients' => 'admin@example.com',
'reply' => '',
'weight' => '0',
'selected' => '1',
]
];
$tests[0]['expected_data'] = [
[
'default_category' => '1',
'site_name' => 'Blorf!',
],
];
$tests[0]['expected_count'] = NULL;
$tests[0]['configuration']['variables'] = ['site_name'];
return $tests;
}
}

View file

@ -1,54 +0,0 @@
<?php
namespace Drupal\Tests\contact\Unit\Plugin\migrate\source;
use Drupal\contact\Plugin\migrate\source\ContactCategory;
use Drupal\Tests\migrate\Unit\MigrateSqlSourceTestCase;
/**
* Tests contact_category source plugin.
*
* @group contact
*/
class ContactCategoryTest extends MigrateSqlSourceTestCase {
const PLUGIN_CLASS = ContactCategory::class;
protected $migrationConfiguration = array(
'id' => 'test',
'source' => array(
'plugin' => 'contact_category',
),
);
protected $expectedResults = array(
array(
'cid' => 1,
'category' => 'contact category value 1',
'recipients' => array('admin@example.com', 'user@example.com'),
'reply' => 'auto reply value 1',
'weight' => 0,
'selected' => 0,
),
array(
'cid' => 2,
'category' => 'contact category value 2',
'recipients' => array('admin@example.com', 'user@example.com'),
'reply' => 'auto reply value 2',
'weight' => 0,
'selected' => 0,
),
);
/**
* {@inheritdoc}
*/
protected function setUp() {
foreach ($this->expectedResults as $k => $row) {
$this->databaseContents['contact'][$k] = $row;
$this->databaseContents['contact'][$k]['recipients'] = implode(',', $row['recipients']);
}
parent::setUp();
}
}

View file

@ -1,55 +0,0 @@
<?php
namespace Drupal\Tests\contact\Unit\Plugin\migrate\source\d6;
use Drupal\contact\Plugin\migrate\source\ContactSettings;
use Drupal\Tests\migrate\Unit\MigrateSqlSourceTestCase;
/**
* Tests D6 contact settings source plugin.
*
* @group contact
*/
class ContactSettingsTest extends MigrateSqlSourceTestCase {
const PLUGIN_CLASS = ContactSettings::class;
protected $migrationConfiguration = array(
'id' => 'test',
'source' => array(
'plugin' => 'd6_contact_settings',
'variables' => array('site_name'),
),
);
protected $expectedResults = array(
array(
'default_category' => '1',
'site_name' => 'Blorf!',
),
);
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->databaseContents['variable'] = array(
array(
'name' => 'site_name',
'value' => serialize('Blorf!'),
),
);
$this->databaseContents['contact'] = array(
array(
'cid' => '1',
'category' => 'Website feedback',
'recipients' => 'admin@example.com',
'reply' => '',
'weight' => '0',
'selected' => '1',
)
);
parent::setUp();
}
}

View file

@ -4,7 +4,6 @@ namespace Drupal\content_moderation\Entity;
use Drupal\content_moderation\ContentModerationStateInterface;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Entity\EntityChangedTrait;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\TypedData\TranslatableInterface;
@ -42,8 +41,6 @@ use Drupal\user\UserInterface;
*/
class ContentModerationState extends ContentEntityBase implements ContentModerationStateInterface {
use EntityChangedTrait;
/**
* {@inheritdoc}
*/

View file

@ -4,7 +4,6 @@ namespace Drupal\content_moderation;
use Drupal\content_moderation\Entity\ContentModerationState;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
@ -77,32 +76,6 @@ class EntityOperations implements ContainerInjectionInterface {
);
}
/**
* Determines the default moderation state on load for an entity.
*
* This method is only applicable when an entity is loaded that has
* no moderation state on it, but should. In those cases, failing to set
* one may result in NULL references elsewhere when other code tries to check
* the moderation state of the entity.
*
* The amount of indirection here makes performance a concern, but
* given how Entity API works I don't know how else to do it.
* This reliably gets us *A* valid state. However, that state may be
* not the ideal one. Suggestions on how to better select the default
* state here are welcome.
*
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
* The entity for which we want a default state.
*
* @return string
* The default state for the given entity.
*/
protected function getDefaultLoadStateId(ContentEntityInterface $entity) {
return $this->moderationInfo
->loadBundleEntity($entity->getEntityType()->getBundleEntityType(), $entity->bundle())
->getThirdPartySetting('content_moderation', 'default_moderation_state');
}
/**
* Acts on an entity and set published status based on the moderation state.
*
@ -170,8 +143,8 @@ class EntityOperations implements ContainerInjectionInterface {
$moderation_state = $entity->moderation_state->target_id;
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
if (!$moderation_state) {
$moderation_state = $this->moderationInfo
->loadBundleEntity($entity->getEntityType()->getBundleEntityType(), $entity->bundle())
$moderation_state = $this->entityTypeManager
->getStorage($entity->getEntityType()->getBundleEntityType())->load($entity->bundle())
->getThirdPartySetting('content_moderation', 'default_moderation_state');
}

View file

@ -49,21 +49,15 @@ class ModerationInformation implements ModerationInformationInterface {
return $entity_type->hasHandlerClass('moderation');
}
/**
* {@inheritdoc}
*/
public function loadBundleEntity($bundle_entity_type_id, $bundle_id) {
if ($bundle_entity_type_id) {
return $this->entityTypeManager->getStorage($bundle_entity_type_id)->load($bundle_id);
}
}
/**
* {@inheritdoc}
*/
public function shouldModerateEntitiesOfBundle(EntityTypeInterface $entity_type, $bundle) {
if ($bundle_entity = $this->loadBundleEntity($entity_type->getBundleEntityType(), $bundle)) {
return $bundle_entity->getThirdPartySetting('content_moderation', 'enabled', FALSE);
if ($this->canModerateEntitiesOfEntityType($entity_type)) {
$bundle_entity = $this->entityTypeManager->getStorage($entity_type->getBundleEntityType())->load($bundle);
if ($bundle_entity) {
return $bundle_entity->getThirdPartySetting('content_moderation', 'enabled', FALSE);
}
}
return FALSE;
}

View file

@ -11,19 +11,6 @@ use Drupal\Core\Entity\EntityTypeInterface;
*/
interface ModerationInformationInterface {
/**
* Loads a specific bundle entity.
*
* @param string $bundle_entity_type_id
* The bundle entity type ID.
* @param string $bundle_id
* The bundle ID.
*
* @return \Drupal\Core\Config\Entity\ConfigEntityInterface|null
* The bundle entity.
*/
public function loadBundleEntity($bundle_entity_type_id, $bundle_id);
/**
* Determines if an entity is moderated.
*

View file

@ -57,8 +57,9 @@ class ModerationStateFieldItemList extends EntityReferenceFieldItemList {
// It is possible that the bundle does not exist at this point. For example,
// the node type form creates a fake Node entity to get default values.
// @see \Drupal\node\NodeTypeForm::form()
$bundle_entity = \Drupal::service('content_moderation.moderation_information')
->loadBundleEntity($entity->getEntityType()->getBundleEntityType(), $entity->bundle());
$bundle_entity = \Drupal::entityTypeManager()
->getStorage($entity->getEntityType()->getBundleEntityType())
->load($entity->bundle());
if ($bundle_entity && ($default = $bundle_entity->getThirdPartySetting('content_moderation', 'default_moderation_state'))) {
return ModerationState::load($default);
}
@ -71,7 +72,24 @@ class ModerationStateFieldItemList extends EntityReferenceFieldItemList {
if ($index !== 0) {
throw new \InvalidArgumentException('An entity can not have multiple moderation states at the same time.');
}
$this->computeModerationFieldItemList();
return isset($this->list[$index]) ? $this->list[$index] : NULL;
}
/**
* {@inheritdoc}
*/
public function getIterator() {
$this->computeModerationFieldItemList();
return parent::getIterator();
}
/**
* Recalculate the moderation field item list.
*/
protected function computeModerationFieldItemList() {
// Compute the value of the moderation state.
$index = 0;
if (!isset($this->list[$index]) || $this->list[$index]->isEmpty()) {
$moderation_state = $this->getModerationState();
// Do not store NULL values in the static cache.
@ -79,8 +97,6 @@ class ModerationStateFieldItemList extends EntityReferenceFieldItemList {
$this->list[$index] = $this->createItem($index, ['entity' => $moderation_state]);
}
}
return isset($this->list[$index]) ? $this->list[$index] : NULL;
}
}

View file

@ -97,8 +97,8 @@ class ModerationStateConstraintValidator extends ConstraintValidator implements
$new_state_id = $entity->moderation_state->target_id;
}
else {
$new_state_id = $default = $this->moderationInformation
->loadBundleEntity($entity->getEntityType()->getBundleEntityType(), $entity->bundle())
$new_state_id = $default = $this->entityTypeManager
->getStorage($entity->getEntityType()->getBundleEntityType())->load($entity->bundle())
->getThirdPartySetting('content_moderation', 'default_moderation_state');
}
if ($new_state_id) {

View file

@ -0,0 +1,78 @@
<?php
namespace Drupal\Tests\content_moderation\Kernel;
use Drupal\KernelTests\KernelTestBase;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
/**
* @coversDefaultClass \Drupal\content_moderation\Plugin\Field\ModerationStateFieldItemList
*
* @group content_moderation
*/
class ModerationStateFieldItemListTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = [
'node',
'content_moderation',
'user',
'system',
'language',
];
/**
* @var \Drupal\node\NodeInterface
*/
protected $testNode;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installSchema('node', 'node_access');
$this->installEntitySchema('node');
$this->installEntitySchema('user');
$this->installEntitySchema('content_moderation_state');
$this->installConfig('content_moderation');
$node_type = NodeType::create([
'type' => 'example',
]);
$node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
$node_type->setThirdPartySetting('content_moderation', 'allowed_moderation_states', ['draft']);
$node_type->setThirdPartySetting('content_moderation', 'default_moderation_state', 'draft');
$node_type->save();
$this->testNode = Node::create([
'type' => 'example',
'title' => 'Test title',
]);
$this->testNode->save();
\Drupal::entityTypeManager()->getStorage('node')->resetCache();
$this->testNode = Node::load($this->testNode->id());
}
/**
* Test the field item list when accessing an index.
*/
public function testArrayIndex() {
$this->assertEquals('draft', $this->testNode->moderation_state[0]->entity->id());
}
/**
* Test the field item list when iterating.
*/
public function testArrayIteration() {
$states = [];
foreach ($this->testNode->moderation_state as $item) {
$states[] = $item->entity->id();
}
$this->assertEquals(['draft'], $states);
}
}

View file

@ -2,6 +2,7 @@
namespace Drupal\Tests\content_moderation\Unit;
use Drupal\content_moderation\Entity\Handler\ModerationHandler;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\ContentEntityType;
@ -70,6 +71,7 @@ class ModerationInformationTest extends \PHPUnit_Framework_TestCase {
$entity_type = new ContentEntityType([
'id' => 'test_entity_type',
'bundle_entity_type' => 'entity_test_bundle',
'handlers' => ['moderation' => ModerationHandler::class],
]);
$entity = $this->prophesize(ContentEntityInterface::class);
$entity->getEntityType()->willReturn($entity_type);
@ -104,6 +106,7 @@ class ModerationInformationTest extends \PHPUnit_Framework_TestCase {
$entity_type = new ContentEntityType([
'id' => 'test_entity_type',
'bundle_entity_type' => 'entity_test_bundle',
'handlers' => ['moderation' => ModerationHandler::class],
]);
$moderation_information = new ModerationInformation($this->setupModerationEntityManager($status), $this->getUser());

View file

@ -92,11 +92,19 @@ class DateTimeFieldItemList extends FieldItemList {
$default_value = parent::processDefaultValue($default_value, $entity, $definition);
if (isset($default_value[0]['default_date_type'])) {
// A default value should be in the format and timezone used for date
// storage.
$date = new DrupalDateTime($default_value[0]['default_date'], DATETIME_STORAGE_TIMEZONE);
$storage_format = $definition->getSetting('datetime_type') == DateTimeItem::DATETIME_TYPE_DATE ? DATETIME_DATE_STORAGE_FORMAT : DATETIME_DATETIME_STORAGE_FORMAT;
$value = $date->format($storage_format);
if ($definition->getSetting('datetime_type') === DateTimeItem::DATETIME_TYPE_DATE) {
// A default date only value should be in the format used for date
// storage but in the user's local timezone.
$date = new DrupalDateTime($default_value[0]['default_date'], drupal_get_user_timezone());
$format = DATETIME_DATE_STORAGE_FORMAT;
}
else {
// A default date+time value should be in the format and timezone used
// for date storage.
$date = new DrupalDateTime($default_value[0]['default_date'], DATETIME_STORAGE_TIMEZONE);
$format = DATETIME_DATETIME_STORAGE_FORMAT;
}
$value = $date->format($format);
// We only provide a default value for the first item, as do all fields.
// Otherwise, there is no way to clear out unwanted values on multiple value
// fields.

View file

@ -594,83 +594,103 @@ class DateTimeFieldTest extends DateTestBase {
]);
$field->save();
// Set now as default_value.
$field_edit = array(
'default_value_input[default_date_type]' => 'now',
);
$this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
// Loop through defined timezones to test that date-only defaults work at
// the extremes.
foreach (static::$timezones as $timezone) {
// Check that default value is selected in default value form.
$this->drupalGet('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name);
$this->assertOptionSelected('edit-default-value-input-default-date-type', 'now', 'The default value is selected in instance settings page');
$this->assertFieldByName('default_value_input[default_date]', '', 'The relative default value is empty in instance settings page');
$this->setSiteTimezone($timezone);
// Check if default_date has been stored successfully.
$config_entity = $this->config('field.field.node.date_content.' . $field_name)->get();
$this->assertEqual($config_entity['default_value'][0], array('default_date_type' => 'now', 'default_date' => 'now'), 'Default value has been stored successfully');
// Set now as default_value.
$field_edit = array(
'default_value_input[default_date_type]' => 'now',
);
$this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
// Clear field cache in order to avoid stale cache values.
\Drupal::entityManager()->clearCachedFieldDefinitions();
// Check that default value is selected in default value form.
$this->drupalGet('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name);
$this->assertOptionSelected('edit-default-value-input-default-date-type', 'now', 'The default value is selected in instance settings page');
$this->assertFieldByName('default_value_input[default_date]', '', 'The relative default value is empty in instance settings page');
// Create a new node to check that datetime field default value is today.
$new_node = Node::create(['type' => 'date_content']);
$expected_date = new DrupalDateTime('now', DATETIME_STORAGE_TIMEZONE);
$this->assertEqual($new_node->get($field_name)->offsetGet(0)->value, $expected_date->format(DATETIME_DATE_STORAGE_FORMAT));
// Check if default_date has been stored successfully.
$config_entity = $this->config('field.field.node.date_content.' . $field_name)
->get();
$this->assertEqual($config_entity['default_value'][0], array(
'default_date_type' => 'now',
'default_date' => 'now',
), 'Default value has been stored successfully');
// Set an invalid relative default_value to test validation.
$field_edit = array(
'default_value_input[default_date_type]' => 'relative',
'default_value_input[default_date]' => 'invalid date',
);
$this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
// Clear field cache in order to avoid stale cache values.
\Drupal::entityManager()->clearCachedFieldDefinitions();
$this->assertText('The relative date value entered is invalid.');
// Create a new node to check that datetime field default value is today.
$new_node = Node::create(['type' => 'date_content']);
$expected_date = new DrupalDateTime('now', drupal_get_user_timezone());
$this->assertEqual($new_node->get($field_name)
->offsetGet(0)->value, $expected_date->format(DATETIME_DATE_STORAGE_FORMAT));
// Set a relative default_value.
$field_edit = array(
'default_value_input[default_date_type]' => 'relative',
'default_value_input[default_date]' => '+90 days',
);
$this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
// Set an invalid relative default_value to test validation.
$field_edit = array(
'default_value_input[default_date_type]' => 'relative',
'default_value_input[default_date]' => 'invalid date',
);
$this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
// Check that default value is selected in default value form.
$this->drupalGet('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name);
$this->assertOptionSelected('edit-default-value-input-default-date-type', 'relative', 'The default value is selected in instance settings page');
$this->assertFieldByName('default_value_input[default_date]', '+90 days', 'The relative default value is displayed in instance settings page');
$this->assertText('The relative date value entered is invalid.');
// Check if default_date has been stored successfully.
$config_entity = $this->config('field.field.node.date_content.' . $field_name)->get();
$this->assertEqual($config_entity['default_value'][0], array('default_date_type' => 'relative', 'default_date' => '+90 days'), 'Default value has been stored successfully');
// Set a relative default_value.
$field_edit = array(
'default_value_input[default_date_type]' => 'relative',
'default_value_input[default_date]' => '+90 days',
);
$this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
// Clear field cache in order to avoid stale cache values.
\Drupal::entityManager()->clearCachedFieldDefinitions();
// Check that default value is selected in default value form.
$this->drupalGet('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name);
$this->assertOptionSelected('edit-default-value-input-default-date-type', 'relative', 'The default value is selected in instance settings page');
$this->assertFieldByName('default_value_input[default_date]', '+90 days', 'The relative default value is displayed in instance settings page');
// Create a new node to check that datetime field default value is +90 days.
$new_node = Node::create(['type' => 'date_content']);
$expected_date = new DrupalDateTime('+90 days', DATETIME_STORAGE_TIMEZONE);
$this->assertEqual($new_node->get($field_name)->offsetGet(0)->value, $expected_date->format(DATETIME_DATE_STORAGE_FORMAT));
// Check if default_date has been stored successfully.
$config_entity = $this->config('field.field.node.date_content.' . $field_name)
->get();
$this->assertEqual($config_entity['default_value'][0], array(
'default_date_type' => 'relative',
'default_date' => '+90 days',
), 'Default value has been stored successfully');
// Remove default value.
$field_edit = array(
'default_value_input[default_date_type]' => '',
);
$this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
// Clear field cache in order to avoid stale cache values.
\Drupal::entityManager()->clearCachedFieldDefinitions();
// Check that default value is selected in default value form.
$this->drupalGet('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name);
$this->assertOptionSelected('edit-default-value-input-default-date-type', '', 'The default value is selected in instance settings page');
$this->assertFieldByName('default_value_input[default_date]', '', 'The relative default value is empty in instance settings page');
// Create a new node to check that datetime field default value is +90
// days.
$new_node = Node::create(['type' => 'date_content']);
$expected_date = new DrupalDateTime('+90 days', drupal_get_user_timezone());
$this->assertEqual($new_node->get($field_name)
->offsetGet(0)->value, $expected_date->format(DATETIME_DATE_STORAGE_FORMAT));
// Check if default_date has been stored successfully.
$config_entity = $this->config('field.field.node.date_content.' . $field_name)->get();
$this->assertTrue(empty($config_entity['default_value']), 'Empty default value has been stored successfully');
// Remove default value.
$field_edit = array(
'default_value_input[default_date_type]' => '',
);
$this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
// Clear field cache in order to avoid stale cache values.
\Drupal::entityManager()->clearCachedFieldDefinitions();
// Check that default value is selected in default value form.
$this->drupalGet('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name);
$this->assertOptionSelected('edit-default-value-input-default-date-type', '', 'The default value is selected in instance settings page');
$this->assertFieldByName('default_value_input[default_date]', '', 'The relative default value is empty in instance settings page');
// Create a new node to check that datetime field default value is not set.
$new_node = Node::create(['type' => 'date_content']);
$this->assertNull($new_node->get($field_name)->value, 'Default value is not set');
// Check if default_date has been stored successfully.
$config_entity = $this->config('field.field.node.date_content.' . $field_name)
->get();
$this->assertTrue(empty($config_entity['default_value']), 'Empty default value has been stored successfully');
// Clear field cache in order to avoid stale cache values.
\Drupal::entityManager()->clearCachedFieldDefinitions();
// Create a new node to check that datetime field default value is not
// set.
$new_node = Node::create(['type' => 'date_content']);
$this->assertNull($new_node->get($field_name)->value, 'Default value is not set');
}
}
/**

View file

@ -32,24 +32,27 @@ field.formatter.settings.daterange_default:
label: 'Date range default display format settings'
mapping:
separator:
type: string
type: label
label: 'Separator'
translation context: 'Date range separator'
field.formatter.settings.daterange_plain:
type: field.formatter.settings.datetime_plain
label: 'Date range plain display format settings'
mapping:
separator:
type: string
type: label
label: 'Separator'
translation context: 'Date range separator'
field.formatter.settings.daterange_custom:
type: field.formatter.settings.datetime_custom
label: 'Date range custom display format settings'
mapping:
separator:
type: string
type: label
label: 'Separator'
translation context: 'Date range separator'
field.widget.settings.daterange_datelist:
type: mapping

View file

@ -0,0 +1,22 @@
<?php
/**
* @file
* Post-update functions for Datetime Range module.
*/
/**
* @addtogroup updates-8.2.x
* @{
*/
/**
* Clear caches to ensure schema changes are read.
*/
function datetime_range_post_update_translatable_separator() {
// Empty post-update hook to cause a cache rebuild.
}
/**
* @} End of "addtogroup updates-8.2.x".
*/

View file

@ -58,13 +58,12 @@ class DateRangeDefaultFormatter extends DateTimeDefaultFormatter {
}
else {
$elements[$delta] = $this->buildDateWithIsoAttribute($start_date);
}
if (!empty($item->_attributes)) {
$elements[$delta]['#attributes'] += $item->_attributes;
// Unset field item attributes since they have been included in the
// formatter output and should not be rendered in the field template.
unset($item->_attributes);
if (!empty($item->_attributes)) {
$elements[$delta]['#attributes'] += $item->_attributes;
// Unset field item attributes since they have been included in the
// formatter output and should not be rendered in the field template.
unset($item->_attributes);
}
}
}
}

View file

@ -140,6 +140,11 @@ class DateRangeFieldTest extends DateTestBase {
]));
$this->assertText(' THESEPARATOR ', 'Found proper separator');
// Verify that hook_entity_prepare_view can add attributes.
// @see entity_test_entity_prepare_view()
$this->drupalGet('entity_test/' . $id);
$this->assertFieldByXPath('//div[@data-field-item-attr="foobar"]');
// Verify that the plain formatter works.
$this->displayOptions['type'] = 'daterange_plain';
$this->displayOptions['settings'] = $this->defaultSettings;
@ -203,6 +208,11 @@ class DateRangeFieldTest extends DateTestBase {
]));
$this->assertNoText(' THESEPARATOR ', 'Separator not found on page');
// Verify that hook_entity_prepare_view can add attributes.
// @see entity_test_entity_prepare_view()
$this->drupalGet('entity_test/' . $id);
$this->assertFieldByXPath('//time[@data-field-item-attr="foobar"]');
$this->displayOptions['type'] = 'daterange_plain';
$this->displayOptions['settings'] = $this->defaultSettings;
entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
@ -289,6 +299,11 @@ class DateRangeFieldTest extends DateTestBase {
$this->assertFieldByXPath('//time[@datetime="' . $end_expected_iso . '"]', $end_expected, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', ['%value' => 'long', '%expected' => $end_expected, '%expected_iso' => $end_expected_iso]));
$this->assertText(' THESEPARATOR ', 'Found proper separator');
// Verify that hook_entity_prepare_view can add attributes.
// @see entity_test_entity_prepare_view()
$this->drupalGet('entity_test/' . $id);
$this->assertFieldByXPath('//div[@data-field-item-attr="foobar"]');
// Verify that the plain formatter works.
$this->displayOptions['type'] = 'daterange_plain';
$this->displayOptions['settings'] = $this->defaultSettings;
@ -360,6 +375,11 @@ class DateRangeFieldTest extends DateTestBase {
$this->assertFieldByXPath('//time[@datetime="' . $start_expected_iso . '"]', $start_expected, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', ['%value' => 'long', '%expected' => $start_expected, '%expected_iso' => $start_expected_iso]));
$this->assertNoText(' THESEPARATOR ', 'Separator not found on page');
// Verify that hook_entity_prepare_view can add attributes.
// @see entity_test_entity_prepare_view()
$this->drupalGet('entity_test/' . $id);
$this->assertFieldByXPath('//time[@data-field-item-attr="foobar"]');
$this->displayOptions['type'] = 'daterange_plain';
$this->displayOptions['settings'] = $this->defaultSettings;
entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
@ -440,6 +460,11 @@ class DateRangeFieldTest extends DateTestBase {
$this->assertFieldByXPath('//time[@datetime="' . $end_expected_iso . '"]', $end_expected, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', ['%value' => 'long', '%expected' => $end_expected, '%expected_iso' => $end_expected_iso]));
$this->assertText(' THESEPARATOR ', 'Found proper separator');
// Verify that hook_entity_prepare_view can add attributes.
// @see entity_test_entity_prepare_view()
$this->drupalGet('entity_test/' . $id);
$this->assertFieldByXPath('//div[@data-field-item-attr="foobar"]');
// Verify that the plain formatter works.
$this->displayOptions['type'] = 'daterange_plain';
$this->displayOptions['settings'] = $this->defaultSettings;
@ -513,6 +538,11 @@ class DateRangeFieldTest extends DateTestBase {
$this->assertFieldByXPath('//time[@datetime="' . $end_expected_iso . '"]', $end_expected, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', ['%value' => 'long', '%expected' => $end_expected, '%expected_iso' => $end_expected_iso]));
$this->assertText(' THESEPARATOR ', 'Found proper separator');
// Verify that hook_entity_prepare_view can add attributes.
// @see entity_test_entity_prepare_view()
$this->drupalGet('entity_test/' . $id);
$this->assertFieldByXPath('//div[@data-field-item-attr="foobar"]');
$this->displayOptions['type'] = 'daterange_plain';
entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
->setComponent($field_name, $this->displayOptions)

View file

@ -0,0 +1,131 @@
<?php
namespace Drupal\Tests\datetime_range\Kernel;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Core\Language\Language;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\KernelTests\KernelTestBase;
use Drupal\language\Entity\ConfigurableLanguage;
/**
* Test to ensure the datetime range separator is translatable.
*
* @group datetime
*/
class SeparatorTranslationTest extends KernelTestBase {
/**
* A field storage to use in this test class.
*
* @var \Drupal\field\Entity\FieldStorageConfig
*/
protected $fieldStorage;
/**
* The field used in this test class.
*
* @var \Drupal\field\Entity\FieldConfig
*/
protected $field;
/**
* {@inheritdoc}
*/
public static $modules = [
'datetime',
'datetime_range',
'entity_test',
'field',
'language',
'system',
'user',
];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('entity_test');
$this->installEntitySchema('user');
$this->installConfig(['system']);
$this->installSchema('system', ['sequences', 'key_value']);
// Add a datetime range field.
$this->fieldStorage = FieldStorageConfig::create([
'field_name' => Unicode::strtolower($this->randomMachineName()),
'entity_type' => 'entity_test',
'type' => 'daterange',
'settings' => ['datetime_type' => DateTimeItem::DATETIME_TYPE_DATE],
]);
$this->fieldStorage->save();
$this->field = FieldConfig::create([
'field_storage' => $this->fieldStorage,
'bundle' => 'entity_test',
'required' => TRUE,
]);
$this->field->save();
$display_options = [
'type' => 'daterange_default',
'label' => 'hidden',
'settings' => [
'format_type' => 'fallback',
'separator' => 'UNTRANSLATED',
],
];
EntityViewDisplay::create([
'targetEntityType' => $this->field->getTargetEntityTypeId(),
'bundle' => $this->field->getTargetBundle(),
'mode' => 'default',
'status' => TRUE,
])->setComponent($this->fieldStorage->getName(), $display_options)
->save();
}
/**
* Tests the translation of the range separator.
*/
public function testSeparatorTranslation() {
// Create an entity.
$entity = EntityTest::create([
'name' => $this->randomString(),
$this->fieldStorage->getName() => [
'value' => '2016-09-20',
'end_value' => '2016-09-21',
],
]);
// Verify the untranslated separator.
$display = EntityViewDisplay::collectRenderDisplay($entity, 'default');
$build = $display->build($entity);
$output = $this->container->get('renderer')->renderRoot($build);
$this->verbose($output);
$this->assertContains('UNTRANSLATED', (string) $output);
// Translate the separator.
ConfigurableLanguage::createFromLangcode('nl')->save();
/** @var \Drupal\language\ConfigurableLanguageManagerInterface $language_manager */
$language_manager = $this->container->get('language_manager');
$language_manager->getLanguageConfigOverride('nl', 'core.entity_view_display.entity_test.entity_test.default')
->set('content.' . $this->fieldStorage->getName() . '.settings.separator', 'NL_TRANSLATED!')
->save();
$this->container->get('language.config_factory_override')
->setLanguage(new Language(['id' => 'nl']));
$this->container->get('cache_tags.invalidator')->invalidateTags($entity->getCacheTags());
$display = EntityViewDisplay::collectRenderDisplay($entity, 'default');
$build = $display->build($entity);
$output = $this->container->get('renderer')->renderRoot($build);
$this->verbose($output);
$this->assertContains('NL_TRANSLATED!', (string) $output);
}
}

View file

@ -467,6 +467,78 @@ function _editor_delete_file_usage(array $uuids, EntityInterface $entity, $count
}
}
/**
* Implements hook_file_download().
*
* @see file_file_download()
* @see file_get_file_references()
*/
function editor_file_download($uri) {
// Get the file record based on the URI. If not in the database just return.
/** @var \Drupal\file\FileInterface[] $files */
$files = \Drupal::entityTypeManager()
->getStorage('file')
->loadByProperties(['uri' => $uri]);
if (count($files)) {
foreach ($files as $item) {
// Since some database servers sometimes use a case-insensitive comparison
// by default, double check that the filename is an exact match.
if ($item->getFileUri() === $uri) {
$file = $item;
break;
}
}
}
if (!isset($file)) {
return;
}
// Temporary files are handled by file_file_download(), so nothing to do here
// about them.
// @see file_file_download()
// Find out if any editor-backed field contains the file.
$usage_list = \Drupal::service('file.usage')->listUsage($file);
// Stop processing if there are no references in order to avoid returning
// headers for files controlled by other modules. Make an exception for
// temporary files where the host entity has not yet been saved (for example,
// an image preview on a node creation form) in which case, allow download by
// the file's owner.
if (empty($usage_list['editor']) && ($file->isPermanent() || $file->getOwnerId() != \Drupal::currentUser()->id())) {
return;
}
// Editor.module MUST NOT call $file->access() here (like file_file_download()
// does) as checking the 'download' access to a file entity would end up in
// FileAccessControlHandler->checkAccess() and ->getFileReferences(), which
// calls file_get_file_references(). This latter one would allow downloading
// files only handled by the file.module, which is exactly not the case right
// here. So instead we must check if the current user is allowed to view any
// of the entities that reference the image using the 'editor' module.
if ($file->isPermanent()) {
$referencing_entity_is_accessible = FALSE;
$references = empty($usage_list['editor']) ? [] : $usage_list['editor'];
foreach ($references as $entity_type => $entity_ids) {
$referencing_entities = entity_load_multiple($entity_type, $entity_ids);
/** @var \Drupal\Core\Entity\EntityInterface $referencing_entity */
foreach ($referencing_entities as $referencing_entity) {
if ($referencing_entity->access('view', NULL, TRUE)->isAllowed()) {
$referencing_entity_is_accessible = TRUE;
break 2;
}
}
}
if (!$referencing_entity_is_accessible) {
return -1;
}
}
// Access is granted.
$headers = file_get_content_headers($file);
return $headers;
}
/**
* Finds all files referenced (data-entity-uuid) by formatted text fields.
*

View file

@ -0,0 +1,100 @@
<?php
namespace Drupal\editor\Tests;
use Drupal\file\Entity\File;
use Drupal\Tests\BrowserTestBase;
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;
/**
* Tests Editor module's file reference filter with private files.
*
* @group editor
*/
class EditorPrivateFileReferenceFilterTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = [
// Needed for the config: this is the only module in core that utilizes the
// functionality in editor.module to be tested, and depends on that.
'ckeditor',
// Depends on filter.module (indirectly).
'node',
// Pulls in the config we're using during testing which create a text format
// - with the filter_html_image_secure filter DISABLED,
// - with the editor set to CKEditor,
// - with drupalimage.image_upload.scheme set to 'private',
// - with drupalimage.image_upload.directory set to ''.
'editor_private_test',
];
/**
* Tests the editor file reference filter with private files.
*/
function testEditorPrivateFileReferenceFilter() {
$author = $this->drupalCreateUser();
$this->drupalLogin($author);
// Create a content type with a body field.
$this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']);
// Create a file in the 'private:// ' stream.
$filename = 'test.png';
$src = '/system/files/' . $filename;
/** @var \Drupal\file\FileInterface $file */
$file = File::create([
'uri' => 'private://' . $filename,
]);
$file->setTemporary();
$file->setOwner($author);
// Create the file itself.
file_put_contents($file->getFileUri(), $this->randomString());
$file->save();
// The image should be visible for its author.
$this->drupalGet($src);
$this->assertSession()->statusCodeEquals(200);
// The not-yet-permanent image should NOT be visible for anonymous.
$this->drupalLogout();
$this->drupalGet($src);
$this->assertSession()->statusCodeEquals(403);
// Resave the file to be permanent.
$file->setPermanent();
$file->save();
// Create a node with its body field properly pointing to the just-created
// file.
$node = $this->drupalCreateNode([
'type' => 'page',
'body' => [
'value' => '<img alt="alt" data-entity-type="file" data-entity-uuid="' . $file->uuid() . '" src="' . $src . '" />',
'format' => 'private_images',
],
'uid' => $author->id(),
]);
// Do the actual test. The image should be visible for anonymous users,
// because they can view the referencing entity.
$this->drupalGet($node->toUrl());
$this->assertSession()->statusCodeEquals(200);
$this->drupalGet($src);
$this->assertSession()->statusCodeEquals(200);
// Disallow anonymous users to view the entity, which then should also
// disallow them to view the image.
Role::load(RoleInterface::ANONYMOUS_ID)
->revokePermission('access content')
->save();
$this->drupalGet($node->toUrl());
$this->assertSession()->statusCodeEquals(403);
$this->drupalGet($src);
$this->assertSession()->statusCodeEquals(403);
}
}

View file

@ -0,0 +1,34 @@
format: private_images
status: true
langcode: en
editor: ckeditor
settings:
toolbar:
rows:
-
-
name: Media
items:
- DrupalImage
-
name: Tools
items:
- Source
plugins:
language:
language_list: un
stylescombo:
styles: ''
image_upload:
status: true
scheme: private
directory: ''
max_size: ''
max_dimensions:
width: null
height: null
dependencies:
config:
- filter.format.private_images
module:
- ckeditor

View file

@ -0,0 +1,23 @@
format: private_images
name: 'Private images'
status: true
langcode: en
filters:
editor_file_reference:
id: editor_file_reference
provider: editor
status: true
weight: 0
settings: { }
filter_html:
id: filter_html
provider: filter
status: false
weight: -10
settings:
allowed_html: '<img src alt data-entity-type data-entity-uuid>'
filter_html_help: true
filter_html_nofollow: false
dependencies:
module:
- editor

View file

@ -0,0 +1,9 @@
name: 'Text Editor Private test'
type: module
description: 'Support module for the Text Editor Private module tests.'
core: 8.x
package: Testing
version: VERSION
dependencies:
- filter
- ckeditor

View file

@ -15,105 +15,101 @@ process:
langcode: 'constants/langcode'
field_name: field_name
type:
-
plugin: field_type
source:
- type
- widget_type
map:
number_integer:
number: integer
optionwidgets_select: list_integer
optionwidgets_buttons: list_integer
optionwidgets_onoff: boolean
number_decimal:
number: decimal
optionwidgets_select: list_float
optionwidgets_buttons: list_float
optionwidgets_onoff: boolean
number_float:
number: float
optionwidgets_select: list_float
optionwidgets_buttons: list_float
optionwidgets_onoff: boolean
email:
email_textfield: email
filefield:
imagefield_widget: image
filefield_widget: file
date:
date_select: datetime
datestamp:
date_select: datetime
datetime:
date_select: datetime
fr_phone:
phone_textfield: telephone
be_phone:
phone_textfield: telephone
it_phone:
phone_textfield: telephone
el_phone:
phone_textfield: telephone
ch_phone:
phone_textfield: telephone
ca_phone:
phone_textfield: telephone
cr_phone:
phone_textfield: telephone
pa_phone:
phone_textfield: telephone
gb_phone:
phone_textfield: telephone
ru_phone:
phone_textfield: telephone
ua_phone:
phone_textfield: telephone
es_phone:
phone_textfield: telephone
au_phone:
phone_textfield: telephone
cs_phone:
phone_textfield: telephone
hu_phone:
phone_textfield: telephone
pl_phone:
phone_textfield: telephone
nl_phone:
phone_textfield: telephone
se_phone:
phone_textfield: telephone
za_phone:
phone_textfield: telephone
il_phone:
phone_textfield: telephone
nz_phone:
phone_textfield: telephone
br_phone:
phone_textfield: telephone
cl_phone:
phone_textfield: telephone
cn_phone:
phone_textfield: telephone
hk_phone:
phone_textfield: telephone
mo_phone:
phone_textfield: telephone
ph_phone:
phone_textfield: telephone
sg_phone:
phone_textfield: telephone
jo_phone:
phone_textfield: telephone
eg_phone:
phone_textfield: telephone
pk_phone:
phone_textfield: telephone
int_phone:
phone_textfield: telephone
-
plugin: skip_on_empty
method: row
plugin: field_type
source:
- type
- widget_type
map:
number_integer:
number: integer
optionwidgets_select: list_integer
optionwidgets_buttons: list_integer
optionwidgets_onoff: boolean
number_decimal:
number: decimal
optionwidgets_select: list_float
optionwidgets_buttons: list_float
optionwidgets_onoff: boolean
number_float:
number: float
optionwidgets_select: list_float
optionwidgets_buttons: list_float
optionwidgets_onoff: boolean
email:
email_textfield: email
filefield:
imagefield_widget: image
filefield_widget: file
date:
date_select: datetime
datestamp:
date_select: datetime
datetime:
date_select: datetime
fr_phone:
phone_textfield: telephone
be_phone:
phone_textfield: telephone
it_phone:
phone_textfield: telephone
el_phone:
phone_textfield: telephone
ch_phone:
phone_textfield: telephone
ca_phone:
phone_textfield: telephone
cr_phone:
phone_textfield: telephone
pa_phone:
phone_textfield: telephone
gb_phone:
phone_textfield: telephone
ru_phone:
phone_textfield: telephone
ua_phone:
phone_textfield: telephone
es_phone:
phone_textfield: telephone
au_phone:
phone_textfield: telephone
cs_phone:
phone_textfield: telephone
hu_phone:
phone_textfield: telephone
pl_phone:
phone_textfield: telephone
nl_phone:
phone_textfield: telephone
se_phone:
phone_textfield: telephone
za_phone:
phone_textfield: telephone
il_phone:
phone_textfield: telephone
nz_phone:
phone_textfield: telephone
br_phone:
phone_textfield: telephone
cl_phone:
phone_textfield: telephone
cn_phone:
phone_textfield: telephone
hk_phone:
phone_textfield: telephone
mo_phone:
phone_textfield: telephone
ph_phone:
phone_textfield: telephone
sg_phone:
phone_textfield: telephone
jo_phone:
phone_textfield: telephone
eg_phone:
phone_textfield: telephone
pk_phone:
phone_textfield: telephone
int_phone:
phone_textfield: telephone
cardinality:
plugin: static_map
bypass: true
@ -126,6 +122,5 @@ process:
source:
- '@type'
- global_settings
destination:
plugin: md_entity:field_storage_config

View file

@ -21,8 +21,6 @@ class FieldInstancePerFormDisplay extends DrupalSqlBase {
$rows = array();
$result = $this->prepareQuery()->execute();
while ($field_row = $result->fetchAssoc()) {
$field_row['display_settings'] = unserialize($field_row['display_settings']);
$field_row['widget_settings'] = unserialize($field_row['widget_settings']);
$bundle = $field_row['type_name'];
$field_name = $field_row['field_name'];
@ -34,7 +32,8 @@ class FieldInstancePerFormDisplay extends DrupalSqlBase {
$rows[$index]['module'] = $field_row['module'];
$rows[$index]['weight'] = $field_row['weight'];
$rows[$index]['widget_type'] = $field_row['widget_type'];
$rows[$index]['widget_settings'] = $field_row['widget_settings'];
$rows[$index]['widget_settings'] = unserialize($field_row['widget_settings']);
$rows[$index]['display_settings'] = unserialize($field_row['display_settings']);
}
return new \ArrayIterator($rows);

View file

@ -165,4 +165,30 @@ class NestedFormTest extends FieldTestBase {
$this->assertFieldValues($entity_2, 'field_unlimited', array(13, 14, 15));
}
/**
* Tests entity level validation within subforms.
*/
public function testNestedEntityFormEntityLevelValidation() {
// Create two entities.
$storage = $this->container->get('entity_type.manager')
->getStorage('entity_test_constraints');
$entity_1 = $storage->create();
$entity_1->save();
$entity_2 = $storage->create();
$entity_2->save();
// Display the 'combined form'.
$this->drupalGet("test-entity-constraints/nested/{$entity_1->id()}/{$entity_2->id()}");
$this->assertFieldByName('entity_2[changed]', 0, 'Entity 2: changed value appears correctly in the form.');
// Submit the form and check that the entities are updated accordingly.
$edit = ['entity_2[changed]' => REQUEST_TIME - 86400];
$this->drupalPostForm(NULL, $edit, t('Save'));
$elements = $this->cssSelect('.entity-2.error');
$this->assertEqual(1, count($elements), 'The whole nested entity form has been correctly flagged with an error class.');
}
}

View file

@ -11,3 +11,17 @@ field_test.entity_nested_form:
type: 'entity:entity_test'
requirements:
_permission: 'administer entity_test content'
field_test.entity_constraints_nested_form:
path: '/test-entity-constraints/nested/{entity_1}/{entity_2}'
defaults:
_title: 'Nested entity form'
_form: '\Drupal\field_test\Form\NestedEntityTestForm'
options:
parameters:
entity_1:
type: 'entity:entity_test_constraints'
entity_2:
type: 'entity:entity_test_constraints'
requirements:
_permission: 'administer entity_test content'

View file

@ -2,6 +2,7 @@
namespace Drupal\field_test\Form;
use Drupal\Core\Entity\EntityChangedInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
@ -33,16 +34,26 @@ class NestedEntityTestForm extends FormBase {
$form_state->set('entity_2', $entity_2);
$form_display_2 = EntityFormDisplay::collectRenderDisplay($entity_2, 'default');
$form_state->set('form_display_2', $form_display_2);
$form['entity_2'] = array(
$form['entity_2'] = [
'#type' => 'details',
'#title' => t('Second entity'),
'#tree' => TRUE,
'#parents' => array('entity_2'),
'#parents' => ['entity_2'],
'#weight' => 50,
);
'#attributes' => ['class' => ['entity-2']]
];
$form_display_2->buildForm($entity_2, $form['entity_2'], $form_state);
if ($entity_2 instanceof EntityChangedInterface) {
// Changed must be sent to the client, for later overwrite error checking.
// @see Drupal\field\Tests\NestedFormTest::testNestedEntityFormEntityLevelValidation()
$form['entity_2']['changed'] = [
'#type' => 'hidden',
'#default_value' => $entity_1->getChangedTime(),
];
}
$form['save'] = array(
'#type' => 'submit',
'#value' => t('Save'),
@ -65,7 +76,16 @@ class NestedEntityTestForm extends FormBase {
$entity_2 = $form_state->get('entity_2');
/** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display_2 */
$form_display_2 = $form_state->get('form_display_2');
$form_display_2->extractFormValues($entity_2, $form['entity_2'], $form_state);
$extracted = $form_display_2->extractFormValues($entity_2, $form['entity_2'], $form_state);
// Extract the values of fields that are not rendered through widgets, by
// simply copying from top-level form values. This leaves the fields that
// are not being edited within this form untouched.
// @see Drupal\field\Tests\NestedFormTest::testNestedEntityFormEntityLevelValidation()
foreach ($form_state->getValues()['entity_2'] as $name => $values) {
if ($entity_2->hasField($name) && !isset($extracted[$name])) {
$entity_2->set($name, $values);
}
}
$form_display_2->validateFormValues($entity_2, $form['entity_2'], $form_state);
}

View file

@ -96,6 +96,19 @@ class MigrateFieldInstanceTest extends MigrateDrupal7TestBase {
$this->assertIdentical($expected_entity_type . '.' . $expected_name, $field->getFieldStorageDefinition()->id());
}
/**
* Asserts the settings of a link field config entity.
*
* @param $id
* The entity ID in the form ENTITY_TYPE.BUNDLE.FIELD_NAME.
* @param $title_setting
* The expected title setting.
*/
protected function assertLinkFields($id, $title_setting) {
$field = FieldConfig::load($id);
$this->assertSame($title_setting, $field->getSetting('title'));
}
/**
* Tests migrating D7 field instances to field_config entities.
*/
@ -131,6 +144,10 @@ class MigrateFieldInstanceTest extends MigrateDrupal7TestBase {
$this->assertEntity('node.test_content_type.field_text', 'Text', 'text', FALSE);
$this->assertEntity('comment.comment_node_test_content_type.field_integer', 'Integer', 'integer', FALSE);
$this->assertEntity('user.user.field_file', 'File', 'file', FALSE);
$this->assertLinkFields('node.test_content_type.field_link', DRUPAL_OPTIONAL);
$this->assertLinkFields('node.article.field_link', DRUPAL_DISABLED);
$this->assertLinkFields('node.blog.field_link', DRUPAL_REQUIRED);
}
}

View file

@ -0,0 +1,80 @@
<?php
namespace Drupal\Tests\field\Kernel\Plugin\migrate\source\d6;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests d6_field_instance_per_form_display source plugin.
*
* @covers \Drupal\field\Plugin\migrate\source\d6\FieldInstancePerFormDisplay
* @group field
*/
class FieldInstancePerFormDisplayTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['field', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public function providerSource() {
$tests = [
[
'source_data' => [],
'expected_data' => [],
],
];
// The expected results.
$tests[0]['expected_data'] = [
[
'display_settings' => [],
'widget_settings' => [],
'type_name' => 'story',
'widget_active' => TRUE,
'field_name' => 'field_test_filefield',
'type' => 'filefield',
'module' => 'filefield',
'weight' => '8',
'widget_type' => 'filefield_widget',
],
];
// The source data.
$empty_array = serialize([]);
$tests[0]['source_data']['content_node_field'] = [
[
'field_name' => 'field_test_filefield',
'type' => 'filefield',
'global_settings' => $empty_array,
'required' => '0',
'multiple' => '0',
'db_storage' => '1',
'module' => 'filefield',
'db_columns' => $empty_array,
'active' => '1',
'locked' => '0',
]
];
$tests[0]['source_data']['content_node_field_instance'] = [
[
'field_name' => 'field_test_filefield',
'type_name' => 'story',
'weight' => '8',
'label' => 'File Field',
'widget_type' => 'filefield_widget',
'widget_settings' => $empty_array,
'display_settings' => $empty_array,
'description' => 'An example image field.',
'widget_module' => 'filefield',
'widget_active' => '1',
],
];
return $tests;
}
}

View file

@ -0,0 +1,101 @@
<?php
namespace Drupal\Tests\field\Kernel\Plugin\migrate\source\d6;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests D6 fields per view mode source plugin.
*
* @covers \Drupal\field\Plugin\migrate\source\d6\FieldInstancePerViewMode
* @group field
*/
class FieldInstancePerViewModeTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['field', 'migrate_drupal', 'node', 'user'];
/**
* {@inheritdoc}
*/
public function providerSource() {
$tests = [
[
'source_data' => [],
'expected_data' => [],
],
];
// The expected results.
$tests[0]['expected_data'] = [
[
'entity_type' => 'node',
'view_mode' => 4,
'type_name' => 'article',
'field_name' => 'field_test',
'type' => 'text',
'module' => 'text',
'weight' => 1,
'label' => 'above',
'display_settings' => [
'weight' => 1,
'parent' => '',
'label' => [
'format' => 'above',
],
4 => [
'format' => 'trimmed',
'exclude' => 0,
],
],
'widget_settings' => [],
],
[
'entity_type' => 'node',
'view_mode' => 'teaser',
'type_name' => 'story',
'field_name' => 'field_test',
'type' => 'text',
'module' => 'text',
'weight' => 2,
'label' => 'above',
'display_settings' => [
'weight' => 1,
'parent' => '',
'label' => [
'format' => 'above',
],
'teaser' => [
'format' => 'trimmed',
'exclude' => 0,
],
],
'widget_settings' => [],
],
];
// The source data.
foreach ($tests[0]['expected_data'] as $k => $field_view_mode) {
// These are stored as serialized strings.
$field_view_mode['display_settings'] = serialize($field_view_mode['display_settings']);
$field_view_mode['widget_settings'] = serialize($field_view_mode['widget_settings']);
$tests[0]['source_data']['content_node_field'][] = [
'field_name' => $field_view_mode['field_name'],
'type' => $field_view_mode['type'],
'module' => $field_view_mode['module'],
];
unset($field_view_mode['type'], $field_view_mode['module']);
$tests[0]['source_data']['content_node_field_instance'][] = $field_view_mode;
// Update the expected display settings.
$tests[0]['expected_data'][$k]['display_settings'] = $tests[0]['expected_data'][$k]['display_settings'][$field_view_mode['view_mode']];
}
return $tests;
}
}

View file

@ -0,0 +1,96 @@
<?php
namespace Drupal\Tests\field\Kernel\Plugin\migrate\source\d6;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests D6 field instance source plugin.
*
* @covers \Drupal\field\Plugin\migrate\source\d6\FieldInstance
* @group field
*/
class FieldInstanceTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['field', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public function providerSource() {
$tests = [
[
'source_data' => [],
'expected_data' => [],
],
];
// The expected results.
$tests[0]['expected_data'] = [
[
'field_name' => 'field_body',
'type_name' => 'page',
'weight' => 1,
'label' => 'body',
'widget_type' => 'text_textarea',
'description' => '',
'widget_module' => 'text',
'widget_active' => 1,
'required' => 1,
'active' => 1,
'global_settings' => [],
'widget_settings' => [
'rows' => 5,
'size' => 60,
'default_value' => [
[
'value' => '',
'_error_element' => 'default_value_widget][field_body][0][value',
'default_value_php' => '',
],
],
],
'display_settings' => [
'label' => [
'format' => 'above',
'exclude' => 0,
],
'teaser' => [
'format' => 'default',
'exclude' => 0,
],
'full' => [
'format' => 'default',
'exclude' => 0,
],
],
],
];
// The source data.
$tests[0]['source_data']['content_node_field_instance'] = array_map(
function (array $row) {
$row['widget_settings'] = serialize($row['widget_settings']);
$row['display_settings'] = serialize($row['display_settings']);
$row['global_settings'] = serialize($row['global_settings']);
return $row;
},
$tests[0]['expected_data']
);
$tests[0]['source_data']['content_node_field'] = [
[
'field_name' => 'field_body',
'required' => 1,
'type' => 'text',
'active' => 1,
'global_settings' => serialize([]),
],
];
return $tests;
}
}

View file

@ -0,0 +1,79 @@
<?php
namespace Drupal\Tests\field\Kernel\Plugin\migrate\source\d6;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests D6 field source plugin.
*
* @covers \Drupal\field\Plugin\migrate\source\d6\Field
* @group field
*/
class FieldTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['field', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public function providerSource() {
$tests = [
[
'source_data' => [],
'expected_data' => [],
],
];
// The expected results.
$tests[0]['expected_data'] = [
[
'field_name' => 'field_body',
'type' => 'text',
'global_settings' => [
'text_processing' => 0,
'max_length' => '',
'allowed_values' => '',
'allowed_values_php' => '',
],
'required' => 0,
'multiple' => 0,
'db_storage' => 1,
'module' => 'text',
'db_columns' => [
'value' => [
'type' => 'text',
'size' => 'big',
'not null' => '',
'sortable' => 1,
'views' => 1,
],
],
'active' => 1,
'locked' => 0,
],
];
// The source data.
$tests[0]['source_data']['content_node_field'] = array_map(
function (array $row) {
$row['global_settings'] = serialize($row['global_settings']);
$row['db_columns'] = serialize($row['db_columns']);
return $row;
},
$tests[0]['expected_data']
);
$tests[0]['source_data']['content_node_field_instance'] = [
[
'widget_type' => 'text_textarea',
'field_name' => 'field_body',
],
];
return $tests;
}
}

View file

@ -1,84 +1,36 @@
<?php
namespace Drupal\Tests\field\Unit\Plugin\migrate\source\d7;
namespace Drupal\Tests\field\Kernel\Plugin\migrate\source\d7;
use Drupal\Tests\migrate\Unit\MigrateSqlSourceTestCase;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests D7 field instance per form display source plugin.
*
* @covers \Drupal\field\Plugin\migrate\source\d7\FieldInstancePerFormDisplay
* @group field
*/
class FieldInstancePerFormDisplayTest extends MigrateSqlSourceTestCase {
const PLUGIN_CLASS = 'Drupal\field\Plugin\migrate\source\d7\FieldInstancePerFormDisplay';
protected $migrationConfiguration = array(
'id' => 'test_fieldinstance',
'source' => array(
'plugin' => 'd7_field_instance_per_form_display',
),
);
// We need to set up the database contents; it's easier to do that below.
// These are sample result queries.
protected $expectedResults = array(
array(
'field_name' => 'body',
'entity_type' => 'node',
'bundle' => 'page',
'widget_settings' => array(
),
'display_settings' => array(
),
'description' => '',
'required' => FALSE,
'global_settings' => array(),
),
array(
'field_name' => 'field_file',
'entity_type' => 'user',
'bundle' => 'user',
'widget_settings' => array(
),
'display_settings' => array(
),
'description' => '',
'required' => FALSE,
'global_settings' => array(),
),
array(
'field_name' => 'field_integer',
'entity_type' => 'comment',
'bundle' => 'comment_node_test_content_type',
'widget_settings' => array(
),
'display_settings' => array(
),
'description' => '',
'required' => FALSE,
'global_settings' => array(),
),
array(
'field_name' => 'field_link',
'entity_type' => 'taxonomy_term',
'bundle' => 'test_vocabulary',
'widget_settings' => array(
),
'display_settings' => array(
),
'description' => '',
'required' => FALSE,
'global_settings' => array(),
),
);
class FieldInstancePerFormDisplayTest extends MigrateSqlSourceTestBase {
/**
* Prepopulate contents with results.
* {@inheritdoc}
*/
protected function setUp() {
$this->databaseContents['field_config_instance'] = array(
array(
public static $modules = ['field', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public function providerSource() {
$tests = [
[
'source_data' => [],
'expected_data' => [],
],
];
// The source data.
$tests[0]['source_data']['field_config_instance'] = [
[
'id' => '2',
'field_id' => '2',
'field_name' => 'body',
@ -86,8 +38,8 @@ class FieldInstancePerFormDisplayTest extends MigrateSqlSourceTestCase {
'bundle' => 'page',
'data' => 'a:6:{s:5:"label";s:4:"Body";s:6:"widget";a:4:{s:4:"type";s:26:"text_textarea_with_summary";s:8:"settings";a:2:{s:4:"rows";i:20;s:12:"summary_rows";i:5;}s:6:"weight";i:-4;s:6:"module";s:4:"text";}s:8:"settings";a:3:{s:15:"display_summary";b:1;s:15:"text_processing";i:1;s:18:"user_register_form";b:0;}s:7:"display";a:2:{s:7:"default";a:5:{s:5:"label";s:6:"hidden";s:4:"type";s:12:"text_default";s:8:"settings";a:0:{}s:6:"module";s:4:"text";s:6:"weight";i:0;}s:6:"teaser";a:5:{s:5:"label";s:6:"hidden";s:4:"type";s:23:"text_summary_or_trimmed";s:8:"settings";a:1:{s:11:"trim_length";i:600;}s:6:"module";s:4:"text";s:6:"weight";i:0;}}s:8:"required";b:0;s:11:"description";s:0:"";}',
'deleted' => '0',
),
array(
],
[
'id' => '33',
'field_id' => '11',
'field_name' => 'field_file',
@ -95,8 +47,8 @@ class FieldInstancePerFormDisplayTest extends MigrateSqlSourceTestCase {
'bundle' => 'user',
'data' => 'a:6:{s:5:"label";s:4:"File";s:6:"widget";a:5:{s:6:"weight";s:1:"8";s:4:"type";s:12:"file_generic";s:6:"module";s:4:"file";s:6:"active";i:1;s:8:"settings";a:1:{s:18:"progress_indicator";s:8:"throbber";}}s:8:"settings";a:5:{s:14:"file_directory";s:0:"";s:15:"file_extensions";s:3:"txt";s:12:"max_filesize";s:0:"";s:17:"description_field";i:0;s:18:"user_register_form";i:0;}s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:5:"above";s:4:"type";s:12:"file_default";s:8:"settings";a:0:{}s:6:"module";s:4:"file";s:6:"weight";i:0;}}s:8:"required";i:0;s:11:"description";s:0:"";}',
'deleted' => '0',
),
array(
],
[
'id' => '32',
'field_id' => '14',
'field_name' => 'field_integer',
@ -104,8 +56,8 @@ class FieldInstancePerFormDisplayTest extends MigrateSqlSourceTestCase {
'bundle' => 'comment_node_test_content_type',
'data' => 'a:7:{s:5:"label";s:7:"Integer";s:6:"widget";a:5:{s:6:"weight";s:1:"2";s:4:"type";s:6:"number";s:6:"module";s:6:"number";s:6:"active";i:0;s:8:"settings";a:0:{}}s:8:"settings";a:5:{s:3:"min";s:0:"";s:3:"max";s:0:"";s:6:"prefix";s:0:"";s:6:"suffix";s:0:"";s:18:"user_register_form";b:0;}s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:5:"above";s:4:"type";s:14:"number_integer";s:8:"settings";a:4:{s:18:"thousand_separator";s:1:" ";s:17:"decimal_separator";s:1:".";s:5:"scale";i:0;s:13:"prefix_suffix";b:1;}s:6:"module";s:6:"number";s:6:"weight";i:1;}}s:8:"required";i:0;s:11:"description";s:0:"";s:13:"default_value";N;}',
'deleted' => '0',
),
array(
],
[
'id' => '25',
'field_id' => '15',
'field_name' => 'field_link',
@ -113,10 +65,10 @@ class FieldInstancePerFormDisplayTest extends MigrateSqlSourceTestCase {
'bundle' => 'test_vocabulary',
'data' => 'a:7:{s:5:"label";s:4:"Link";s:6:"widget";a:5:{s:6:"weight";s:2:"10";s:4:"type";s:10:"link_field";s:6:"module";s:4:"link";s:6:"active";i:0;s:8:"settings";a:0:{}}s:8:"settings";a:12:{s:12:"absolute_url";i:1;s:12:"validate_url";i:1;s:3:"url";i:0;s:5:"title";s:8:"optional";s:11:"title_value";s:19:"Unused Static Title";s:27:"title_label_use_field_label";i:0;s:15:"title_maxlength";s:3:"128";s:7:"display";a:1:{s:10:"url_cutoff";s:2:"81";}s:10:"attributes";a:6:{s:6:"target";s:6:"_blank";s:3:"rel";s:8:"nofollow";s:18:"configurable_class";i:0;s:5:"class";s:7:"classes";s:18:"configurable_title";i:1;s:5:"title";s:0:"";}s:10:"rel_remove";s:19:"rel_remove_external";s:13:"enable_tokens";i:1;s:18:"user_register_form";b:0;}s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:5:"above";s:4:"type";s:12:"link_default";s:6:"weight";s:1:"9";s:8:"settings";a:0:{}s:6:"module";s:4:"link";}}s:8:"required";i:0;s:11:"description";s:0:"";s:13:"default_value";N;}',
'deleted' => '0',
),
);
$this->databaseContents['field_config'] = array(
array(
],
];
$tests[0]['source_data']['field_config'] = [
[
'id' => '2',
'field_name' => 'body',
'type' => 'text_with_summary',
@ -130,8 +82,8 @@ class FieldInstancePerFormDisplayTest extends MigrateSqlSourceTestCase {
'cardinality' => '1',
'translatable' => '0',
'deleted' => '0',
),
array(
],
[
'id' => '11',
'field_name' => 'field_file',
'type' => 'file',
@ -145,8 +97,8 @@ class FieldInstancePerFormDisplayTest extends MigrateSqlSourceTestCase {
'cardinality' => '1',
'translatable' => '0',
'deleted' => '0',
),
array(
],
[
'id' => '14',
'field_name' => 'field_integer',
'type' => 'number_integer',
@ -160,8 +112,8 @@ class FieldInstancePerFormDisplayTest extends MigrateSqlSourceTestCase {
'cardinality' => '1',
'translatable' => '0',
'deleted' => '0',
),
array(
],
[
'id' => '15',
'field_name' => 'field_link',
'type' => 'link_field',
@ -175,10 +127,43 @@ class FieldInstancePerFormDisplayTest extends MigrateSqlSourceTestCase {
'cardinality' => '1',
'translatable' => '0',
'deleted' => '0',
),
);
],
];
parent::setUp();
// The expected results.
$tests[0]['expected_data'] = [
[
'field_name' => 'body',
'entity_type' => 'node',
'bundle' => 'page',
'widget_settings' => [
'rows' => 20,
'summary_rows' => 5,
],
],
[
'field_name' => 'field_file',
'entity_type' => 'user',
'bundle' => 'user',
'widget_settings' => [
'progress_indicator' => 'throbber',
],
],
[
'field_name' => 'field_integer',
'entity_type' => 'comment',
'bundle' => 'comment_node_test_content_type',
'widget_settings' => [],
],
[
'field_name' => 'field_link',
'entity_type' => 'taxonomy_term',
'bundle' => 'test_vocabulary',
'widget_settings' => [],
],
];
return $tests;
}
}

Some files were not shown because too many files have changed in this diff Show more