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

This commit is contained in:
Pantheon Automation 2017-01-04 16:50:53 -08:00 committed by Greg Anderson
parent 8544b60b39
commit db56c09587
86 changed files with 2413 additions and 488 deletions

View file

@ -123,7 +123,6 @@ Color
- ?
Comment
- Dick Olsson 'dixon_' https://www.drupal.org/u/dixon_
- Lee Rowlands 'larowlan' https://www.drupal.org/u/larowlan
- Andrey Postnikov 'andypost' https://www.drupal.org/u/andypost

View file

@ -74,7 +74,7 @@ function drupal_get_schema_versions($module) {
* module is not installed.
*/
function drupal_get_installed_schema_version($module, $reset = FALSE, $array = FALSE) {
static $versions = array();
$versions = &drupal_static(__FUNCTION__, array());
if ($reset) {
$versions = array();

View file

@ -81,7 +81,7 @@ class Drupal {
/**
* The current system version.
*/
const VERSION = '8.2.4';
const VERSION = '8.2.5';
/**
* Core API compatibility.
@ -556,8 +556,7 @@ class Drupal {
* Renders a link with a given link text and Url object.
*
* This method is a convenience wrapper for the link generator service's
* generate() method. For detailed documentation, see
* \Drupal\Core\Routing\LinkGeneratorInterface::generate().
* generate() method.
*
* @param string $text
* The link text for the anchor tag.

View file

@ -11,6 +11,7 @@ use Drupal\Core\Render\Element;
* Properties:
* - #default_value: An array with the keys: 'year', 'month', and 'day'.
* Defaults to the current date if no value is supplied.
* - #size: The size of the input element in characters.
*
* @code
* $form['expiration'] = array(

View file

@ -10,6 +10,7 @@ use Drupal\Core\Render\Element;
*
* Properties:
* - #default_value: An RFC-compliant email address.
* - #size: The size of the input element in characters.
*
* Example usage:
* @code

View file

@ -16,6 +16,7 @@ use Drupal\Component\Utility\Number as NumberUtility;
* - #step: Ensures that the number is an even multiple of step, offset by #min
* if specified. A #min of 1 and a #step of 2 would allow values of 1, 3, 5,
* etc.
* - #size: The size of the input element in characters.
*
* Usage example:
* @code

View file

@ -8,6 +8,9 @@ use Drupal\Core\Render\Element;
/**
* Provides a form element for entering a password, with hidden text.
*
* Properties:
* - #size: The size of the input element in characters.
*
* Usage example:
* @code
* $form['pass'] = array(

View file

@ -10,6 +10,9 @@ use Drupal\Core\Form\FormStateInterface;
* Formats as a pair of password fields, which do not validate unless the two
* entered passwords match.
*
* Properties:
* - #size: The size of the input element in characters.
*
* Usage example:
* @code
* $form['pass'] = array(

View file

@ -14,8 +14,31 @@ use Drupal\Core\Render\Element;
* list. If a value is an array, it will be rendered similarly, but as an
* optgroup. The key of the sub-array will be used as the label for the
* optgroup. Nesting optgroups is not allowed.
* - #empty_option: The label that will be displayed to denote no selection.
* - #empty_value: The value of the option that is used to denote no selection.
* - #empty_option: (optional) The label to show for the first default option.
* By default, the label is automatically set to "- Select -" for a required
* field and "- None -" for an optional field.
* - #empty_value: (optional) The value for the first default option, which is
* used to determine whether the user submitted a value or not.
* - If #required is TRUE, this defaults to '' (an empty string).
* - If #required is not TRUE and this value isn't set, then no extra option
* is added to the select control, leaving the control in a slightly
* illogical state, because there's no way for the user to select nothing,
* since all user agents automatically preselect the first available
* option. But people are used to this being the behavior of select
* controls.
* @todo Address the above issue in Drupal 8.
* - If #required is not TRUE and this value is set (most commonly to an
* empty string), then an extra option (see #empty_option above)
* representing a "non-selection" is added with this as its value.
* - #multiple: (optional) Indicates whether one or more options can be
* selected. Defaults to FALSE.
* - #default_value: Must be NULL or not set in case there is no value for the
* element yet, in which case a first default option is inserted by default.
* Whether this first option is a valid option depends on whether the field
* is #required or not.
* - #required: (optional) Whether the user needs to select an option (TRUE)
* or not (FALSE). Defaults to FALSE.
* - #size: The size of the input element in characters.
*
* Usage example:
* @code
@ -66,31 +89,7 @@ class Select extends FormElement {
* select lists.
*
* @param array $element
* The form element to process. Properties used:
* - #multiple: (optional) Indicates whether one or more options can be
* selected. Defaults to FALSE.
* - #default_value: Must be NULL or not set in case there is no value for the
* element yet, in which case a first default option is inserted by default.
* Whether this first option is a valid option depends on whether the field
* is #required or not.
* - #required: (optional) Whether the user needs to select an option (TRUE)
* or not (FALSE). Defaults to FALSE.
* - #empty_option: (optional) The label to show for the first default option.
* By default, the label is automatically set to "- Select -" for a required
* field and "- None -" for an optional field.
* - #empty_value: (optional) The value for the first default option, which is
* used to determine whether the user submitted a value or not.
* - If #required is TRUE, this defaults to '' (an empty string).
* - If #required is not TRUE and this value isn't set, then no extra option
* is added to the select control, leaving the control in a slightly
* illogical state, because there's no way for the user to select nothing,
* since all user agents automatically preselect the first available
* option. But people are used to this being the behavior of select
* controls.
* @todo Address the above issue in Drupal 8.
* - If #required is not TRUE and this value is set (most commonly to an
* empty string), then an extra option (see #empty_option above)
* representing a "non-selection" is added with this as its value.
* The form element to process.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param array $complete_form

View file

@ -24,6 +24,7 @@ use Drupal\Component\Utility\Html as HtmlUtility;
* providing responsive tables. Defaults to TRUE.
* - #sticky: Indicates whether to add the drupal.tableheader library that makes
* table headers always visible at the top of the page. Defaults to FALSE.
* - #size: The size of the input element in characters.
*
* Usage example:
* @code

View file

@ -10,6 +10,9 @@ use Drupal\Core\Render\Element;
* Provides an HTML5 input element with type of "tel". It provides no special
* validation.
*
* Properties:
* - #size: The size of the input element in characters.
*
* Usage example:
* @code
* $form['phone'] = array(

View file

@ -11,6 +11,7 @@ use Drupal\Core\Render\Element;
*
* Properties:
* - #default_value: A valid URL string.
* - #size: The size of the input element in characters.
*
* Usage example:
* @code

View file

@ -254,13 +254,13 @@
* form array, which specifies the form elements for an HTML form; see the
* @link form_api Form generation topic @endlink for more information on forms.
*
* Render arrays (at each level in the hierarchy) will usually have one of the
* following three properties defined:
* Render arrays (at any level of the hierarchy) will usually have one of the
* following properties defined:
* - #type: Specifies that the array contains data and options for a particular
* type of "render element" (examples: 'form', for an HTML form; 'textfield',
* 'submit', and other HTML form element types; 'table', for a table with
* rows, columns, and headers). See @ref elements below for more on render
* element types.
* type of "render element" (for example, 'form', for an HTML form;
* 'textfield', 'submit', for HTML form element types; 'table', for a table
* with rows, columns, and headers). See @ref elements below for more on
* render element types.
* - #theme: Specifies that the array contains data to be themed by a particular
* theme hook. Modules define theme hooks by implementing hook_theme(), which
* specifies the input "variables" used to provide data and options; if a
@ -277,30 +277,29 @@
* can customize the markup. Note that the value is passed through
* \Drupal\Component\Utility\Xss::filterAdmin(), which strips known XSS
* vectors while allowing a permissive list of HTML tags that are not XSS
* vectors. (I.e, <script> and <style> are not allowed.) See
* \Drupal\Component\Utility\Xss::$adminTags for the list of tags that will
* be allowed. If your markup needs any of the tags that are not in this
* whitelist, then you can implement a theme hook and template file and/or
* an asset library. Aternatively, you can use the render array key
* #allowed_tags to alter which tags are filtered.
* vectors. (For example, <script> and <style> are not allowed.) See
* \Drupal\Component\Utility\Xss::$adminTags for the list of allowed tags. If
* your markup needs any of the tags not in this whitelist, then you can
* implement a theme hook and/or an asset library. Alternatively, you can use
* the key #allowed_tags to alter which tags are filtered.
* - #plain_text: Specifies that the array provides text that needs to be
* escaped. This value takes precedence over #markup if present.
* - #allowed_tags: If #markup is supplied this can be used to change which tags
* are using to filter the markup. The value should be an array of tags that
* Xss::filter() would accept. If #plain_text is set this value is ignored.
* escaped. This value takes precedence over #markup.
* - #allowed_tags: If #markup is supplied, this can be used to change which
* tags are allowed in the markup. The value is an array of tags that
* Xss::filter() would accept. If #plain_text is set, this value is ignored.
*
* Usage example:
* @code
* $output['admin_filtered_string'] = array(
* $output['admin_filtered_string'] = [
* '#markup' => '<em>This is filtered using the admin tag list</em>',
* );
* $output['filtered_string'] = array(
* '#markup' => '<em>This is filtered</em>',
* '#allowed_tags' => ['strong'],
* );
* $output['escaped_string'] = array(
* ];
* $output['filtered_string'] = [
* '#markup' => '<video><source src="v.webm" type="video/webm"></video>',
* '#allowed_tags' => ['video', 'source'],
* ];
* $output['escaped_string'] = [
* '#plain_text' => '<em>This is escaped</em>',
* );
* ];
* @endcode
*
* @see core.libraries.yml

View file

@ -138,6 +138,12 @@ interface BigPipeInterface {
* The HTML response content to send.
* @param array $attachments
* The HTML response's attachments.
*
* @internal
* This method should only be invoked by
* \Drupal\big_pipe\Render\BigPipeResponse, which is itself an internal
* class. Furthermore, the signature of this method will change in
* https://www.drupal.org/node/2657684.
*/
public function sendContent($content, array $attachments);

View file

@ -13,7 +13,10 @@ use Drupal\Core\Render\HtmlResponse;
*
* @see \Drupal\big_pipe\Render\BigPipeInterface
*
* @todo Will become obsolete with https://www.drupal.org/node/2577631
* @internal
* This is a temporary solution until a generic response emitter interface is
* created in https://www.drupal.org/node/2577631. Only code internal to
* BigPipe should instantiate or type hint to this class.
*/
class BigPipeResponse extends HtmlResponse {

View file

@ -273,6 +273,20 @@
}
});
// Redirect on hash change when the original hash has an associated CKEditor.
function redirectTextareaFragmentToCKEditorInstance() {
var hash = location.hash.substr(1);
var element = document.getElementById(hash);
if (element) {
var editor = CKEDITOR.dom.element.get(element).getEditor();
if (editor) {
var id = editor.container.getAttribute('id');
location.replace('#' + id);
}
}
}
$(window).on('hashchange.ckeditor', redirectTextareaFragmentToCKEditorInstance);
// Set the CKEditor cache-busting string to the same value as Drupal.
CKEDITOR.timestamp = drupalSettings.ckeditor.timestamp;

View file

@ -0,0 +1,121 @@
<?php
namespace Drupal\Tests\ckeditor\FunctionalJavascript;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\editor\Entity\Editor;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\filter\Entity\FilterFormat;
use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
use Drupal\node\Entity\NodeType;
/**
* Tests the integration of CKEditor.
*
* @group ckeditor
*/
class CKEditorIntegrationTest extends JavascriptTestBase {
/**
* The account.
*
* @var \Drupal\user\UserInterface
*/
protected $account;
/**
* {@inheritdoc}
*/
public static $modules = ['node', 'ckeditor', 'filter'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create a text format and associate CKEditor.
$filtered_html_format = FilterFormat::create([
'format' => 'filtered_html',
'name' => 'Filtered HTML',
'weight' => 0,
]);
$filtered_html_format->save();
Editor::create([
'format' => 'filtered_html',
'editor' => 'ckeditor',
])->save();
// Create a node type for testing.
NodeType::create(['type' => 'page', 'name' => 'page'])->save();
$field_storage = FieldStorageConfig::loadByName('node', 'body');
// Create a body field instance for the 'page' node type.
FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => 'page',
'label' => 'Body',
'settings' => ['display_summary' => TRUE],
'required' => TRUE,
])->save();
// Assign widget settings for the 'default' form mode.
EntityFormDisplay::create([
'targetEntityType' => 'node',
'bundle' => 'page',
'mode' => 'default',
'status' => TRUE,
])->setComponent('body', ['type' => 'text_textarea_with_summary'])
->save();
$this->account = $this->drupalCreateUser([
'administer nodes',
'create page content',
'use text format filtered_html',
]);
$this->drupalLogin($this->account);
}
/**
* Tests if the fragment link to a textarea works with CKEditor enabled.
*/
public function testFragmentLink() {
$session = $this->getSession();
$web_assert = $this->assertSession();
$ckeditor_id = '#cke_edit-body-0-value';
$this->drupalGet('node/add/page');
$session->getPage();
// Add a bottom margin to the title field to be sure the body field is not
// visible. PhantomJS runs with a resolution of 1024x768px.
$session->executeScript("document.getElementById('edit-title-0-value').style.marginBottom = '800px';");
// Check that the CKEditor-enabled body field is currently not visible in
// the viewport.
$web_assert->assertNotVisibleInViewport('css', $ckeditor_id, 'topLeft', 'CKEditor-enabled body field is visible.');
$before_url = $session->getCurrentUrl();
// Trigger a hash change with as target the hidden textarea.
$session->executeScript("location.hash = '#edit-body-0-value';");
// Check that the CKEditor-enabled body field is visible in the viewport.
$web_assert->assertVisibleInViewport('css', $ckeditor_id, 'topLeft', 'CKEditor-enabled body field is not visible.');
// Use JavaScript to go back in the history instead of
// \Behat\Mink\Session::back() because that function doesn't work after a
// hash change.
$session->executeScript("history.back();");
$after_url = $session->getCurrentUrl();
// Check that going back in the history worked.
self::assertEquals($before_url, $after_url, 'History back works.');
}
}

View file

@ -36,3 +36,4 @@ migration_dependencies:
optional:
- d7_node_type
- d7_comment_type
- d7_taxonomy_vocabulary

View file

@ -96,13 +96,9 @@ function file_requirements($phase) {
$value = t('Not enabled');
$description = t('Your server is not capable of displaying file upload progress. File upload progress requires PHP be run with mod_php or PHP-FPM and not as FastCGI.');
}
elseif (!$implementation && extension_loaded('apcu')) {
$value = t('Not enabled');
$description = t('Your server is capable of displaying file upload progress through APC, but it is not enabled. Add <code>apc.rfc1867 = 1</code> to your php.ini configuration. Alternatively, it is recommended to use <a href="http://pecl.php.net/package/uploadprogress">PECL uploadprogress</a>, which supports more than one simultaneous upload.');
}
elseif (!$implementation) {
$value = t('Not enabled');
$description = t('Your server is capable of displaying file upload progress, but does not have the required libraries. It is recommended to install the <a href="http://pecl.php.net/package/uploadprogress">PECL uploadprogress library</a> (preferred) or to install <a href="http://php.net/apcu">APC</a>.');
$description = t('Your server is capable of displaying file upload progress, but does not have the required libraries. It is recommended to install the <a href="http://pecl.php.net/package/uploadprogress">PECL uploadprogress library</a>.');
}
elseif ($implementation == 'apc') {
$value = t('Enabled (<a href="http://php.net/manual/apcu.configuration.php#ini.apcu.rfc1867">APC RFC1867</a>)');

View file

@ -929,7 +929,7 @@ function file_progress_implementation() {
if (extension_loaded('uploadprogress')) {
$implementation = 'uploadprogress';
}
elseif (extension_loaded('apc') && ini_get('apc.rfc1867')) {
elseif (version_compare(PHP_VERSION, '7', '<') && extension_loaded('apc') && ini_get('apc.rfc1867')) {
$implementation = 'apc';
}
}

View file

@ -291,6 +291,21 @@ class FileWidget extends WidgetBase implements ContainerFactoryPluginInterface {
return $new_values;
}
/**
* {@inheritdoc}
*/
public function extractFormValues(FieldItemListInterface $items, array $form, FormStateInterface $form_state) {
parent::extractFormValues($items, $form, $form_state);
// Update reference to 'items' stored during upload to take into account
// changes to values like 'alt' etc.
// @see \Drupal\file\Plugin\Field\FieldWidget\FileWidget::submit()
$field_name = $this->fieldDefinition->getName();
$field_state = static::getWidgetState($form['#parents'], $field_name, $form_state);
$field_state['items'] = $items->getValue();
static::setWidgetState($form['#parents'], $field_name, $form_state, $field_state);
}
/**
* Form API callback. Retrieves the value for the file_generic field element.
*

View file

@ -0,0 +1,13 @@
id: d7_filter_settings
label: Drupal 7 filter settings
migration_tags:
- Drupal 7
source:
plugin: variable
variables:
- filter_fallback_format
process:
fallback_format: filter_fallback_format
destination:
plugin: config
config_name: filter.settings

View file

@ -0,0 +1,32 @@
<?php
namespace Drupal\Tests\filter\Kernel\Migrate\d7;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
/**
* Tests migration of Filter's settings to configuration.
*
* @group filter
*/
class MigrateFilterSettingsTest extends MigrateDrupal7TestBase {
public static $modules = ['filter'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installConfig(static::$modules);
$this->executeMigration('d7_filter_settings');
}
/**
* Tests migration of Filter variables to configuration.
*/
public function testFilterSettings() {
$this->assertSame('plain_text', $this->config('filter.settings')->get('fallback_format'));
}
}

View file

@ -0,0 +1,111 @@
<?php
namespace Drupal\Tests\inline_form_errors\FunctionalJavascript;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\editor\Entity\Editor;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\filter\Entity\FilterFormat;
use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
use Drupal\node\Entity\NodeType;
/**
* Tests the inline errors fragment link to a CKEditor-enabled textarea.
*
* @group ckeditor
*/
class FormErrorHandlerCKEditorTest extends JavascriptTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['node', 'ckeditor', 'inline_form_errors', 'filter'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create a text format and associate CKEditor.
$filtered_html_format = FilterFormat::create([
'format' => 'filtered_html',
'name' => 'Filtered HTML',
'weight' => 0,
]);
$filtered_html_format->save();
Editor::create([
'format' => 'filtered_html',
'editor' => 'ckeditor',
])->save();
// Create a node type for testing.
NodeType::create(['type' => 'page', 'name' => 'page'])->save();
$field_storage = FieldStorageConfig::loadByName('node', 'body');
// Create a body field instance for the 'page' node type.
FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => 'page',
'label' => 'Body',
'settings' => ['display_summary' => TRUE],
'required' => TRUE,
])->save();
// Assign widget settings for the 'default' form mode.
EntityFormDisplay::create([
'targetEntityType' => 'node',
'bundle' => 'page',
'mode' => 'default',
'status' => TRUE,
])->setComponent('body', ['type' => 'text_textarea_with_summary'])
->save();
$account = $this->drupalCreateUser([
'administer nodes',
'create page content',
'use text format filtered_html',
]);
$this->drupalLogin($account);
}
/**
* Tests if the fragment link to a textarea works with CKEditor enabled.
*/
public function testFragmentLink() {
$session = $this->getSession();
$web_assert = $this->assertSession();
$ckeditor_id = '#cke_edit-body-0-value';
$this->drupalGet('node/add/page');
$page = $this->getSession()->getPage();
// Only enter a title in the node add form and leave the body field empty.
$edit = ['edit-title-0-value' => 'Test inline form error with CKEditor'];
$this->submitForm($edit, 'Save and publish');
// Add a bottom margin to the title field to be sure the body field is not
// visible. PhantomJS runs with a resolution of 1024x768px.
$session->executeScript("document.getElementById('edit-title-0-value').style.marginBottom = '800px';");
// Check that the CKEditor-enabled body field is currently not visible in
// the viewport.
$web_assert->assertNotVisibleInViewport('css', $ckeditor_id, 'topLeft', 'CKEditor-enabled body field is not visible.');
// Check if we can find the error fragment link within the errors summary
// message.
$errors_link = $page->find('css', '.messages--error a[href=\#edit-body-0-value]');
$this->assertTrue($errors_link->isVisible(), 'Error fragment link is visible.');
$errors_link->click();
// Check that the CKEditor-enabled body field is visible in the viewport.
$web_assert->assertVisibleInViewport('css', $ckeditor_id, 'topLeft', 'CKEditor-enabled body field is visible.');
}
}

View file

@ -20,7 +20,7 @@ process:
plugin: skip_on_empty
method: row
'link/uri':
plugin: d7_internal_uri
plugin: link_uri
source:
- link_path
'link/options': options

View file

@ -63,6 +63,9 @@ class LinkUri extends ProcessPluginBase implements ContainerFactoryPluginInterfa
$path = ltrim($path, '/');
if (parse_url($path, PHP_URL_SCHEME) === NULL) {
if ($path == '<front>') {
$path = '';
}
$path = 'internal:/' . $path;
// Convert entity URIs to the entity scheme, if the path matches a route

View file

@ -2,42 +2,12 @@
namespace Drupal\menu_link_content\Plugin\migrate\process\d7;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
use Drupal\menu_link_content\Plugin\migrate\process\LinkUri;
/**
* Process a path into an 'internal:' URI.
* Processes an internal uri into an 'internal:' or 'entity:' URI.
*
* @MigrateProcessPlugin(
* id = "d7_internal_uri"
* )
* @deprecated in Drupal 8.2.0, will be removed before Drupal 9.0.0. Use
* \Drupal\menu_link_content\Plugin\migrate\process\LinkUri instead.
*/
class InternalUri extends ProcessPluginBase {
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
list($path) = $value;
$path = ltrim($path, '/');
if (parse_url($path, PHP_URL_SCHEME) == NULL) {
// If $path is the node page (i.e. node/[nid]) then return entity path.
if (preg_match('/^node\/\d+$/', $path)) {
// "entity: URI"s enable the menu link to appear in the Menu Settings
// section on the node edit page. Other entities (e.g. taxonomy terms,
// users) do not have the Menu Settings section.
return 'entity:' . $path;
}
elseif ($path == '<front>') {
return 'internal:/';
}
else {
return 'internal:/' . $path;
}
}
return $path;
}
}
class InternalUri extends LinkUri {}

View file

@ -5,6 +5,7 @@ namespace Drupal\Tests\menu_link_content\Kernel\Migrate\d7;
use Drupal\Core\Menu\MenuTreeParameters;
use Drupal\menu_link_content\Entity\MenuLinkContent;
use Drupal\menu_link_content\MenuLinkContentInterface;
use Drupal\node\Entity\Node;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
/**
@ -18,7 +19,7 @@ class MigrateMenuLinkTest extends MigrateDrupal7TestBase {
/**
* {@inheritdoc}
*/
public static $modules = array('link', 'menu_ui', 'menu_link_content');
public static $modules = array('link', 'menu_ui', 'menu_link_content', 'node');
/**
* {@inheritdoc}
@ -26,6 +27,13 @@ class MigrateMenuLinkTest extends MigrateDrupal7TestBase {
protected function setUp() {
parent::setUp();
$this->installEntitySchema('menu_link_content');
$this->installEntitySchema('node');
$node = Node::create([
'nid' => 3,
'title' => 'node link test',
'type' => 'article',
]);
$node->save();
$this->executeMigrations(['d7_menu', 'd7_menu_links']);
\Drupal::service('router.builder')->rebuild();
}

View file

@ -117,6 +117,10 @@ class LinkUriTest extends UnitTestCase {
$expected = 'internal:/test';
$tests['without_scheme'] = [$value, $expected];
$value = ['<front>'];
$expected = 'internal:/';
$tests['front'] = [$value, $expected];
$url = Url::fromRoute('route_name');
$tests['with_route'] = [$value, $expected, $url];

View file

@ -1,73 +0,0 @@
<?php
namespace Drupal\Tests\menu_link_content\Unit\Plugin\migrate\process\d7;
use Drupal\menu_link_content\Plugin\migrate\process\d7\InternalUri;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Row;
use Drupal\Tests\UnitTestCase;
/**
* Tests \Drupal\menu_link_content\Plugin\migrate\process\d7\InternalUri.
*
* @group menu_link_content
*
* @coversDefaultClass \Drupal\menu_link_content\Plugin\migrate\process\d7\InternalUri
*/
class InternalUriTest extends UnitTestCase {
/**
* The 'd7_internal_uri' process plugin being tested.
*
* @var \Drupal\menu_link_content\Plugin\migrate\process\d7\InternalUri
*/
protected $processPlugin;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->processPlugin = new InternalUri([], 'd7_internal_uri', []);
}
/**
* Tests InternalUri::transform().
*
* @param array $value
* The value to pass to InternalUri::transform().
* @param string $expected
* The expected return value of InternalUri::transform().
*
* @dataProvider providerTestTransform
*
* @covers ::transform
*/
public function testTransform(array $value, $expected) {
$migrate_executable = $this->prophesize(MigrateExecutableInterface::class);
$row = $this->prophesize(Row::class);
$actual = $this->processPlugin->transform($value, $migrate_executable->reveal(), $row->reveal(), 'link/uri');
$this->assertEquals($expected, $actual);
}
/**
* Provides test cases for InternalUriTest::testTransform().
*
* @return array
* An array of test cases, each which the following values:
* - The value array to pass to InternalUri::transform().
* - The expected path returned by InternalUri::transform().
*/
public function providerTestTransform() {
$tests = [];
$tests['with_scheme'] = [['http://example.com'], 'http://example.com'];
$tests['leading_slash'] = [['/test'], 'internal:/test'];
$tests['without_scheme'] = [['test'], 'internal:/test'];
$tests['front'] = [['<front>'], 'internal:/'];
$tests['node'] = [['node/27'], 'entity:node/27'];
return $tests;
}
}

View file

@ -108,8 +108,13 @@ class Download extends ProcessPluginBase implements ContainerFactoryPluginInterf
// Stream the request body directly to the final destination stream.
$this->configuration['guzzle_options']['sink'] = $destination_stream;
// Make the request. Guzzle throws an exception for anything other than 200.
$this->httpClient->get($source, $this->configuration['guzzle_options']);
try {
// Make the request. Guzzle throws an exception for anything but 200.
$this->httpClient->get($source, $this->configuration['guzzle_options']);
}
catch (\Exception $e) {
throw new MigrateException("{$e->getMessage()} ($source)");
}
return $final_destination;
}

View file

@ -112,20 +112,17 @@ class FileCopy extends ProcessPluginBase implements ContainerFactoryPluginInterf
return $destination;
}
$replace = $this->getOverwriteMode();
// We attempt the copy/move first to avoid calling file_prepare_directory()
// any more than absolutely necessary.
$final_destination = $this->writeFile($source, $destination, $replace);
if ($final_destination) {
return $final_destination;
}
// If writeFile didn't work, make sure there's a writable directory in
// place.
// Check if a writable directory exists, and if not try to create it.
$dir = $this->getDirectory($destination);
if (!file_prepare_directory($dir, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
throw new MigrateException("Could not create or write to directory '$dir'");
// If the directory exists and is writable, avoid file_prepare_directory()
// call and write the file to destination.
if (!is_dir($dir) || !is_writable($dir)) {
if (!file_prepare_directory($dir, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
throw new MigrateException("Could not create or write to directory '$dir'");
}
}
$final_destination = $this->writeFile($source, $destination, $replace);
$final_destination = $this->writeFile($source, $destination, $this->getOverwriteMode());
if ($final_destination) {
return $final_destination;
}

View file

@ -22,15 +22,17 @@ class Iterator extends ProcessPluginBase {
* Runs a process pipeline on each destination property per list item.
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
$return = array();
foreach ($value as $key => $new_value) {
$new_row = new Row($new_value, array());
$migrate_executable->processRow($new_row, $this->configuration['process']);
$destination = $new_row->getDestination();
if (array_key_exists('key', $this->configuration)) {
$key = $this->transformKey($key, $migrate_executable, $new_row);
$return = [];
if (!is_null($value)) {
foreach ($value as $key => $new_value) {
$new_row = new Row($new_value, []);
$migrate_executable->processRow($new_row, $this->configuration['process']);
$destination = $new_row->getDestination();
if (array_key_exists('key', $this->configuration)) {
$key = $this->transformKey($key, $migrate_executable, $new_row);
}
$return[$key] = $destination;
}
$return[$key] = $destination;
}
return $return;
}

View file

@ -310,13 +310,13 @@ abstract class SourcePluginBase extends PluginBase implements MigrateSourceInter
while (!isset($this->currentRow) && $this->getIterator()->valid()) {
$row_data = $this->getIterator()->current() + $this->configuration;
$this->getIterator()->next();
$this->fetchNextRow();
$row = new Row($row_data, $this->migration->getSourcePlugin()->getIds(), $this->migration->getDestinationIds());
// Populate the source key for this row.
$this->currentSourceIds = $row->getSourceIdValues();
// Pick up the existing map row, if any, unless getNextRow() did it.
// Pick up the existing map row, if any, unless fetchNextRow() did it.
if (!$this->mapRowAdded && ($id_map = $this->idMap->getRowBySource($this->currentSourceIds))) {
$row->setIdMap($id_map);
}
@ -348,7 +348,14 @@ abstract class SourcePluginBase extends PluginBase implements MigrateSourceInter
}
/**
* Checks if the incoming data is newer than what we've previously imported.
* Position the iterator to the following row.
*/
protected function fetchNextRow() {
$this->getIterator()->next();
}
/**
* Check if the incoming data is newer than what we've previously imported.
*
* @param \Drupal\migrate\Row $row
* The row we're importing.

View file

@ -5,6 +5,7 @@ namespace Drupal\migrate\Plugin\migrate\source;
use Drupal\Core\Database\Database;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\State\StateInterface;
use Drupal\migrate\MigrateException;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Plugin\migrate\id_map\Sql;
use Drupal\migrate\Plugin\MigrateIdMapInterface;
@ -42,6 +43,22 @@ abstract class SqlBase extends SourcePluginBase implements ContainerFactoryPlugi
*/
protected $state;
/**
* The count of the number of batches run.
*
* @var int
*/
protected $batch = 0;
/**
* Number of records to fetch from the database during each batch.
*
* A value of zero indicates no batching is to be done.
*
* @var int
*/
protected $batchSize = 0;
/**
* {@inheritdoc}
*/
@ -160,70 +177,110 @@ abstract class SqlBase extends SourcePluginBase implements ContainerFactoryPlugi
* we will take advantage of the PDO-based API to optimize the query up-front.
*/
protected function initializeIterator() {
$this->prepareQuery();
// Initialize the batch size.
if ($this->batchSize == 0 && isset($this->configuration['batch_size'])) {
// Valid batch sizes are integers >= 0.
if (is_int($this->configuration['batch_size']) && ($this->configuration['batch_size']) >= 0) {
$this->batchSize = $this->configuration['batch_size'];
}
else {
throw new MigrateException("batch_size must be greater than or equal to zero");
}
}
// Get the key values, for potential use in joining to the map table.
$keys = array();
// If a batch has run the query is already setup.
if ($this->batch == 0) {
$this->prepareQuery();
// The rules for determining what conditions to add to the query are as
// follows (applying first applicable rule):
// 1. If the map is joinable, join it. We will want to accept all rows
// which are either not in the map, or marked in the map as NEEDS_UPDATE.
// Note that if high water fields are in play, we want to accept all rows
// above the high water mark in addition to those selected by the map
// conditions, so we need to OR them together (but AND with any existing
// conditions in the query). So, ultimately the SQL condition will look
// like (original conditions) AND (map IS NULL OR map needs update
// OR above high water).
$conditions = $this->query->orConditionGroup();
$condition_added = FALSE;
if (empty($this->configuration['ignore_map']) && $this->mapJoinable()) {
// Build the join to the map table. Because the source key could have
// multiple fields, we need to build things up.
$count = 1;
$map_join = '';
$delimiter = '';
foreach ($this->getIds() as $field_name => $field_schema) {
if (isset($field_schema['alias'])) {
$field_name = $field_schema['alias'] . '.' . $this->query->escapeField($field_name);
// Get the key values, for potential use in joining to the map table.
$keys = array();
// The rules for determining what conditions to add to the query are as
// follows (applying first applicable rule):
// 1. If the map is joinable, join it. We will want to accept all rows
// which are either not in the map, or marked in the map as NEEDS_UPDATE.
// Note that if high water fields are in play, we want to accept all rows
// above the high water mark in addition to those selected by the map
// conditions, so we need to OR them together (but AND with any existing
// conditions in the query). So, ultimately the SQL condition will look
// like (original conditions) AND (map IS NULL OR map needs update
// OR above high water).
$conditions = $this->query->orConditionGroup();
$condition_added = FALSE;
if (empty($this->configuration['ignore_map']) && $this->mapJoinable()) {
// Build the join to the map table. Because the source key could have
// multiple fields, we need to build things up.
$count = 1;
$map_join = '';
$delimiter = '';
foreach ($this->getIds() as $field_name => $field_schema) {
if (isset($field_schema['alias'])) {
$field_name = $field_schema['alias'] . '.' . $this->query->escapeField($field_name);
}
$map_join .= "$delimiter$field_name = map.sourceid" . $count++;
$delimiter = ' AND ';
}
$map_join .= "$delimiter$field_name = map.sourceid" . $count++;
$delimiter = ' AND ';
}
$alias = $this->query->leftJoin($this->migration->getIdMap()->getQualifiedMapTableName(), 'map', $map_join);
$conditions->isNull($alias . '.sourceid1');
$conditions->condition($alias . '.source_row_status', MigrateIdMapInterface::STATUS_NEEDS_UPDATE);
$condition_added = TRUE;
$alias = $this->query->leftJoin($this->migration->getIdMap()
->getQualifiedMapTableName(), 'map', $map_join);
$conditions->isNull($alias . '.sourceid1');
$conditions->condition($alias . '.source_row_status', MigrateIdMapInterface::STATUS_NEEDS_UPDATE);
$condition_added = TRUE;
// And as long as we have the map table, add its data to the row.
$n = count($this->getIds());
for ($count = 1; $count <= $n; $count++) {
$map_key = 'sourceid' . $count;
$this->query->addField($alias, $map_key, "migrate_map_$map_key");
}
if ($n = count($this->migration->getDestinationIds())) {
// And as long as we have the map table, add its data to the row.
$n = count($this->getIds());
for ($count = 1; $count <= $n; $count++) {
$map_key = 'destid' . $count++;
$map_key = 'sourceid' . $count;
$this->query->addField($alias, $map_key, "migrate_map_$map_key");
}
if ($n = count($this->migration->getDestinationIds())) {
for ($count = 1; $count <= $n; $count++) {
$map_key = 'destid' . $count++;
$this->query->addField($alias, $map_key, "migrate_map_$map_key");
}
}
$this->query->addField($alias, 'source_row_status', 'migrate_map_source_row_status');
}
// 2. If we are using high water marks, also include rows above the mark.
// But, include all rows if the high water mark is not set.
if ($this->getHighWaterProperty() && ($high_water = $this->getHighWater()) !== '') {
$high_water_field = $this->getHighWaterField();
$conditions->condition($high_water_field, $high_water, '>');
$this->query->orderBy($high_water_field);
}
if ($condition_added) {
$this->query->condition($conditions);
}
$this->query->addField($alias, 'source_row_status', 'migrate_map_source_row_status');
}
// 2. If we are using high water marks, also include rows above the mark.
// But, include all rows if the high water mark is not set.
if ($this->getHighWaterProperty() && ($high_water = $this->getHighWater()) !== '') {
$high_water_field = $this->getHighWaterField();
$conditions->condition($high_water_field, $high_water, '>');
$this->query->orderBy($high_water_field);
}
if ($condition_added) {
$this->query->condition($conditions);
}
// Download data in batches for performance.
if (($this->batchSize > 0)) {
$this->query->range($this->batch * $this->batchSize, $this->batchSize);
}
return new \IteratorIterator($this->query->execute());
}
/**
* Position the iterator to the following row.
*/
protected function fetchNextRow() {
$this->getIterator()->next();
// We might be out of data entirely, or just out of data in the current
// batch. Attempt to fetch the next batch and see.
if ($this->batchSize > 0 && !$this->getIterator()->valid()) {
$this->fetchNextBatch();
}
}
/**
* Prepares query for the next set of data from the source database.
*/
protected function fetchNextBatch() {
$this->batch++;
unset($this->iterator);
$this->getIterator()->rewind();
}
/**
* @return \Drupal\Core\Database\Query\SelectInterface
*/
@ -249,6 +306,14 @@ abstract class SqlBase extends SourcePluginBase implements ContainerFactoryPlugi
if (!$this->getIds()) {
return FALSE;
}
// With batching, we want a later batch to return the same rows that would
// have been returned at the same point within a monolithic query. If we
// join to the map table, the first batch is writing to the map table and
// thus affecting the results of subsequent batches. To be safe, we avoid
// joining to the map table when batching.
if ($this->batchSize > 0) {
return FALSE;
}
$id_map = $this->migration->getIdMap();
if (!$id_map instanceof Sql) {
return FALSE;

View file

@ -0,0 +1,7 @@
type: module
name: Migrate query batch Source test
description: 'Provides a database table and records for SQL import with batch testing.'
package: Testing
core: 8.x
dependencies:
- migrate

View file

@ -0,0 +1,45 @@
<?php
namespace Drupal\migrate_query_batch_test\Plugin\migrate\source;
use Drupal\migrate\Plugin\migrate\source\SqlBase;
/**
* Source plugin for migration high water tests.
*
* @MigrateSource(
* id = "query_batch_test"
* )
*/
class QueryBatchTest extends SqlBase {
/**
* {@inheritdoc}
*/
public function query() {
return ($this->select('query_batch_test', 'q')->fields('q'));
}
/**
* {@inheritdoc}
*/
public function fields() {
$fields = [
'id' => $this->t('Id'),
'data' => $this->t('data'),
];
return $fields;
}
/**
* {@inheritdoc}
*/
public function getIds() {
return [
'id' => [
'type' => 'integer',
],
];
}
}

View file

@ -0,0 +1,81 @@
<?php
namespace Drupal\Tests\migrate\Functional\process;
use Drupal\migrate\MigrateExecutable;
use Drupal\migrate\MigrateMessage;
use Drupal\migrate\Plugin\MigrateIdMapInterface;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\Tests\BrowserTestBase;
/**
* Tests the 'download' process plugin.
*
* @group migrate
*/
class DownloadFunctionalTest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['migrate', 'file'];
/**
* Tests that an exception is thrown bu migration continues with the next row.
*/
public function testExceptionThrow() {
$invalid_url = "{$this->baseUrl}/not-existent-404";
$valid_url = "{$this->baseUrl}/core/misc/favicon.ico";
$definition = [
'source' => [
'plugin' => 'embedded_data',
'data_rows' => [
['url' => $invalid_url, 'uri' => 'public://first.txt'],
['url' => $valid_url, 'uri' => 'public://second.ico'],
],
'ids' => [
'url' => ['type' => 'string'],
],
],
'process' => [
'uri' => [
'plugin' => 'download',
'source' => ['url', 'uri'],
]
],
'destination' => [
'plugin' => 'entity:file',
],
];
$migration = \Drupal::service('plugin.manager.migration')->createStubMigration($definition);
$executable = new MigrateExecutable($migration, new MigrateMessage());
$result = $executable->import();
// Check that the migration has completed.
$this->assertEquals($result, MigrationInterface::RESULT_COMPLETED);
/** @var \Drupal\migrate\Plugin\MigrateIdMapInterface $id_map_plugin */
$id_map_plugin = $migration->getIdMap();
// Check that the first row was marked as failed in the id map table.
$map_row = $id_map_plugin->getRowBySource(['url' => $invalid_url]);
$this->assertEquals(MigrateIdMapInterface::STATUS_FAILED, $map_row['source_row_status']);
$this->assertNull($map_row['destid1']);
// Check that a message with the thrown exception has been logged.
$messages = $id_map_plugin->getMessageIterator(['url' => $invalid_url])->fetchAll();
$this->assertCount(1, $messages);
$message = reset($messages);
$this->assertEquals("Cannot read from non-readable stream ($invalid_url)", $message->message);
$this->assertEquals(MigrationInterface::MESSAGE_ERROR, $message->level);
// Check that the second row was migrated successfully.
$map_row = $id_map_plugin->getRowBySource(['url' => $valid_url]);
$this->assertEquals(MigrateIdMapInterface::STATUS_IMPORTED, $map_row['source_row_status']);
$this->assertEquals(1, $map_row['destid1']);
}
}

View file

@ -0,0 +1,261 @@
<?php
namespace Drupal\Tests\migrate\Kernel;
use Drupal\KernelTests\KernelTestBase;
use Drupal\migrate\MigrateException;
use Drupal\migrate\Plugin\MigrateIdMapInterface;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\Core\Database\Driver\sqlite\Connection;
/**
* Tests query batching.
*
* @covers \Drupal\migrate_query_batch_test\Plugin\migrate\source\QueryBatchTest
* @group migrate
*/
class QueryBatchTest extends KernelTestBase {
/**
* The mocked migration.
*
* @var MigrationInterface|\Prophecy\Prophecy\ObjectProphecy
*/
protected $migration;
/**
* {@inheritdoc}
*/
public static $modules = [
'migrate',
'migrate_query_batch_test',
];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create a mock migration. This will be injected into the source plugin
// under test.
$this->migration = $this->prophesize(MigrationInterface::class);
$this->migration->id()->willReturn(
$this->randomMachineName(16)
);
// Prophesize a useless ID map plugin and an empty set of destination IDs.
// Calling code can override these prophecies later and set up different
// behaviors.
$this->migration->getIdMap()->willReturn(
$this->prophesize(MigrateIdMapInterface::class)->reveal()
);
$this->migration->getDestinationIds()->willReturn([]);
}
/**
* Tests a negative batch size throws an exception.
*/
public function testBatchSizeNegative() {
$this->setExpectedException(MigrateException::class, 'batch_size must be greater than or equal to zero');
$plugin = $this->getPlugin(['batch_size' => -1]);
$plugin->next();
}
/**
* Tests a non integer batch size throws an exception.
*/
public function testBatchSizeNonInteger() {
$this->setExpectedException(MigrateException::class, 'batch_size must be greater than or equal to zero');
$plugin = $this->getPlugin(['batch_size' => '1']);
$plugin->next();
}
/**
* {@inheritdoc}
*/
public function queryDataProvider() {
// Define the parameters for building the data array. The first element is
// the number of source data rows, the second is the batch size to set on
// the plugin configuration.
$test_parameters = [
// Test when batch size is 0.
[200, 0],
// Test when rows mod batch size is 0.
[200, 20],
// Test when rows mod batch size is > 0.
[200, 30],
// Test when batch size = row count.
[200, 200],
// Test when batch size > row count.
[200, 300],
];
// Build the data provider array. The provider array consists of the source
// data rows, the expected result data, the expected count, the plugin
// configuration, the expected batch size and the expected batch count.
$table = 'query_batch_test';
$tests = [];
$data_set = 0;
foreach ($test_parameters as $data) {
list($num_rows, $batch_size) = $data;
for ($i = 0; $i < $num_rows; $i++) {
$tests[$data_set]['source_data'][$table][] = [
'id' => $i,
'data' => $this->randomString(),
];
}
$tests[$data_set]['expected_data'] = $tests[$data_set]['source_data'][$table];
$tests[$data_set][2] = $num_rows;
// Plugin configuration array.
$tests[$data_set][3] = ['batch_size' => $batch_size];
// Expected batch size.
$tests[$data_set][4] = $batch_size;
// Expected batch count is 0 unless a batch size is set.
$expected_batch_count = 0;
if ($batch_size > 0) {
$expected_batch_count = (int) ($num_rows / $batch_size);
if ($num_rows % $batch_size) {
// If there is a remainder an extra batch is needed to get the
// remaining rows.
$expected_batch_count++;
}
}
$tests[$data_set][5] = $expected_batch_count;
$data_set++;
}
return $tests;
}
/**
* Tests query batch size.
*
* @param array $source_data
* The source data, keyed by table name. Each table is an array containing
* the rows in that table.
* @param array $expected_data
* The result rows the plugin is expected to return.
* @param int $num_rows
* How many rows the source plugin is expected to return.
* @param array $configuration
* Configuration for the source plugin specifying the batch size.
* @param int $expected_batch_size
* The expected batch size, will be set to zero for invalid batch sizes.
* @param int $expected_batch_count
* The total number of batches.
*
* @dataProvider queryDataProvider
*/
public function testQueryBatch($source_data, $expected_data, $num_rows, $configuration, $expected_batch_size, $expected_batch_count) {
$plugin = $this->getPlugin($configuration);
// Since we don't yet inject the database connection, we need to use a
// reflection hack to set it in the plugin instance.
$reflector = new \ReflectionObject($plugin);
$property = $reflector->getProperty('database');
$property->setAccessible(TRUE);
$connection = $this->getDatabase($source_data);
$property->setValue($plugin, $connection);
// Test the results.
$i = 0;
/** @var \Drupal\migrate\Row $row */
foreach ($plugin as $row) {
$expected = $expected_data[$i++];
$actual = $row->getSource();
foreach ($expected as $key => $value) {
$this->assertArrayHasKey($key, $actual);
$this->assertSame((string) $value, (string) $actual[$key]);
}
}
// Test that all rows were retrieved.
self::assertSame($num_rows, $i);
// Test the batch size.
if (is_null($expected_batch_size)) {
$expected_batch_size = $configuration['batch_size'];
}
$property = $reflector->getProperty('batchSize');
$property->setAccessible(TRUE);
self::assertSame($expected_batch_size, $property->getValue($plugin));
// Test the batch count.
if (is_null($expected_batch_count)) {
$expected_batch_count = intdiv($num_rows, $expected_batch_size);
if ($num_rows % $configuration['batch_size']) {
$expected_batch_count++;
}
}
$property = $reflector->getProperty('batch');
$property->setAccessible(TRUE);
self::assertSame($expected_batch_count, $property->getValue($plugin));
}
/**
* Instantiates the source plugin under test.
*
* @param array $configuration
* The source plugin configuration.
*
* @return \Drupal\migrate\Plugin\MigrateSourceInterface|object
* The fully configured source plugin.
*/
protected function getPlugin($configuration) {
/** @var \Drupal\migrate\Plugin\MigratePluginManager $plugin_manager */
$plugin_manager = $this->container->get('plugin.manager.migrate.source');
$plugin = $plugin_manager->createInstance('query_batch_test', $configuration, $this->migration->reveal());
$this->migration
->getSourcePlugin()
->willReturn($plugin);
return $plugin;
}
/**
* Builds an in-memory SQLite database from a set of source data.
*
* @param array $source_data
* The source data, keyed by table name. Each table is an array containing
* the rows in that table.
*
* @return \Drupal\Core\Database\Driver\sqlite\Connection
* The SQLite database connection.
*/
protected function getDatabase(array $source_data) {
// Create an in-memory SQLite database. Plugins can interact with it like
// any other database, and it will cease to exist when the connection is
// closed.
$connection_options = ['database' => ':memory:'];
$pdo = Connection::open($connection_options);
$connection = new Connection($pdo, $connection_options);
// Create the tables and fill them with data.
foreach ($source_data as $table => $rows) {
// Use the biggest row to build the table schema.
$counts = array_map('count', $rows);
asort($counts);
end($counts);
$pilot = $rows[key($counts)];
$connection->schema()
->createTable($table, [
// SQLite uses loose affinity typing, so it's OK for every field to
// be a text field.
'fields' => array_map(function () {
return ['type' => 'text'];
}, $pilot),
]);
$fields = array_keys($pilot);
$insert = $connection->insert($table)->fields($fields);
array_walk($rows, [$insert, 'values']);
$insert->execute();
}
return $connection;
}
}

View file

@ -4,6 +4,7 @@ namespace Drupal\Tests\migrate\Kernel\process;
use Drupal\Core\StreamWrapper\StreamWrapperInterface;
use Drupal\KernelTests\Core\File\FileTestBase;
use Drupal\migrate\MigrateException;
use Drupal\migrate\Plugin\migrate\process\FileCopy;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Plugin\MigrateProcessInterface;
@ -12,6 +13,8 @@ use Drupal\migrate\Row;
/**
* Tests the file_copy process plugin.
*
* @coversDefaultClass \Drupal\migrate\Plugin\migrate\process\FileCopy
*
* @group migrate
*/
class FileCopyTest extends FileTestBase {
@ -120,6 +123,32 @@ class FileCopyTest extends FileTestBase {
$this->doTransform($source, 'public://wontmatter.jpg');
}
/**
* Tests that non-writable destination throw an exception.
*
* @covers ::transform
*/
public function testNonWritableDestination() {
$source = $this->createUri('file.txt', NULL, 'temporary');
// Create the parent location.
$this->createDirectory('public://dir');
// Copy the file under public://dir/subdir1/.
$this->doTransform($source, 'public://dir/subdir1/file.txt');
// Check that 'subdir1' was created and the file was successfully migrated.
$this->assertFileExists('public://dir/subdir1/file.txt');
// Remove all permissions from public://dir to trigger a failure when
// trying to create a subdirectory 'subdir2' inside public://dir.
$this->fileSystem->chmod('public://dir', 0);
// Check that the proper exception is raised.
$this->setExpectedException(MigrateException::class, "Could not create or write to directory 'public://dir/subdir2'");
$this->doTransform($source, 'public://dir/subdir2/file.txt');
}
/**
* Test the 'rename' overwrite mode.
*/

View file

@ -3828,6 +3828,15 @@ $connection->insert('field_config_instance')
'data' => 'a:7:{s:5:"label";s:21:"Term Entity Reference";s:6:"widget";a:5:{s:6:"weight";s:2:"18";s:4:"type";s:33:"entityreference_autocomplete_tags";s:6:"module";s:15:"entityreference";s:6:"active";i:1;s:8:"settings";a:3:{s:14:"match_operator";s:8:"CONTAINS";s:4:"size";s:2:"60";s:4:"path";s:0:"";}}s:8:"settings";a:2:{s:9:"behaviors";a:1:{s:14:"taxonomy-index";a:1:{s:6:"status";b: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:21:"entityreference_label";s:8:"settings";a:1:{s:4:"link";b:0;}s:6:"module";s:15:"entityreference";s:6:"weight";i:17;}}s:8:"required";i:0;s:11:"description";s:0:"";s:13:"default_value";N;}',
'deleted' => '0',
))
->values(array(
'id' => '41',
'field_id' => '20',
'field_name' => 'field_term_reference',
'entity_type' => 'taxonomy_term',
'bundle' => 'test_vocabulary',
'data' => 'a:7:{s:5:"label";s:14:"Term Reference";s:6:"widget";a:5:{s:6:"weight";s:2:"14";s:4:"type";s:21:"taxonomy_autocomplete";s:6:"module";s:8:"taxonomy";s:6:"active";i:0;s:8:"settings";a:2:{s:4:"size";i:60;s:17:"autocomplete_path";s:21:"taxonomy/autocomplete";}}s:8:"settings";a:1:{s:18:"user_register_form";b:0;}s:7:"display";a:1:{s:7:"default";a:4:{s:5:"label";s:5:"above";s:4:"type";s:6:"hidden";s:6:"weight";s:2:"13";s:8:"settings";a:0:{}}}s:8:"required";i:0;s:11:"description";s:0:"";s:13:"default_value";N;}',
'deleted' => '0',
))
->execute();
$connection->schema()->createTable('field_data_body', array(
@ -4850,6 +4859,16 @@ $connection->insert('field_data_field_integer')
'delta' => '0',
'field_integer_value' => '99',
))
->values(array(
'entity_type' => 'taxonomy_term',
'bundle' => 'test_vocabulary',
'deleted' => '0',
'entity_id' => '4',
'revision_id' => '4',
'language' => 'und',
'delta' => '0',
'field_integer_value' => '6',
))
->execute();
$connection->schema()->createTable('field_data_field_integer_list', array(
@ -5649,6 +5668,16 @@ $connection->insert('field_data_field_term_reference')
'delta' => '0',
'field_term_reference_tid' => '4',
))
->values(array(
'entity_type' => 'taxonomy_term',
'bundle' => 'test_vocabulary',
'deleted' => '0',
'entity_id' => '2',
'revision_id' => '2',
'language' => 'und',
'delta' => '0',
'field_term_reference_tid' => '3',
))
->execute();
$connection->schema()->createTable('field_data_field_text', array(
@ -7023,6 +7052,16 @@ $connection->insert('field_revision_field_integer')
'delta' => '0',
'field_integer_value' => '99',
))
->values(array(
'entity_type' => 'taxonomy_term',
'bundle' => 'test_vocabulary',
'deleted' => '0',
'entity_id' => '4',
'revision_id' => '4',
'language' => 'und',
'delta' => '0',
'field_integer_value' => '6',
))
->execute();
$connection->schema()->createTable('field_revision_field_integer_list', array(
@ -7830,6 +7869,16 @@ $connection->insert('field_revision_field_term_reference')
'delta' => '0',
'field_term_reference_tid' => '4',
))
->values(array(
'entity_type' => 'taxonomy_term',
'bundle' => 'test_vocabulary',
'deleted' => '0',
'entity_id' => '2',
'revision_id' => '2',
'language' => 'und',
'delta' => '0',
'field_term_reference_tid' => '3',
))
->execute();
$connection->schema()->createTable('field_revision_field_text', array(

View file

@ -250,6 +250,10 @@ class MigrateUpgradeForm extends ConfirmFormBase {
'source_module' => 'filter',
'destination_module' => 'filter',
],
'd7_filter_settings' => [
'source_module' => 'filter',
'destination_module' => 'filter',
],
'd6_forum_settings' => [
'source_module' => 'forum',
'destination_module' => 'forum',

View file

@ -43,8 +43,8 @@ class MigrateUpgrade7Test extends MigrateUpgradeTestBase {
'configurable_language' => 4,
'contact_form' => 3,
'editor' => 2,
'field_config' => 48,
'field_storage_config' => 36,
'field_config' => 49,
'field_storage_config' => 37,
'file' => 1,
'filter_format' => 7,
'image_style' => 6,

View file

@ -72,7 +72,7 @@ class NodePreviewForm extends FormBase {
public function buildForm(array $form, FormStateInterface $form_state, EntityInterface $node = NULL) {
$view_mode = $node->preview_view_mode;
$query_options = $node->isNew() ? array('query' => array('uuid' => $node->uuid())) : array();
$query_options = array('query' => array('uuid' => $node->uuid()));
$form['backlink'] = array(
'#type' => 'link',
'#title' => $this->t('Back to content editing'),

View file

@ -67,31 +67,28 @@ class NodeForm extends ContentEntityForm {
public function form(array $form, FormStateInterface $form_state) {
// Try to restore from temp store, this must be done before calling
// parent::form().
$uuid = $this->entity->uuid();
$store = $this->tempStoreFactory->get('node_preview');
// If the user is creating a new node, the UUID is passed in the request.
if ($request_uuid = \Drupal::request()->query->get('uuid')) {
$uuid = $request_uuid;
}
if ($preview = $store->get($uuid)) {
// Attempt to load from preview when the uuid is present unless we are
// rebuilding the form.
$request_uuid = \Drupal::request()->query->get('uuid');
if (!$form_state->isRebuilding() && $request_uuid && $preview = $store->get($request_uuid)) {
/** @var $preview \Drupal\Core\Form\FormStateInterface */
foreach ($preview->getValues() as $name => $value) {
$form_state->setValue($name, $value);
}
$form_state->setStorage($preview->getStorage());
$form_state->setUserInput($preview->getUserInput());
// Rebuild the form.
$form_state->setRebuild();
// The combination of having user input and rebuilding the form means
// that it will attempt to cache the form state which will fail if it is
// a GET request.
$form_state->setRequestMethod('POST');
$this->entity = $preview->getFormObject()->getEntity();
$this->entity->in_preview = NULL;
// Remove the stale temp store entry for existing nodes.
if (!$this->entity->isNew()) {
$store->delete($uuid);
}
$this->hasBeenPreviewed = TRUE;
}

View file

@ -28,7 +28,7 @@ class PagePreviewTest extends NodeTestBase {
*
* @var array
*/
public static $modules = array('node', 'taxonomy', 'comment', 'image', 'file');
public static $modules = array('node', 'taxonomy', 'comment', 'image', 'file', 'text', 'node_test', 'menu_ui');
/**
* The name of the created field.
@ -41,7 +41,7 @@ class PagePreviewTest extends NodeTestBase {
parent::setUp();
$this->addDefaultCommentField('node', 'page');
$web_user = $this->drupalCreateUser(array('edit own page content', 'create page content'));
$web_user = $this->drupalCreateUser(array('edit own page content', 'create page content', 'administer menu'));
$this->drupalLogin($web_user);
// Add a vocabulary so we can test different view modes.
@ -124,6 +124,34 @@ class PagePreviewTest extends NodeTestBase {
entity_get_display('node', 'page', 'default')
->setComponent('field_image')
->save();
// Create a multi-value text field.
$field_storage = FieldStorageConfig::create([
'field_name' => 'field_test_multi',
'entity_type' => 'node',
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
'type' => 'text',
'settings' => [
'max_length' => 50,
]
]);
$field_storage->save();
FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => 'page',
])->save();
entity_get_form_display('node', 'page', 'default')
->setComponent('field_test_multi', array(
'type' => 'text_textfield',
))
->save();
entity_get_display('node', 'page', 'default')
->setComponent('field_test_multi', array(
'type' => 'string',
))
->save();
}
/**
@ -176,8 +204,11 @@ class PagePreviewTest extends NodeTestBase {
$this->clickLink(t('Back to content editing'));
$this->assertFieldByName($title_key, $edit[$title_key], 'Title field displayed.');
$this->assertFieldByName($body_key, $edit[$body_key], 'Body field displayed.');
$this->assertFieldByName($term_key, $edit[$term_key] . ' (' . $this->term->id() . ')', 'Term field displayed.');
$this->assertFieldByName($term_key, $edit[$term_key], 'Term field displayed.');
$this->assertFieldByName('field_image[0][alt]', 'Picture of llamas');
$this->drupalPostAjaxForm(NULL, array(), array('field_test_multi_add_more' => t('Add another item')), NULL, array(), array(), 'node-page-form');
$this->assertFieldByName('field_test_multi[0][value]');
$this->assertFieldByName('field_test_multi[1][value]');
// Return to page preview to check everything is as expected.
$this->drupalPostForm(NULL, array(), t('Preview'));
@ -191,7 +222,7 @@ class PagePreviewTest extends NodeTestBase {
$this->drupalGet('node/add/page', array('query' => array('uuid' => $uuid)));
$this->assertFieldByName($title_key, $edit[$title_key], 'Title field displayed.');
$this->assertFieldByName($body_key, $edit[$body_key], 'Body field displayed.');
$this->assertFieldByName($term_key, $edit[$term_key] . ' (' . $this->term->id() . ')', 'Term field displayed.');
$this->assertFieldByName($term_key, $edit[$term_key], 'Term field displayed.');
// Save the node - this is a new POST, so we need to upload the image.
$this->drupalPostForm('node/add/page', $edit, t('Upload'));
@ -260,6 +291,80 @@ class PagePreviewTest extends NodeTestBase {
$this->drupalPostForm('node/add/page', array($title_key => 'Preview'), t('Preview'));
$this->clickLink(t('Back to content editing'));
$this->assertRaw('edit-submit');
// Assert multiple items can be added and are not lost when previewing.
$test_image_1 = current($this->drupalGetTestFiles('image', 39325));
$edit_image_1['files[field_image_0][]'] = drupal_realpath($test_image_1->uri);
$test_image_2 = current($this->drupalGetTestFiles('image', 39325));
$edit_image_2['files[field_image_1][]'] = drupal_realpath($test_image_2->uri);
$edit['field_image[0][alt]'] = 'Alt 1';
$this->drupalPostForm('node/add/page', $edit_image_1, t('Upload'));
$this->drupalPostForm(NULL, $edit, t('Preview'));
$this->clickLink(t('Back to content editing'));
$this->assertFieldByName('files[field_image_1][]');
$this->drupalPostForm(NULL, $edit_image_2, t('Upload'));
$this->assertNoFieldByName('files[field_image_1][]');
$title = 'node_test_title';
$example_text_1 = 'example_text_preview_1';
$example_text_2 = 'example_text_preview_2';
$example_text_3 = 'example_text_preview_3';
$this->drupalGet('node/add/page');
$edit = [
'title[0][value]' => $title,
'field_test_multi[0][value]' => $example_text_1,
];
$this->assertRaw('Storage is not set');
$this->drupalPostForm(NULL, $edit, t('Preview'));
$this->clickLink(t('Back to content editing'));
$this->assertRaw('Storage is set');
$this->assertFieldByName('field_test_multi[0][value]');
$this->drupalPostForm(NULL, [], t('Save'));
$this->assertText('Basic page ' . $title . ' has been created.');
$node = $this->drupalGetNodeByTitle($title);
$this->drupalGet('node/' . $node->id() . '/edit');
$this->drupalPostAjaxForm(NULL, [], array('field_test_multi_add_more' => t('Add another item')));
$this->drupalPostAjaxForm(NULL, [], array('field_test_multi_add_more' => t('Add another item')));
$edit = [
'field_test_multi[1][value]' => $example_text_2,
'field_test_multi[2][value]' => $example_text_3,
];
$this->drupalPostForm(NULL, $edit, t('Preview'));
$this->clickLink(t('Back to content editing'));
$this->drupalPostForm(NULL, $edit, t('Preview'));
$this->clickLink(t('Back to content editing'));
$this->assertFieldByName('field_test_multi[0][value]', $example_text_1);
$this->assertFieldByName('field_test_multi[1][value]', $example_text_2);
$this->assertFieldByName('field_test_multi[2][value]', $example_text_3);
// Now save the node and make sure all values got saved.
$this->drupalPostForm(NULL, [], t('Save'));
$this->assertText($example_text_1);
$this->assertText($example_text_2);
$this->assertText($example_text_3);
// Edit again, change the menu_ui settings and click on preview.
$this->drupalGet('node/' . $node->id() . '/edit');
$edit = [
'menu[enabled]' => TRUE,
'menu[title]' => 'Changed title',
];
$this->drupalPostForm(NULL, $edit, t('Preview'));
$this->clickLink(t('Back to content editing'));
$this->assertFieldChecked('edit-menu-enabled', 'Menu option is still checked');
$this->assertFieldByName('menu[title]', 'Changed title', 'Menu link title is correct after preview');
// Save, change the title while saving and make sure that it is correctly
// saved.
$edit = [
'menu[enabled]' => TRUE,
'menu[title]' => 'Second title change',
];
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->drupalGet('node/' . $node->id() . '/edit');
$this->assertFieldByName('menu[title]', 'Second title change', 'Menu link title is correct after saving');
}
/**

View file

@ -10,9 +10,11 @@
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\node\NodeInterface;
/**
* Implements hook_ENTITY_TYPE_view() for node entities.
*/
@ -175,3 +177,16 @@ function node_test_node_insert(NodeInterface $node) {
$node->save();
}
}
/**
* Implements hook_form_alter().
*/
function node_test_form_alter(&$form, FormStateInterface $form_state, $form_id) {
if (!$form_state->get('node_test_form_alter')) {
drupal_set_message('Storage is not set');
$form_state->set('node_test_form_alter', TRUE);
}
else {
drupal_set_message('Storage is set');
}
}

View file

@ -49,93 +49,104 @@ class OutsideInBlockFormTest extends OutsideInJavascriptTestBase {
/**
* Tests opening Offcanvas tray by click blocks and elements in the blocks.
*
* @dataProvider providerTestBlocks
*/
public function testBlocks() {
// @todo: re-enable once https://www.drupal.org/node/2830485 is resolved.
$this->markTestSkipped('Test skipped due to random failures in DrupalCI, see https://www.drupal.org/node/2830485');
public function testBlocks($block_id, $new_page_text, $element_selector, $label_selector, $button_text, $toolbar_item) {
$web_assert = $this->assertSession();
$page = $this->getSession()->getPage();
$block_selector = '#' . $block_id;
$this->drupalGet('user');
if (isset($toolbar_item)) {
// Check that you can open a toolbar tray and it will be closed after
// entering edit mode.
if ($element = $page->find('css', "#toolbar-administration a.is-active")) {
// If a tray was open from page load close it.
$element->click();
$this->waitForNoElement("#toolbar-administration a.is-active");
}
$page->find('css', $toolbar_item)->click();
$this->waitForElement("{$toolbar_item}.is-active");
}
$this->toggleEditingMode();
if (isset($toolbar_item)) {
$this->waitForNoElement("{$toolbar_item}.is-active");
}
$this->openBlockForm($block_selector);
switch ($block_id) {
case 'block-powered':
// Fill out form, save the form.
$page->fillField('settings[label]', $new_page_text);
$page->checkField('settings[label_display]');
break;
case 'block-branding':
// Fill out form, save the form.
$page->fillField('settings[site_information][site_name]', $new_page_text);
break;
}
if (isset($new_page_text)) {
$page->pressButton($button_text);
// Make sure the changes are present.
// @todo Use a wait method that will take into account the form submitting
// and all JavaScript activity. https://www.drupal.org/node/2837676
// The use \Behat\Mink\WebAssert::pageTextContains to check text.
$this->assertJsCondition('jQuery("' . $block_selector . ' ' . $label_selector . '").html() == "' . $new_page_text . '"');
}
$this->openBlockForm($block_selector);
$this->toggleEditingMode();
// Canvas should close when editing module is closed.
$this->waitForOffCanvasToClose();
// Go into Edit mode again.
$this->toggleEditingMode();
$element_selector = "$block_selector {$element_selector}";
// Open block form by clicking a element inside the block.
// This confirms that default action for links and form elements is
// suppressed.
$this->openBlockForm($element_selector);
// Exit edit mode.
$this->toggleEditingMode();
}
/**
* Dataprovider for testBlocks().
*/
public function providerTestBlocks() {
$blocks = [
[
'block-powered' => [
'id' => 'block-powered',
'new_page_text' => 'Can you imagine anyone showing the label on this block?',
'element_selector' => '.content a',
'label_selector' => 'h2',
'button_text' => 'Save Powered by Drupal',
'toolbar_item' => '#toolbar-item-user',
],
[
'block-branding' => [
'id' => 'block-branding',
'new_page_text' => 'The site that will live a very short life.',
'element_selector' => 'a[rel="home"]:nth-child(2)',
'label_selector' => '.site-branding__name a',
'button_text' => 'Save Site branding',
'toolbar_item' => '#toolbar-item-administration',
],
[
'block-search' => [
'id' => 'block-search',
'new_page_text' => NULL,
'element_selector' => '#edit-submit',
'label_selector' => 'h2',
'button_text' => 'Save Search form',
'toolbar_item' => NULL,
],
];
$page = $this->getSession()->getPage();
foreach ($blocks as $block) {
$block_selector = '#' . $block['id'];
$this->drupalGet('user');
if (isset($block['toolbar_item'])) {
// Check that you can open a toolbar tray and it will be closed after
// entering edit mode.
if ($element = $page->find('css', "#toolbar-administration a.is-active")) {
// If a tray was open from page load close it.
$element->click();
$this->waitForNoElement("#toolbar-administration a.is-active");
}
$page->find('css', $block['toolbar_item'])->click();
$this->waitForElement("{$block['toolbar_item']}.is-active");
}
$this->toggleEditingMode();
if (isset($block['toolbar_item'])) {
$this->waitForNoElement("{$block['toolbar_item']}.is-active");
}
$this->openBlockForm($block_selector);
switch ($block['id']) {
case 'block-powered':
// Fill out form, save the form.
$page->fillField('settings[label]', $block['new_page_text']);
$page->checkField('settings[label_display]');
break;
case 'block-branding':
// Fill out form, save the form.
$page->fillField('settings[site_information][site_name]', $block['new_page_text']);
break;
}
if (isset($block['new_page_text'])) {
$page->pressButton($block['button_text']);
// Make sure the changes are present.
$this->assertSession()->assertWaitOnAjaxRequest();
$web_assert->pageTextContains($block['new_page_text']);
}
$this->openBlockForm($block_selector);
$this->toggleEditingMode();
// Canvas should close when editing module is closed.
$this->waitForOffCanvasToClose();
// Go into Edit mode again.
$this->toggleEditingMode();
$element_selector = "$block_selector {$block['element_selector']}";
// Open block form by clicking a element inside the block.
// This confirms that default action for links and form elements is
// suppressed.
$this->openBlockForm($element_selector);
// Exit edit mode.
$this->toggleEditingMode();
}
return $blocks;
}
/**

View file

@ -9,6 +9,19 @@ use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
*/
abstract class OutsideInJavascriptTestBase extends JavascriptTestBase {
/**
* {@inheritdoc}
*/
protected function drupalGet($path, array $options = array(), array $headers = array()) {
$return = parent::drupalGet($path, $options, $headers);
// After the page loaded we need to additionally wait until the settings
// tray Ajax activity is done.
$this->assertSession()->assertWaitOnAjaxRequest();
return $return;
}
/**
* Enables a theme.
*

View file

@ -17,16 +17,6 @@ $connection->insert('key_value')
'name' => 'rest',
'value' => 'i:8000;',
])
->fields([
'collection' => 'system.schema',
'name' => 'serialization',
'value' => 'i:8000;',
])
->fields([
'collection' => 'system.schema',
'name' => 'basic_auth',
'value' => 'i:8000;',
])
->execute();
// Update core.extension.
@ -37,9 +27,9 @@ $extensions = $connection->select('config')
->execute()
->fetchField();
$extensions = unserialize($extensions);
$extensions['module']['basic_auth'] = 8000;
$extensions['module']['rest'] = 8000;
$extensions['module']['serialization'] = 8000;
$extensions['module']['basic_auth'] = 0;
$extensions['module']['rest'] = 0;
$extensions['module']['serialization'] = 0;
$connection->update('config')
->fields([
'data' => serialize($extensions),
@ -58,7 +48,7 @@ $config = [
],
],
],
'link_domain' => '~',
'link_domain' => NULL,
];
$data = $connection->insert('config')
->fields([

View file

@ -17,11 +17,6 @@ $connection->insert('key_value')
'name' => 'rest',
'value' => 'i:8000;',
])
->fields([
'collection' => 'system.schema',
'name' => 'serialization',
'value' => 'i:8000;',
])
->execute();
// Update core.extension.
@ -32,8 +27,8 @@ $extensions = $connection->select('config')
->execute()
->fetchField();
$extensions = unserialize($extensions);
$extensions['module']['rest'] = 8000;
$extensions['module']['serialization'] = 8000;
$extensions['module']['rest'] = 0;
$extensions['module']['serialization'] = 0;
$connection->update('config')
->fields([
'data' => serialize($extensions),
@ -52,7 +47,7 @@ $config = [
],
],
],
'link_domain' => '~',
'link_domain' => NULL,
];
$data = $connection->insert('config')
->fields([

View file

@ -18,16 +18,6 @@ $connection->insert('key_value')
'name' => 'rest',
'value' => 'i:8000;',
])
->fields([
'collection' => 'system.schema',
'name' => 'serialization',
'value' => 'i:8000;',
])
->fields([
'collection' => 'system.schema',
'name' => 'basic_auth',
'value' => 'i:8000;',
])
->execute();
// Update core.extension.

View file

@ -383,8 +383,11 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
$get_headers = $response->getHeaders();
// Verify that the GET and HEAD responses are the same. The only difference
// is that there's no body.
$ignored_headers = ['Date', 'Content-Length', 'X-Drupal-Cache', 'X-Drupal-Dynamic-Cache'];
// is that there's no body. For this reason the 'Transfer-Encoding' header
// is also added to the list of headers to ignore, as this could be added to
// GET requests - depending on web server configuration. This would usually
// be 'Transfer-Encoding: chunked'.
$ignored_headers = ['Date', 'Content-Length', 'X-Drupal-Cache', 'X-Drupal-Dynamic-Cache', 'Transfer-Encoding'];
foreach ($ignored_headers as $ignored_header) {
unset($head_headers[$ignored_header]);
unset($get_headers[$ignored_header]);

View file

@ -411,6 +411,7 @@ class ModulesListForm extends FormBase {
foreach (array_keys($modules['install']) as $module) {
if (!drupal_check_module($module)) {
unset($modules['install'][$module]);
unset($modules['experimental'][$module]);
foreach (array_keys($data[$module]->required_by) as $dependent) {
unset($modules['install'][$dependent]);
unset($modules['dependencies'][$dependent]);

View file

@ -120,6 +120,14 @@ class ExperimentalModuleTest extends WebTestBase {
$this->drupalPostForm(NULL, [], 'Continue');
$this->assertText('2 modules have been enabled: Experimental Test, Experimental Dependency Test');
// Try to enable an experimental module that can not be due to
// hook_requirements().
\Drupal::state()->set('experimental_module_requirements_test_requirements', TRUE);
$edit = [];
$edit["modules[Core (Experimental)][experimental_module_requirements_test][enable]"] = TRUE;
$this->drupalPostForm('admin/modules', $edit, 'Install');
$this->assertUrl('admin/modules', [], 'If the module can not be installed we are not taken to the confirm form.');
$this->assertText('The Experimental Test Requirements module can not be installed.');
}
}

View file

@ -192,6 +192,8 @@ abstract class UpdatePathTestBase extends WebTestBase {
$this->container = \Drupal::getContainer();
$this->replaceUser1();
require_once \Drupal::root() . '/core/includes/update.inc';
}
/**
@ -251,6 +253,28 @@ abstract class UpdatePathTestBase extends WebTestBase {
if ($this->checkFailedUpdates) {
$this->assertNoRaw('<strong>' . t('Failed:') . '</strong>');
// Ensure that there are no pending updates.
foreach (['update', 'post_update'] as $update_type) {
switch ($update_type) {
case 'update':
$all_updates = update_get_update_list();
break;
case 'post_update':
$all_updates = \Drupal::service('update.post_update_registry')->getPendingUpdateInformation();
break;
}
foreach ($all_updates as $module => $updates) {
if (!empty($updates['pending'])) {
foreach (array_keys($updates['pending']) as $update_name) {
$this->fail("The $update_name() update function from the $module module did not run.");
}
}
}
}
// Reset the static cache of drupal_get_installed_schema_version() so that
// more complex update path testing works.
drupal_static_reset('drupal_get_installed_schema_version');
// The config schema can be incorrect while the update functions are being
// executed. But once the update has been completed, it needs to be valid
// again. Assert the schema of all configuration objects now.

View file

@ -0,0 +1,6 @@
name: 'Experimental Requirements Test'
type: module
description: 'Module in the experimental package to test hook_requirements() with an experimental module.'
package: Core (Experimental)
version: VERSION
core: 8.x

View file

@ -0,0 +1,20 @@
<?php
/**
* @file
* Experimental Test Requirements module to test hook_requirements().
*/
/**
* Implements hook_requirements().
*/
function experimental_module_requirements_test_requirements() {
$requirements = [];
if (\Drupal::state()->get('experimental_module_requirements_test_requirements', FALSE)) {
$requirements['experimental_module_requirements_test_requirements'] = [
'severity' => REQUIREMENT_ERROR,
'description' => t('The Experimental Test Requirements module can not be installed.'),
];
}
return $requirements;
}

View file

@ -0,0 +1,18 @@
<?php
/**
* @file
* Experimental Test Requirements module to test hook_requirements().
*/
/**
* Implements hook_help().
*/
function experimental_module_requirements_test_help($route_name) {
switch ($route_name) {
case 'help.page.experimental_module_requirements_test':
// Make the help text conform to core standards. See
// \Drupal\system\Tests\Module\InstallUninstallTest::assertHelp().
return t('The Experimental Requirements Test module is not done yet. It may eat your data, but you can read the <a href=":url">online documentation for the Experimental Requirements Test module</a>.', [':url' => 'http://www.example.com']);
}
}

View file

@ -3,7 +3,7 @@ label: Taxonomy terms
migration_tags:
- Drupal 6
source:
plugin: taxonomy_term
plugin: d6_taxonomy_term
process:
# If you are using this file to build a custom migration consider removing
# the tid field to allow incremental migrations.

View file

@ -2,8 +2,9 @@ id: d7_taxonomy_term
label: Taxonomy terms
migration_tags:
- Drupal 7
deriver: Drupal\taxonomy\Plugin\migrate\D7TaxonomyTermDeriver
source:
plugin: taxonomy_term
plugin: d7_taxonomy_term
process:
# If you are using this file to build a custom migration consider removing
# the tid field to allow incremental migrations.
@ -35,3 +36,5 @@ destination:
migration_dependencies:
required:
- d7_taxonomy_vocabulary
optional:
- d7_field_instance

View file

@ -0,0 +1,130 @@
<?php
namespace Drupal\taxonomy\Plugin\migrate;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Database\DatabaseExceptionWrapper;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Drupal\migrate\Exception\RequirementsException;
use Drupal\migrate\Plugin\Migration;
use Drupal\migrate\Plugin\MigrationDeriverTrait;
use Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Deriver for Drupal 7 taxonomy term migrations based on vocabularies.
*/
class D7TaxonomyTermDeriver extends DeriverBase implements ContainerDeriverInterface {
use MigrationDeriverTrait;
/**
* The base plugin ID this derivative is for.
*
* @var string
*/
protected $basePluginId;
/**
* Already-instantiated cckfield plugins, keyed by ID.
*
* @var \Drupal\migrate_drupal\Plugin\MigrateCckFieldInterface[]
*/
protected $cckPluginCache;
/**
* The CCK plugin manager.
*
* @var \Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface
*/
protected $cckPluginManager;
/**
* D7TaxonomyTermDeriver constructor.
*
* @param string $base_plugin_id
* The base plugin ID for the plugin ID.
* @param \Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface $cck_manager
* The CCK plugin manager.
*/
public function __construct($base_plugin_id, MigrateCckFieldPluginManagerInterface $cck_manager) {
$this->basePluginId = $base_plugin_id;
$this->cckPluginManager = $cck_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$base_plugin_id,
$container->get('plugin.manager.migrate.cckfield')
);
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
$fields = [];
try {
$source_plugin = static::getSourcePlugin('d7_field_instance');
$source_plugin->checkRequirements();
// Read all field instance definitions in the source database.
foreach ($source_plugin as $row) {
if ($row->getSourceProperty('entity_type') == 'taxonomy_term') {
$fields[$row->getSourceProperty('bundle')][$row->getSourceProperty('field_name')] = $row->getSource();
}
}
}
catch (RequirementsException $e) {
// If checkRequirements() failed then the field module did not exist and
// we do not have any fields. Therefore, $fields will be empty and below
// we'll create a migration just for the node properties.
}
try {
foreach (static::getSourcePlugin('d7_taxonomy_vocabulary') as $row) {
$bundle = $row->getSourceProperty('machine_name');
$values = $base_plugin_definition;
$values['label'] = t('@label (@type)', [
'@label' => $values['label'],
'@type' => $row->getSourceProperty('name'),
]);
$values['source']['bundle'] = $bundle;
$values['destination']['default_bundle'] = $bundle;
/** @var Migration $migration */
$migration = \Drupal::service('plugin.manager.migration')->createStubMigration($values);
if (isset($fields[$bundle])) {
foreach ($fields[$bundle] as $field_name => $info) {
$field_type = $info['type'];
try {
$plugin_id = $this->cckPluginManager->getPluginIdFromFieldType($field_type, ['core' => 7], $migration);
if (!isset($this->cckPluginCache[$field_type])) {
$this->cckPluginCache[$field_type] = $this->cckPluginManager->createInstance($plugin_id, ['core' => 7], $migration);
}
$this->cckPluginCache[$field_type]
->processCckFieldValues($migration, $field_name, $info);
}
catch (PluginNotFoundException $ex) {
$migration->setProcessOfProperty($field_name, $field_name);
}
}
}
$this->derivatives[$bundle] = $migration->getPluginDefinition();
}
}
catch (DatabaseExceptionWrapper $e) {
// Once we begin iterating the source plugin it is possible that the
// source tables will not exist. This can happen when the
// MigrationPluginManager gathers up the migration definitions but we do
// not actually have a Drupal 7 source database.
}
return $this->derivatives;
}
}

View file

@ -14,6 +14,10 @@ use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
* id = "taxonomy_term",
* source_provider = "taxonomy"
* )
*
* @deprecated in Drupal 8.3.0, intended to be removed in Drupal 9.0.0.
* Use \Drupal\taxonomy\Plugin\migrate\source\d6\Term or
* \Drupal\taxonomy\Plugin\migrate\source\d7\Term.
*/
class Term extends DrupalSqlBase {
@ -50,7 +54,7 @@ class Term extends DrupalSqlBase {
->orderBy('td.tid');
if (isset($this->configuration['vocabulary'])) {
$query->condition('td.vid', $this->configuration['vocabulary'], 'IN');
$query->condition('td.vid', (array) $this->configuration['vocabulary'], 'IN');
}
return $query;

View file

@ -0,0 +1,74 @@
<?php
namespace Drupal\taxonomy\Plugin\migrate\source\d6;
use Drupal\migrate\Row;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
/**
* Taxonomy term source from database.
*
* @todo Support term_relation, term_synonym table if possible.
*
* @MigrateSource(
* id = "d6_taxonomy_term",
* source_provider = "taxonomy"
* )
*/
class Term extends DrupalSqlBase {
/**
* {@inheritdoc}
*/
public function query() {
$query = $this->select('term_data', 'td')
->fields('td')
->distinct()
->orderBy('td.tid');
if (isset($this->configuration['bundle'])) {
$query->condition('td.vid', (array) $this->configuration['bundle'], 'IN');
}
return $query;
}
/**
* {@inheritdoc}
*/
public function fields() {
$fields = [
'tid' => $this->t('The term ID.'),
'vid' => $this->t('Existing term VID'),
'name' => $this->t('The name of the term.'),
'description' => $this->t('The term description.'),
'weight' => $this->t('Weight'),
'parent' => $this->t("The Drupal term IDs of the term's parents."),
];
return $fields;
}
/**
* {@inheritdoc}
*/
public function prepareRow(Row $row) {
// Find parents for this row.
$parents = $this->select('term_hierarchy', 'th')
->fields('th', ['parent', 'tid'])
->condition('tid', $row->getSourceProperty('tid'))
->execute()
->fetchCol();
$row->setSourceProperty('parent', $parents);
return parent::prepareRow($row);
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['tid']['type'] = 'integer';
return $ids;
}
}

View file

@ -0,0 +1,84 @@
<?php
namespace Drupal\taxonomy\Plugin\migrate\source\d7;
use Drupal\migrate\Row;
use Drupal\migrate_drupal\Plugin\migrate\source\d7\FieldableEntity;
/**
* Taxonomy term source from database.
*
* @todo Support term_relation, term_synonym table if possible.
*
* @MigrateSource(
* id = "d7_taxonomy_term",
* source_provider = "taxonomy"
* )
*/
class Term extends FieldableEntity {
/**
* {@inheritdoc}
*/
public function query() {
$query = $this->select('taxonomy_term_data', 'td')
->fields('td')
->distinct()
->orderBy('tid');
$query->leftJoin('taxonomy_vocabulary', 'tv', 'td.vid = tv.vid');
$query->addField('tv', 'machine_name');
if (isset($this->configuration['bundle'])) {
$query->condition('tv.machine_name', (array) $this->configuration['bundle'], 'IN');
}
return $query;
}
/**
* {@inheritdoc}
*/
public function fields() {
$fields = [
'tid' => $this->t('The term ID.'),
'vid' => $this->t('Existing term VID'),
'machine_name' => $this->t('Vocabulary machine name'),
'name' => $this->t('The name of the term.'),
'description' => $this->t('The term description.'),
'weight' => $this->t('Weight'),
'parent' => $this->t("The Drupal term IDs of the term's parents."),
'format' => $this->t("Format of the term description."),
];
return $fields;
}
/**
* {@inheritdoc}
*/
public function prepareRow(Row $row) {
// Get Field API field values.
foreach (array_keys($this->getFields('taxonomy_term', $row->getSourceProperty('machine_name'))) as $field) {
$tid = $row->getSourceProperty('tid');
$row->setSourceProperty($field, $this->getFieldValues('taxonomy_term', $field, $tid));
}
// Find parents for this row.
$parents = $this->select('taxonomy_term_hierarchy', 'th')
->fields('th', ['parent', 'tid'])
->condition('tid', $row->getSourceProperty('tid'))
->execute()
->fetchCol();
$row->setSourceProperty('parent', $parents);
return parent::prepareRow($row);
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['tid']['type'] = 'integer';
return $ids;
}
}

View file

@ -13,7 +13,16 @@ use Drupal\taxonomy\TermInterface;
*/
class MigrateTaxonomyTermTest extends MigrateDrupal7TestBase {
public static $modules = array('taxonomy', 'text');
public static $modules = [
'comment',
'datetime',
'image',
'link',
'node',
'taxonomy',
'telephone',
'text',
];
/**
* The cached taxonomy tree items, keyed by vid and tid.
@ -28,7 +37,16 @@ class MigrateTaxonomyTermTest extends MigrateDrupal7TestBase {
protected function setUp() {
parent::setUp();
$this->installEntitySchema('taxonomy_term');
$this->executeMigrations(['d7_taxonomy_vocabulary', 'd7_taxonomy_term']);
$this->installConfig(static::$modules);
$this->executeMigrations([
'd7_node_type',
'd7_comment_type',
'd7_field',
'd7_taxonomy_vocabulary',
'd7_field_instance',
'd7_taxonomy_term'
]);
}
/**
@ -48,8 +66,12 @@ class MigrateTaxonomyTermTest extends MigrateDrupal7TestBase {
* The weight the migrated entity should have.
* @param array $expected_parents
* The parent terms the migrated entity should have.
* @param int $expected_field_integer_value
* The value the migrated entity field should have.
* @param int $expected_term_reference_tid
* The term reference id the migrated entity field should have.
*/
protected function assertEntity($id, $expected_label, $expected_vid, $expected_description = '', $expected_format = NULL, $expected_weight = 0, $expected_parents = []) {
protected function assertEntity($id, $expected_label, $expected_vid, $expected_description = '', $expected_format = NULL, $expected_weight = 0, $expected_parents = [], $expected_field_integer_value = NULL, $expected_term_reference_tid = NULL) {
/** @var \Drupal\taxonomy\TermInterface $entity */
$entity = Term::load($id);
$this->assertTrue($entity instanceof TermInterface);
@ -60,6 +82,14 @@ class MigrateTaxonomyTermTest extends MigrateDrupal7TestBase {
$this->assertEqual($expected_weight, $entity->getWeight());
$this->assertIdentical($expected_parents, $this->getParentIDs($id));
$this->assertHierarchy($expected_vid, $id, $expected_parents);
if (!is_null($expected_field_integer_value)) {
$this->assertTrue($entity->hasField('field_integer'));
$this->assertEquals($expected_field_integer_value, $entity->field_integer->value);
}
if (!is_null($expected_term_reference_tid)) {
$this->assertTrue($entity->hasField('field_integer'));
$this->assertEquals($expected_term_reference_tid, $entity->field_term_reference->target_id);
}
}
/**
@ -67,9 +97,9 @@ class MigrateTaxonomyTermTest extends MigrateDrupal7TestBase {
*/
public function testTaxonomyTerms() {
$this->assertEntity(1, 'General discussion', 'forums', '', NULL, 2);
$this->assertEntity(2, 'Term1', 'test_vocabulary', 'The first term.', 'filtered_html');
$this->assertEntity(2, 'Term1', 'test_vocabulary', 'The first term.', 'filtered_html', 0, [], NULL, 3);
$this->assertEntity(3, 'Term2', 'test_vocabulary', 'The second term.', 'filtered_html');
$this->assertEntity(4, 'Term3', 'test_vocabulary', 'The third term.', 'full_html', 0, [3]);
$this->assertEntity(4, 'Term3', 'test_vocabulary', 'The third term.', 'full_html', 0, [3], 6);
$this->assertEntity(5, 'Custom Forum', 'forums', 'Where the cool kids are.', NULL, 3);
$this->assertEntity(6, 'Games', 'forums', '', NULL, 4);
$this->assertEntity(7, 'Minecraft', 'forums', '', NULL, 1, [6]);

View file

@ -1,11 +1,11 @@
<?php
namespace Drupal\Tests\taxonomy\Kernel\Plugin\migrate\source;
namespace Drupal\Tests\taxonomy\Kernel\Plugin\migrate\source\d6;
/**
* Tests the taxonomy term source with vocabulary filter.
*
* @covers \Drupal\taxonomy\Plugin\migrate\source\Term
* @covers \Drupal\taxonomy\Plugin\migrate\source\d6\Term
* @group taxonomy
*/
class TermSourceWithVocabularyFilterTest extends TermTest {
@ -47,7 +47,7 @@ class TermSourceWithVocabularyFilterTest extends TermTest {
// Set up source plugin configuration.
$tests[0]['configuration'] = [
'vocabulary' => [5],
'bundle' => [5],
];
return $tests;

View file

@ -1,13 +1,13 @@
<?php
namespace Drupal\Tests\taxonomy\Kernel\Plugin\migrate\source;
namespace Drupal\Tests\taxonomy\Kernel\Plugin\migrate\source\d6;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests taxonomy term source plugin.
*
* @covers \Drupal\taxonomy\Plugin\migrate\source\Term
* @covers \Drupal\taxonomy\Plugin\migrate\source\d6\Term
* @group taxonomy
*/
class TermTest extends MigrateSqlSourceTestBase {
@ -67,6 +67,13 @@ class TermTest extends MigrateSqlSourceTestBase {
'description' => 'description value 6',
'weight' => 0,
],
[
'tid' => 7,
'vid' => 3,
'name' => 'name value 7',
'description' => 'description value 7',
'weight' => 0,
],
];
$tests[0]['source_data']['term_hierarchy'] = [
[
@ -97,6 +104,10 @@ class TermTest extends MigrateSqlSourceTestBase {
'tid' => 6,
'parent' => 2,
],
[
'tid' => 7,
'parent' => 0,
],
];
// The expected results.
@ -149,8 +160,58 @@ class TermTest extends MigrateSqlSourceTestBase {
'weight' => 0,
'parent' => [3, 2],
],
[
'tid' => 7,
'vid' => 3,
'name' => 'name value 7',
'description' => 'description value 7',
'weight' => 0,
'parent' => [0],
],
];
$tests[0]['expected_count'] = NULL;
// Empty configuration will return terms for all vocabularies.
$tests[0]['configuration'] = [];
// Change configuration to get one vocabulary, 5.
$tests[1]['source_data'] = $tests[0]['source_data'];
$tests[1]['expected_data'] = [
[
'tid' => 1,
'vid' => 5,
'name' => 'name value 1',
'description' => 'description value 1',
'weight' => 0,
'parent' => [0],
],
[
'tid' => 4,
'vid' => 5,
'name' => 'name value 4',
'description' => 'description value 4',
'weight' => 1,
'parent' => [1],
],
];
$tests[1]['expected_count'] = NULL;
$tests[1]['configuration']['bundle'] = ['5'];
// Same as previous test, but with configuration vocabulary as a string
// instead of an array.
$tests[2]['source_data'] = $tests[0]['source_data'];
$tests[2]['expected_data'] = $tests[1]['expected_data'];
$tests[2]['expected_count'] = NULL;
$tests[2]['configuration']['bundle'] = '5';
// Change configuration to get two vocabularies, 5 and 6.
$tests[3]['source_data'] = $tests[0]['source_data'];
$tests[3]['expected_data'] = $tests[0]['expected_data'];
// Remove the last element because it is for vid 3.
array_pop($tests[3]['expected_data']);
$tests[3]['expected_count'] = NULL;
$tests[3]['configuration']['bundle'] = ['5', '6'];
return $tests;
}

View file

@ -0,0 +1,56 @@
<?php
namespace Drupal\Tests\taxonomy\Kernel\Plugin\migrate\source\d7;
/**
* Tests the taxonomy term source with vocabulary filter.
*
* @covers \Drupal\taxonomy\Plugin\migrate\source\d7\Term
* @group taxonomy
*/
class TermSourceWithVocabularyFilterTest extends TermTest {
/**
* {@inheritdoc}
*/
public static $modules = ['taxonomy', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public function providerSource() {
// Get the source data from parent.
$tests = parent::providerSource();
// The expected results.
$tests[0]['expected_data'] = [
[
'tid' => 1,
'vid' => 5,
'name' => 'name value 1',
'description' => 'description value 1',
'weight' => 0,
'parent' => [0],
],
[
'tid' => 4,
'vid' => 5,
'name' => 'name value 4',
'description' => 'description value 4',
'weight' => 1,
'parent' => [1],
],
];
// We know there are two rows with machine_name == 'tags'.
$tests[0]['expected_count'] = 2;
// Set up source plugin configuration.
$tests[0]['configuration'] = [
'bundle' => ['tags'],
];
return $tests;
}
}

View file

@ -0,0 +1,258 @@
<?php
namespace Drupal\Tests\taxonomy\Kernel\Plugin\migrate\source\d7;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests taxonomy term source plugin.
*
* @covers \Drupal\taxonomy\Plugin\migrate\source\d7\Term
* @group taxonomy
*/
class TermTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['taxonomy', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public function providerSource() {
$tests = [];
// The source data.
$tests[0]['source_data']['taxonomy_term_data'] = [
[
'tid' => 1,
'vid' => 5,
'name' => 'name value 1',
'description' => 'description value 1',
'weight' => 0,
],
[
'tid' => 2,
'vid' => 6,
'name' => 'name value 2',
'description' => 'description value 2',
'weight' => 0,
],
[
'tid' => 3,
'vid' => 6,
'name' => 'name value 3',
'description' => 'description value 3',
'weight' => 0,
],
[
'tid' => 4,
'vid' => 5,
'name' => 'name value 4',
'description' => 'description value 4',
'weight' => 1,
],
[
'tid' => 5,
'vid' => 6,
'name' => 'name value 5',
'description' => 'description value 5',
'weight' => 1,
],
[
'tid' => 6,
'vid' => 6,
'name' => 'name value 6',
'description' => 'description value 6',
'weight' => 0,
],
[
'tid' => 7,
'vid' => 3,
'name' => 'name value 7',
'description' => 'description value 7',
'weight' => 0,
],
];
$tests[0]['source_data']['taxonomy_term_hierarchy'] = [
[
'tid' => 1,
'parent' => 0,
],
[
'tid' => 2,
'parent' => 0,
],
[
'tid' => 3,
'parent' => 0,
],
[
'tid' => 4,
'parent' => 1,
],
[
'tid' => 5,
'parent' => 2,
],
[
'tid' => 6,
'parent' => 3,
],
[
'tid' => 6,
'parent' => 2,
],
[
'tid' => 7,
'parent' => 0,
],
];
$tests[0]['source_data']['taxonomy_vocabulary'] = [
[
'vid' => 5,
'machine_name' => 'tags',
],
[
'vid' => 6,
'machine_name' => 'categories',
],
];
$tests[0]['source_data']['field_config_instance'] = [
[
'field_name' => 'field_term_field',
'entity_type' => 'taxonomy_term',
'bundle' => 'tags',
'deleted' => 0,
],
[
'field_name' => 'field_term_field',
'entity_type' => 'taxonomy_term',
'bundle' => 'categories',
'deleted' => 0,
],
];
$tests[0]['source_data']['field_data_field_term_field'] = [
[
'entity_type' => 'taxonomy_term',
'bundle' => 'tags',
'deleted' => 0,
'entity_id' => 1,
'delta' => 0,
],
[
'entity_type' => 'taxonomy_term',
'bundle' => 'categories',
'deleted' => 0,
'entity_id' => 1,
'delta' => 0,
],
];
// The expected results.
$tests[0]['expected_data'] = [
[
'tid' => 1,
'vid' => 5,
'name' => 'name value 1',
'description' => 'description value 1',
'weight' => 0,
'parent' => [0],
],
[
'tid' => 2,
'vid' => 6,
'name' => 'name value 2',
'description' => 'description value 2',
'weight' => 0,
'parent' => [0],
],
[
'tid' => 3,
'vid' => 6,
'name' => 'name value 3',
'description' => 'description value 3',
'weight' => 0,
'parent' => [0],
],
[
'tid' => 4,
'vid' => 5,
'name' => 'name value 4',
'description' => 'description value 4',
'weight' => 1,
'parent' => [1],
],
[
'tid' => 5,
'vid' => 6,
'name' => 'name value 5',
'description' => 'description value 5',
'weight' => 1,
'parent' => [2],
],
[
'tid' => 6,
'vid' => 6,
'name' => 'name value 6',
'description' => 'description value 6',
'weight' => 0,
'parent' => [3, 2],
],
[
'tid' => 7,
'vid' => 3,
'name' => 'name value 7',
'description' => 'description value 7',
'weight' => 0,
'parent' => [0],
],
];
$tests[0]['expected_count'] = NULL;
// Empty configuration will return terms for all vocabularies.
$tests[0]['configuration'] = [];
// Change configuration to get one vocabulary, "tags".
$tests[1]['source_data'] = $tests[0]['source_data'];
$tests[1]['expected_data'] = [
[
'tid' => 1,
'vid' => 5,
'name' => 'name value 1',
'description' => 'description value 1',
'weight' => 0,
'parent' => [0],
],
[
'tid' => 4,
'vid' => 5,
'name' => 'name value 4',
'description' => 'description value 4',
'weight' => 1,
'parent' => [1],
],
];
$tests[1]['expected_count'] = NULL;
$tests[1]['configuration']['bundle'] = ['tags'];
// Same as previous test, but with configuration vocabulary as a string
// instead of an array.
$tests[2]['source_data'] = $tests[0]['source_data'];
$tests[2]['expected_data'] = $tests[1]['expected_data'];
$tests[2]['expected_count'] = NULL;
$tests[2]['configuration']['bundle'] = 'tags';
// Change configuration to get two vocabularies, "tags" and "categories".
$tests[3]['source_data'] = $tests[0]['source_data'];
$tests[3]['expected_data'] = $tests[0]['expected_data'];
// Remove the last element because it is for vid 3.
array_pop($tests[3]['expected_data']);
$tests[3]['expected_count'] = NULL;
$tests[3]['configuration']['bundle'] = ['tags', 'categories'];
return $tests;
}
}

View file

@ -3,12 +3,8 @@ label: Profile values
class: Drupal\user\Plugin\migrate\ProfileValues
migration_tags:
- Drupal 6
builder:
plugin: d6_profile_values
source:
plugin: d6_profile_field_values
load:
plugin: drupal_entity
process:
uid: uid
destination:

View file

@ -233,7 +233,7 @@ class UserAuthenticationController extends ControllerBase implements ContainerIn
/**
* Logs out a user.
*
* @return \Drupal\rest\ResourceResponse
* @return \Symfony\Component\HttpFoundation\Response
* The response object.
*/
public function logout() {

View file

@ -1172,7 +1172,7 @@ abstract class ArgumentPluginBase extends HandlerBase implements CacheableDepend
* Moves argument options into their place.
*
* When configuring the default argument behavior, almost each of the radio
* buttons has its own fieldset shown bellow it when the radio button is
* buttons has its own fieldset shown below it when the radio button is
* clicked. That fieldset is created through a custom form process callback.
* Each element that has #argument_option defined and pointing to a default
* behavior gets moved to the appropriate fieldset.

View file

@ -206,7 +206,7 @@ class InOperator extends FilterPluginBase {
}
if (empty($this->options['expose']['multiple'])) {
if (empty($this->options['expose']['required']) && (empty($default_value) || !empty($this->options['expose']['reduce']))) {
if (empty($this->options['expose']['required']) && (empty($default_value) || !empty($this->options['expose']['reduce'])) || isset($this->options['value']['all'])) {
$default_value = 'All';
}
elseif (empty($default_value)) {

View file

@ -18,19 +18,24 @@ class FilterTest extends PluginTestBase {
*
* @var array
*/
public static $testViews = array('test_filter');
public static $testViews = array('test_filter', 'test_filter_in_operator_ui');
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('views_ui');
public static $modules = array('views_ui', 'node');
protected function setUp() {
parent::setUp();
$this->enableViewsTestModule();
$this->adminUser = $this->drupalCreateUser(array('administer views'));
$this->drupalLogin($this->adminUser);
$this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
$this->drupalCreateContentType(['type' => 'page', 'name' => 'Page']);
}
/**
@ -138,4 +143,23 @@ class FilterTest extends PluginTestBase {
$this->assertEqual(count($view->result), 5, format_string('All @count results returned', array('@count' => count($view->displayHandlers))));
}
/**
* Test no error message is displayed when all options are selected in an
* exposed filter.
*/
public function testInOperatorSelectAllOptions() {
$view = Views::getView('test_filter_in_operator_ui');
$row['row[type]'] = 'fields';
$this->drupalPostForm('admin/structure/views/nojs/display/test_filter_in_operator_ui/default/row', $row, t('Apply'));
$field['name[node_field_data.nid]'] = TRUE;
$this->drupalPostForm('admin/structure/views/nojs/add-handler/test_filter_in_operator_ui/default/field', $field, t('Add and configure fields'));
$this->drupalPostForm('admin/structure/views/nojs/handler/test_filter_in_operator_ui/default/field/nid', [], t('Apply'));
$edit['options[value][all]'] = TRUE;
$edit['options[value][article]'] = TRUE;
$edit['options[value][page]'] = TRUE;
$this->drupalPostForm('admin/structure/views/nojs/handler/test_filter_in_operator_ui/default/filter/type', $edit, t('Apply'));
$this->drupalPostForm('admin/structure/views/view/test_filter_in_operator_ui/edit/default', [], t('Save'));
$this->drupalPostForm(NULL, [], t('Update preview'));
$this->assertNoText('An illegal choice has been detected.');
}
}

View file

@ -61,7 +61,7 @@ class Views {
/**
* Returns the views data helper service.
*
* @return \Drupal\views\ViewsData
* @return \Drupal\views\ViewsDataHelper
* Returns a views data helper object.
*/
public static function viewsDataHelper() {

View file

@ -2,6 +2,10 @@
namespace Drupal\FunctionalJavascriptTests;
use Behat\Mink\Element\NodeElement;
use Behat\Mink\Exception\ElementHtmlException;
use Behat\Mink\Exception\ElementNotFoundException;
use Behat\Mink\Exception\UnsupportedDriverActionException;
use Drupal\Tests\WebAssert;
/**
@ -39,4 +43,199 @@ class JSWebAssert extends WebAssert {
$this->assertWaitOnAjaxRequest();
}
/**
* Test that a node, or it's specific corner, is visible in the viewport.
*
* Note: Always set the viewport size. This can be done with a PhantomJS
* startup parameter or in your test with \Behat\Mink\Session->resizeWindow().
* Drupal CI Javascript tests by default use a viewport of 1024x768px.
*
* @param string $selector_type
* The element selector type (CSS, XPath).
* @param string|array $selector
* The element selector. Note: the first found element is used.
* @param bool|string $corner
* (Optional) The corner to test:
* topLeft, topRight, bottomRight, bottomLeft.
* Or FALSE to check the complete element (default).
* @param string $message
* (optional) A message for the exception.
*
* @throws \Behat\Mink\Exception\ElementHtmlException
* When the element doesn't exist.
* @throws \Behat\Mink\Exception\ElementNotFoundException
* When the element is not visible in the viewport.
*/
public function assertVisibleInViewport($selector_type, $selector, $corner = FALSE, $message = 'Element is not visible in the viewport.') {
$node = $this->session->getPage()->find($selector_type, $selector);
if ($node === NULL) {
if (is_array($selector)) {
$selector = implode(' ', $selector);
}
throw new ElementNotFoundException($this->session->getDriver(), 'element', $selector_type, $selector);
}
// Check if the node is visible on the page, which is a prerequisite of
// being visible in the viewport.
if (!$node->isVisible()) {
throw new ElementHtmlException($message, $this->session->getDriver(), $node);
}
$result = $this->checkNodeVisibilityInViewport($node, $corner);
if (!$result) {
throw new ElementHtmlException($message, $this->session->getDriver(), $node);
}
}
/**
* Test that a node, or its specific corner, is not visible in the viewport.
*
* Note: the node should exist in the page, otherwise this assertion fails.
*
* @param string $selector_type
* The element selector type (CSS, XPath).
* @param string|array $selector
* The element selector. Note: the first found element is used.
* @param bool|string $corner
* (Optional) Corner to test: topLeft, topRight, bottomRight, bottomLeft.
* Or FALSE to check the complete element (default).
* @param string $message
* (optional) A message for the exception.
*
* @throws \Behat\Mink\Exception\ElementHtmlException
* When the element doesn't exist.
* @throws \Behat\Mink\Exception\ElementNotFoundException
* When the element is not visible in the viewport.
*
* @see \Drupal\FunctionalJavascriptTests\JSWebAssert::assertVisibleInViewport()
*/
public function assertNotVisibleInViewport($selector_type, $selector, $corner = FALSE, $message = 'Element is visible in the viewport.') {
$node = $this->session->getPage()->find($selector_type, $selector);
if ($node === NULL) {
if (is_array($selector)) {
$selector = implode(' ', $selector);
}
throw new ElementNotFoundException($this->session->getDriver(), 'element', $selector_type, $selector);
}
$result = $this->checkNodeVisibilityInViewport($node, $corner);
if ($result) {
throw new ElementHtmlException($message, $this->session->getDriver(), $node);
}
}
/**
* Check the visibility of a node, or it's specific corner.
*
* @param \Behat\Mink\Element\NodeElement $node
* A valid node.
* @param bool|string $corner
* (Optional) Corner to test: topLeft, topRight, bottomRight, bottomLeft.
* Or FALSE to check the complete element (default).
*
* @return bool
* Returns TRUE if the node is visible in the viewport, FALSE otherwise.
*
* @throws \Behat\Mink\Exception\UnsupportedDriverActionException
* When an invalid corner specification is given.
*/
private function checkNodeVisibilityInViewport(NodeElement $node, $corner = FALSE) {
$xpath = $node->getXpath();
// Build the Javascript to test if the complete element or a specific corner
// is in the viewport.
switch ($corner) {
case 'topLeft':
$test_javascript_function = <<<JS
function t(r, lx, ly) {
return (
r.top >= 0 &&
r.top <= ly &&
r.left >= 0 &&
r.left <= lx
)
}
JS;
break;
case 'topRight':
$test_javascript_function = <<<JS
function t(r, lx, ly) {
return (
r.top >= 0 &&
r.top <= ly &&
r.right >= 0 &&
r.right <= lx
);
}
JS;
break;
case 'bottomRight':
$test_javascript_function = <<<JS
function t(r, lx, ly) {
return (
r.bottom >= 0 &&
r.bottom <= ly &&
r.right >= 0 &&
r.right <= lx
);
}
JS;
break;
case 'bottomLeft':
$test_javascript_function = <<<JS
function t(r, lx, ly) {
return (
r.bottom >= 0 &&
r.bottom <= ly &&
r.left >= 0 &&
r.left <= lx
);
}
JS;
break;
case FALSE:
$test_javascript_function = <<<JS
function t(r, lx, ly) {
return (
r.top >= 0 &&
r.left >= 0 &&
r.bottom <= ly &&
r.right <= lx
);
}
JS;
break;
// Throw an exception if an invalid corner parameter is given.
default:
throw new UnsupportedDriverActionException($corner, $this->session->getDriver());
}
// Build the full Javascript test. The shared logic gets the corner
// specific test logic injected.
$full_javascript_visibility_test = <<<JS
(function(t){
var w = window,
d = document,
e = d.documentElement,
n = d.evaluate("$xpath", d, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue,
r = n.getBoundingClientRect(),
lx = (w.innerWidth || e.clientWidth),
ly = (w.innerHeight || e.clientHeight);
return t(r, lx, ly);
}($test_javascript_function));
JS;
// Check the visibility by injecting and executing the full Javascript test
// script in the page.
return $this->session->evaluateScript($full_javascript_visibility_test);
}
}

View file

@ -146,7 +146,7 @@ abstract class KernelTestBase extends \PHPUnit_Framework_TestCase implements Ser
*
* @var array
*/
public static $modules = array();
protected static $modules = array();
/**
* The virtual filesystem root directory.

View file

@ -175,7 +175,7 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
*
* @see \Drupal\Tests\BrowserTestBase::installDrupal()
*/
public static $modules = [];
protected static $modules = [];
/**
* An array of config object names that are excluded from schema checking.

View file

@ -762,13 +762,7 @@ if (file_exists(__DIR__ . '/settings.pantheon.php')) {
*
* Keep this code block at the end of this file to take full effect.
*/
<<<<<<< HEAD
if (file_exists(__DIR__ . '/settings.local.php')) {
include __DIR__ . '/settings.local.php';
}
=======
#
# if (file_exists($app_root . '/' . $site_path . '/settings.local.php')) {
# include $app_root . '/' . $site_path . '/settings.local.php';
# }
>>>>>>> b96f629ea4b63fb16b11b89c0a88fb8524e82359

2
vendor/autoload.php vendored
View file

@ -2,6 +2,6 @@
// autoload.php @generated by Composer
require_once __DIR__ . '/composer' . '/autoload_real.php';
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInitDrupal8::getLoader();

View file

@ -53,8 +53,9 @@ class ClassLoader
private $useIncludePath = false;
private $classMap = array();
private $classMapAuthoritative = false;
private $missingClasses = array();
private $apcuPrefix;
public function getPrefixes()
{
@ -271,6 +272,26 @@ class ClassLoader
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
@ -313,29 +334,34 @@ class ClassLoader
*/
public function findFile($class)
{
// work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731
if ('\\' == $class[0]) {
$class = substr($class, 1);
}
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative) {
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if ($file === null && defined('HHVM_VERSION')) {
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if ($file === null) {
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
return $this->classMap[$class] = false;
$this->missingClasses[$class] = true;
}
return $file;
@ -399,6 +425,8 @@ class ClassLoader
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
return false;
}
}

View file

@ -1,3 +1,4 @@
Copyright (c) 2016 Nils Adermann, Jordi Boggiano
Permission is hereby granted, free of charge, to any person obtaining a copy
@ -17,3 +18,4 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -23,7 +23,7 @@ class ComposerAutoloaderInitDrupal8
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInitDrupal8', 'loadClassLoader'));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION');
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
if ($useStaticLoader) {
require_once __DIR__ . '/autoload_static.php';

View file

@ -22,7 +22,7 @@
"composer/composer": "1.0.*@dev",
"phpunit/phpunit": "4.1.*"
},
"time": "2015-02-18 17:17:01",
"time": "2015-02-18T17:17:01+00:00",
"type": "composer-installer",
"extra": {
"class": "Composer\\Installers\\Installer",
@ -121,7 +121,7 @@
"phpunit/phpunit": "~4.8|~5.0",
"squizlabs/php_codesniffer": "~2.1.0"
},
"time": "2016-03-08 17:11:37",
"time": "2016-03-08T17:11:37+00:00",
"type": "composer-plugin",
"extra": {
"branch-alias": {
@ -165,7 +165,7 @@
"require-dev": {
"phpunit/phpunit": "4.*"
},
"time": "2014-11-20 16:49:30",
"time": "2014-11-20T16:49:30+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@ -210,7 +210,7 @@
"ircmaxell/password-compat": "~1.0",
"php": ">=5.3.3"
},
"time": "2016-01-20 09:13:37",
"time": "2016-01-20T09:13:37+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -267,7 +267,7 @@
"require": {
"php": ">=5.3.3"
},
"time": "2016-01-20 09:13:37",
"time": "2016-01-20T09:13:37+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -330,7 +330,7 @@
"suggest": {
"ext-mbstring": "For best performance"
},
"time": "2016-01-20 09:13:37",
"time": "2016-01-20T09:13:37+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -394,7 +394,7 @@
"require-dev": {
"symfony/expression-language": "~2.4|~3.0.0"
},
"time": "2016-03-27 12:57:53",
"time": "2016-03-27T12:57:53+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -456,7 +456,7 @@
"symfony/dependency-injection": "",
"symfony/http-kernel": ""
},
"time": "2016-03-07 14:04:32",
"time": "2016-03-07T14:04:32+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -504,7 +504,7 @@
"reference": "1.0.0",
"shasum": ""
},
"time": "2012-12-21 11:40:51",
"time": "2012-12-21T11:40:51+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@ -555,7 +555,7 @@
"symfony/class-loader": "~2.2",
"symfony/http-kernel": "~2.3.24|~2.5.9|~2.6,>=2.6.2"
},
"time": "2015-10-11 09:39:48",
"time": "2015-10-11T09:39:48+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -636,7 +636,7 @@
"symfony/finder": "",
"symfony/var-dumper": ""
},
"time": "2016-03-25 01:40:30",
"time": "2016-03-25T01:40:30+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -689,7 +689,7 @@
"symfony/http-foundation": "~2.1|~3.0",
"symfony/http-kernel": "~2.1|~3.0"
},
"time": "2016-08-01 12:05:04",
"time": "2016-08-01T12:05:04+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@ -736,7 +736,7 @@
"phpunit/phpunit": "~4.5",
"phpunit/phpunit-mock-objects": "~2.3"
},
"time": "2015-09-21 09:42:36",
"time": "2015-09-21T09:42:36+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -795,7 +795,7 @@
"require": {
"php": ">=5.3.2"
},
"time": "2014-09-09 13:34:57",
"time": "2014-09-09T13:34:57+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -854,7 +854,7 @@
"require-dev": {
"phpunit/phpunit": "4.*"
},
"time": "2014-12-20 21:24:13",
"time": "2014-12-20T21:24:13+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -923,7 +923,7 @@
"require-dev": {
"phpunit/phpunit": "~4.0"
},
"time": "2015-04-14 22:21:58",
"time": "2015-04-14T22:21:58+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -996,7 +996,7 @@
"predis/predis": "~1.0",
"satooshi/php-coveralls": "~0.6"
},
"time": "2015-08-31 12:36:41",
"time": "2015-08-31T12:36:41+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -1065,7 +1065,7 @@
"doctrine/cache": "1.*",
"phpunit/phpunit": "4.*"
},
"time": "2015-08-31 12:32:49",
"time": "2015-08-31T12:32:49+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -1138,7 +1138,7 @@
"require-dev": {
"phpunit/phpunit": "~3.7"
},
"time": "2015-08-31 13:00:22",
"time": "2015-08-31T13:00:22+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -1215,7 +1215,7 @@
"suggest": {
"ml/json-ld": "~1.0"
},
"time": "2015-02-27 09:45:49",
"time": "2015-02-27T09:45:49+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@ -1274,7 +1274,7 @@
"phpunit/phpunit": "~4.4",
"satooshi/php-coveralls": "dev-master"
},
"time": "2015-06-22 21:07:51",
"time": "2015-06-22T21:07:51+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -1327,7 +1327,7 @@
"require-dev": {
"phpunit/phpunit": "~4.0"
},
"time": "2016-05-18 16:56:05",
"time": "2016-05-18T16:56:05+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -1377,7 +1377,7 @@
"require": {
"php": ">=5.3.0"
},
"time": "2015-05-04 20:22:00",
"time": "2015-05-04T20:22:00+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -1435,7 +1435,7 @@
"require-dev": {
"phpunit/phpunit": "~4.0"
},
"time": "2016-06-24 23:00:38",
"time": "2016-06-24T23:00:38+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -1494,7 +1494,7 @@
"sami/sami": "~2.0",
"satooshi/php-coveralls": "1.0.*"
},
"time": "2016-05-10 14:11:45",
"time": "2016-05-10T14:11:45+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -1561,7 +1561,7 @@
"suggest": {
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
},
"time": "2015-12-01 02:52:15",
"time": "2015-12-01T02:52:15+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@ -1610,7 +1610,7 @@
"require-dev": {
"silex/silex": "~1.0"
},
"time": "2016-06-02 06:58:42",
"time": "2016-06-02T06:58:42+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -1676,7 +1676,7 @@
"symfony/http-foundation": "For using a Symfony Request object",
"symfony/yaml": "For using the YAML loader"
},
"time": "2016-03-23 13:11:46",
"time": "2016-03-23T13:11:46+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -1746,7 +1746,7 @@
"suggest": {
"symfony/event-dispatcher": "DynamicRouter can optionally trigger an event at the start of matching. Minimal version (~2.1)"
},
"time": "2016-03-31 09:11:39",
"time": "2016-03-31T09:11:39+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -1794,7 +1794,7 @@
"require": {
"php": ">=5.3.3"
},
"time": "2016-03-03 16:49:40",
"time": "2016-03-03T16:49:40+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -1853,7 +1853,7 @@
"require-dev": {
"symfony/finder": "~2.0,>=2.0.5|~3.0.0"
},
"time": "2016-03-10 19:33:53",
"time": "2016-03-10T19:33:53+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -1915,7 +1915,7 @@
"symfony/event-dispatcher": "",
"symfony/process": ""
},
"time": "2016-03-17 09:19:04",
"time": "2016-03-17T09:19:04+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -1979,7 +1979,7 @@
"symfony/proxy-manager-bridge": "Generate service proxies to lazy load them",
"symfony/yaml": ""
},
"time": "2016-03-21 07:27:21",
"time": "2016-03-21T07:27:21+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -2033,7 +2033,7 @@
"suggest": {
"ext-iconv": "For best performance"
},
"time": "2016-02-26 11:31:02",
"time": "2016-02-26T11:31:02+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -2091,7 +2091,7 @@
"require": {
"php": ">=5.3.9"
},
"time": "2016-03-23 13:11:46",
"time": "2016-03-23T13:11:46+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -2150,7 +2150,7 @@
"suggest": {
"zendframework/zend-diactoros": "To use the Zend Diactoros factory"
},
"time": "2015-05-29 17:57:12",
"time": "2015-05-29T17:57:12+00:00",
"type": "symfony-bridge",
"installation-source": "dist",
"autoload": {
@ -2213,7 +2213,7 @@
"symfony/property-access": "For using the ObjectNormalizer.",
"symfony/yaml": "For using the default YAML mapping loader."
},
"time": "2016-03-07 14:04:32",
"time": "2016-03-07T14:04:32+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -2279,7 +2279,7 @@
"symfony/config": "",
"symfony/yaml": ""
},
"time": "2016-03-25 01:40:30",
"time": "2016-03-25T01:40:30+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -2353,7 +2353,7 @@
"symfony/property-access": "For using the 2.4 Validator API",
"symfony/yaml": ""
},
"time": "2016-03-27 12:57:53",
"time": "2016-03-27T12:57:53+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -2412,7 +2412,7 @@
"phpunit/phpunit": "~4.6",
"squizlabs/php_codesniffer": "^2.3.1"
},
"time": "2015-08-10 20:04:20",
"time": "2015-08-10T20:04:20+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -2474,7 +2474,7 @@
"zendframework/zend-serializer": "Zend\\Serializer component",
"zendframework/zend-servicemanager": "To support hydrator plugin manager usage"
},
"time": "2015-09-25 04:06:33",
"time": "2015-09-25T04:06:33+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -2532,7 +2532,7 @@
"zendframework/zend-serializer": "^2.5.1, to use the SerializableStrategy",
"zendframework/zend-servicemanager": "^2.5.1, to support hydrator plugin manager usage"
},
"time": "2015-09-17 14:06:43",
"time": "2015-09-17T14:06:43+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -2578,7 +2578,7 @@
"fabpot/php-cs-fixer": "1.7.*",
"phpunit/phpunit": "~4.0"
},
"time": "2015-06-03 14:05:37",
"time": "2015-06-03T14:05:37+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -2638,7 +2638,7 @@
"zendframework/zend-servicemanager": "Zend\\ServiceManager component, for default/recommended ExtensionManager implementations",
"zendframework/zend-validator": "Zend\\Validator component"
},
"time": "2015-08-04 21:39:18",
"time": "2015-08-04T21:39:18+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -2687,7 +2687,7 @@
"suggest": {
"symfony/css-selector": ""
},
"time": "2015-10-11 09:39:48",
"time": "2015-10-11T09:39:48+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -2735,7 +2735,7 @@
"require": {
"php": ">=5.3.9"
},
"time": "2016-03-04 07:54:35",
"time": "2016-03-04T07:54:35+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -2798,7 +2798,7 @@
"suggest": {
"symfony/process": ""
},
"time": "2015-10-23 14:47:27",
"time": "2015-10-23T14:47:27+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -2853,7 +2853,7 @@
"phpunit/phpunit": "^4.0",
"psr/log": "^1.0"
},
"time": "2016-07-15 17:22:37",
"time": "2016-07-15T17:22:37+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -2914,7 +2914,7 @@
"symfony/css-selector": "~2.1|~3.0",
"symfony/dom-crawler": "~2.1|~3.0"
},
"time": "2015-11-05 12:58:44",
"time": "2015-11-05T12:58:44+00:00",
"type": "application",
"extra": {
"branch-alias": {
@ -2971,7 +2971,7 @@
"behat/mink-selenium2-driver": "slow, but JS-enabled driver for any app (requires Selenium2)",
"behat/mink-zombie-driver": "fast and JS-enabled headless driver for any app (requires node.js)"
},
"time": "2016-03-05 08:26:18",
"time": "2016-03-05T08:26:18+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -3028,7 +3028,7 @@
"silex/silex": "~1.2",
"symfony/phpunit-bridge": "~2.7|~3.0"
},
"time": "2016-03-05 08:59:47",
"time": "2016-03-05T08:59:47+00:00",
"type": "mink-driver",
"extra": {
"branch-alias": {
@ -3085,7 +3085,7 @@
"require-dev": {
"symfony/phpunit-bridge": "~2.7|~3.0"
},
"time": "2016-03-05 09:04:22",
"time": "2016-03-05T09:04:22+00:00",
"type": "mink-driver",
"extra": {
"branch-alias": {
@ -3140,7 +3140,7 @@
"symfony/debug": "~2.7",
"symfony/phpunit-bridge": "~2.7"
},
"time": "2016-01-25 21:22:18",
"time": "2016-01-25T21:22:18+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -3206,7 +3206,7 @@
"symfony/phpunit-bridge": "~2.7",
"symfony/process": "~2.1"
},
"time": "2016-01-18 09:21:03",
"time": "2016-01-18T09:21:03+00:00",
"type": "phantomjs-api",
"extra": {
"branch-alias": {
@ -3268,7 +3268,7 @@
"symfony/phpunit-bridge": "~2.7",
"symfony/process": "~2.3"
},
"time": "2015-12-04 13:55:02",
"time": "2015-12-04T13:55:02+00:00",
"type": "mink-driver",
"extra": {
"branch-alias": {
@ -3324,7 +3324,7 @@
"require-dev": {
"phpunit/phpunit": "~4.5"
},
"time": "2015-10-06 16:59:57",
"time": "2015-10-06T16:59:57+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -3376,7 +3376,7 @@
"dflydev/markdown": "~1.0",
"erusev/parsedown": "~1.0"
},
"time": "2015-02-03 12:10:50",
"time": "2015-02-03T12:10:50+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -3424,7 +3424,7 @@
"require-dev": {
"phpunit/phpunit": "~4.2"
},
"time": "2015-09-15 10:49:45",
"time": "2015-09-15T10:49:45+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -3471,7 +3471,7 @@
"require": {
"php": ">=5.3.9"
},
"time": "2016-03-04 07:54:35",
"time": "2016-03-04T07:54:35+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -3519,7 +3519,7 @@
"reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
"shasum": ""
},
"time": "2015-06-21 13:59:46",
"time": "2015-06-21T13:59:46+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@ -3565,7 +3565,7 @@
"suggest": {
"ext-uopz": "*"
},
"time": "2014-10-06 09:23:50",
"time": "2014-10-06T09:23:50+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -3615,7 +3615,7 @@
"require-dev": {
"phpunit/phpunit": "~4.4"
},
"time": "2015-06-21 08:04:50",
"time": "2015-06-21T08:04:50+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -3671,7 +3671,7 @@
"require-dev": {
"phpunit/phpunit": "~4.4"
},
"time": "2015-06-21 07:55:53",
"time": "2015-06-21T07:55:53+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -3738,7 +3738,7 @@
"require-dev": {
"phpunit/phpunit": "~4.4"
},
"time": "2015-08-03 06:14:51",
"time": "2015-08-03T06:14:51+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -3790,7 +3790,7 @@
"require-dev": {
"phpunit/phpunit": "~4.2"
},
"time": "2015-02-22 15:13:53",
"time": "2015-02-22T15:13:53+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -3846,7 +3846,7 @@
"require-dev": {
"phpunit/phpunit": "~4.4"
},
"time": "2015-07-26 15:48:44",
"time": "2015-07-26T15:48:44+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -3907,7 +3907,7 @@
"require": {
"php": ">=5.3.3"
},
"time": "2015-06-21 13:50:34",
"time": "2015-06-21T13:50:34+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@ -3957,7 +3957,7 @@
"phpunit/phpunit": "~4.0",
"squizlabs/php_codesniffer": "~2.0"
},
"time": "2015-06-14 21:17:01",
"time": "2015-06-14T21:17:01+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -4015,7 +4015,7 @@
"suggest": {
"ext-soap": "*"
},
"time": "2015-10-02 06:51:40",
"time": "2015-10-02T06:51:40+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -4064,7 +4064,7 @@
"require": {
"php": ">=5.3.3"
},
"time": "2015-06-21 08:01:12",
"time": "2015-06-21T08:01:12+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@ -4107,7 +4107,7 @@
"require": {
"php": ">=5.3.3"
},
"time": "2015-06-21 13:08:43",
"time": "2015-06-21T13:08:43+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -4170,7 +4170,7 @@
"ext-xdebug": ">=2.2.1",
"ext-xmlwriter": "*"
},
"time": "2015-10-06 15:47:00",
"time": "2015-10-06T15:47:00+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -4225,7 +4225,7 @@
"require-dev": {
"phpspec/phpspec": "~2.0"
},
"time": "2015-08-13 10:07:40",
"time": "2015-08-13T10:07:40+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -4303,7 +4303,7 @@
"suggest": {
"phpunit/php-invoker": "~1.1"
},
"time": "2016-07-21 06:48:14",
"time": "2016-07-21T06:48:14+00:00",
"bin": [
"phpunit"
],