Issue #2510380 by Wim Leers, DuaelFr, mlewand, quicksketch, Reinmar, oleq: Images cannot be linked in CKEditor
parent
c7a9c9ae10
commit
2d342c0f63
|
@ -65,14 +65,11 @@
|
|||
}
|
||||
widgetDefinition.allowedContent = new CKEDITOR.style(allowedContentDefinition);
|
||||
|
||||
// Override the 'link' part, to completely disable image2's link
|
||||
// support: http://dev.ckeditor.com/ticket/11341.
|
||||
widgetDefinition.parts.link = 'This is a nonsensical selector to disable this functionality completely';
|
||||
|
||||
// Override downcast(): since we only accept <img> in our upcast method,
|
||||
// the element is already correct. We only need to update the element's
|
||||
// data-entity-uuid attribute.
|
||||
widgetDefinition.downcast = function (element) {
|
||||
element.attributes['data-entity-type'] = this.data['data-entity-type'];
|
||||
element.attributes['data-entity-uuid'] = this.data['data-entity-uuid'];
|
||||
};
|
||||
|
||||
|
@ -176,6 +173,18 @@
|
|||
return widget;
|
||||
};
|
||||
};
|
||||
|
||||
var originalInit = widgetDefinition.init;
|
||||
widgetDefinition.init = function () {
|
||||
originalInit.call(this);
|
||||
|
||||
// Update data.link object with attributes if the link has been
|
||||
// discovered.
|
||||
// @see plugins/image2/plugin.js/init() in CKEditor; this is similar.
|
||||
if (this.parts.link) {
|
||||
this.setData('link', CKEDITOR.plugins.link.parseLinkAttributes(editor, this.parts.link));
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// Add a widget#edit listener to every instance of image2 widget in order
|
||||
|
@ -233,25 +242,86 @@
|
|||
}
|
||||
},
|
||||
|
||||
// Disable image2's integration with the link/drupallink plugins: don't
|
||||
// allow the widget itself to become a link. Support for that may be added
|
||||
// by an text filter that adds a data- attribute specifically for that.
|
||||
afterInit: function (editor) {
|
||||
if (editor.plugins.drupallink) {
|
||||
var cmd = editor.getCommand('drupallink');
|
||||
// Needs to be refreshed on selection changes.
|
||||
cmd.contextSensitive = 1;
|
||||
// Disable command and cancel event when the image widget is selected.
|
||||
cmd.on('refresh', function (evt) {
|
||||
var widget = editor.widgets.focused;
|
||||
if (widget && widget.name === 'image') {
|
||||
this.setState(CKEDITOR.TRISTATE_DISABLED);
|
||||
evt.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
linkCommandIntegrator(editor);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
* Integrates the drupalimage widget with the drupallink plugin.
|
||||
*
|
||||
* Makes images linkable.
|
||||
*
|
||||
* @param {CKEDITOR.editor} editor
|
||||
* A CKEditor instance.
|
||||
*/
|
||||
function linkCommandIntegrator(editor) {
|
||||
// Nothing to integrate with if the drupallink plugin is not loaded.
|
||||
if (!editor.plugins.drupallink) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Override default behaviour of 'drupalunlink' command.
|
||||
editor.getCommand('drupalunlink').on('exec', function (evt) {
|
||||
var widget = getFocusedWidget(editor);
|
||||
|
||||
// Override 'drupalunlink' only when link truly belongs to the widget. If
|
||||
// wrapped inline widget in a link, let default unlink work.
|
||||
// @see https://dev.ckeditor.com/ticket/11814
|
||||
if (!widget || !widget.parts.link) {
|
||||
return;
|
||||
}
|
||||
|
||||
widget.setData('link', null);
|
||||
|
||||
// Selection (which is fake) may not change if unlinked image in focused
|
||||
// widget, i.e. if captioned image. Let's refresh command state manually
|
||||
// here.
|
||||
this.refresh(editor, editor.elementPath());
|
||||
|
||||
evt.cancel();
|
||||
});
|
||||
|
||||
// Override default refresh of 'drupalunlink' command.
|
||||
editor.getCommand('drupalunlink').on('refresh', function (evt) {
|
||||
var widget = getFocusedWidget(editor);
|
||||
|
||||
if (!widget) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Note that widget may be wrapped in a link, which
|
||||
// does not belong to that widget (#11814).
|
||||
this.setState(widget.data.link || widget.wrapper.getAscendant('a') ?
|
||||
CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED);
|
||||
|
||||
evt.cancel();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the focused widget, if of the type specific for this plugin.
|
||||
*
|
||||
* @param {CKEDITOR.editor} editor
|
||||
* A CKEditor instance.
|
||||
*
|
||||
* @return {?CKEDITOR.plugins.widget}
|
||||
* The focused image2 widget instance, or null.
|
||||
*/
|
||||
function getFocusedWidget(editor) {
|
||||
var widget = editor.widgets.focused;
|
||||
|
||||
if (widget && widget.name === 'image') {
|
||||
return widget;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Expose an API for other plugins to interact with drupalimage widgets.
|
||||
CKEDITOR.plugins.drupalimage = {
|
||||
getFocusedWidget: getFocusedWidget
|
||||
};
|
||||
|
||||
})(jQuery, Drupal, CKEDITOR);
|
||||
|
|
|
@ -71,10 +71,9 @@
|
|||
// data-caption attributes.
|
||||
var originalDowncast = widgetDefinition.downcast;
|
||||
widgetDefinition.downcast = function (element) {
|
||||
var img = originalDowncast.call(this, element);
|
||||
if (!img) {
|
||||
img = findElementByName(element, 'img');
|
||||
}
|
||||
var img = findElementByName(element, 'img');
|
||||
originalDowncast.call(this, img);
|
||||
|
||||
var caption = this.editables.caption;
|
||||
var captionHtml = caption && caption.getData();
|
||||
var attrs = img.attributes;
|
||||
|
@ -91,10 +90,14 @@
|
|||
attrs['data-align'] = this.data.align;
|
||||
}
|
||||
}
|
||||
attrs['data-entity-type'] = this.data['data-entity-type'];
|
||||
attrs['data-entity-uuid'] = this.data['data-entity-uuid'];
|
||||
|
||||
return img;
|
||||
// If img is wrapped with a link, we want to return that link.
|
||||
if (img.parent.name === 'a') {
|
||||
return img.parent;
|
||||
}
|
||||
else {
|
||||
return img;
|
||||
}
|
||||
};
|
||||
|
||||
// We want to upcast <img> elements to a DOM structure required by the
|
||||
|
@ -115,6 +118,11 @@
|
|||
|
||||
element = originalUpcast.call(this, element, data);
|
||||
var attrs = element.attributes;
|
||||
|
||||
if (element.parent.name === 'a') {
|
||||
element = element.parent;
|
||||
}
|
||||
|
||||
var retElement = element;
|
||||
var caption;
|
||||
|
||||
|
|
|
@ -31,6 +31,8 @@
|
|||
modes: {wysiwyg: 1},
|
||||
canUndo: true,
|
||||
exec: function (editor) {
|
||||
var drupalImageUtils = CKEDITOR.plugins.drupalimage;
|
||||
var focusedImageWidget = drupalImageUtils && drupalImageUtils.getFocusedWidget(editor);
|
||||
var linkElement = getSelectedLink(editor);
|
||||
var linkDOMElement = null;
|
||||
|
||||
|
@ -56,9 +58,30 @@
|
|||
existingValues[attributeName] = linkElement.data('cke-saved-' + attributeName) || attribute.nodeValue;
|
||||
}
|
||||
}
|
||||
// Or, if an image widget is focused, we're editing a link wrapping
|
||||
// an image widget.
|
||||
else if (focusedImageWidget && focusedImageWidget.data.link) {
|
||||
var url = focusedImageWidget.data.link.url;
|
||||
existingValues.href = url.protocol + url.url;
|
||||
}
|
||||
|
||||
// Prepare a save callback to be used upon saving the dialog.
|
||||
var saveCallback = function (returnValues) {
|
||||
// If an image widget is focused, we're not editing an independent
|
||||
// link, but we're wrapping an image widget in a link.
|
||||
if (focusedImageWidget) {
|
||||
var urlMatch = returnValues.attributes.href.match(urlRegex);
|
||||
focusedImageWidget.setData('link', {
|
||||
type: 'url',
|
||||
url: {
|
||||
protocol: urlMatch[1],
|
||||
url: urlMatch[2]
|
||||
}
|
||||
});
|
||||
editor.fire('saveSnapshot');
|
||||
return;
|
||||
}
|
||||
|
||||
editor.fire('saveSnapshot');
|
||||
|
||||
// Create a new link element if needed.
|
||||
|
@ -256,4 +279,57 @@
|
|||
return null;
|
||||
}
|
||||
|
||||
var urlRegex = /^((?:http|https):\/\/)?(.*)$/;
|
||||
|
||||
/**
|
||||
* The image2 plugin is currently tightly coupled to the link plugin: it
|
||||
* calls CKEDITOR.plugins.link.parseLinkAttributes().
|
||||
*
|
||||
* Drupal 8's CKEditor build doesn't include the 'link' plugin. Because it
|
||||
* includes its own link plugin that integrates with Drupal's dialog system.
|
||||
* So, to allow images to be linked, we need to duplicate the necessary subset
|
||||
* of the logic.
|
||||
*
|
||||
* @todo Remove once we update to CKEditor 4.5.5.
|
||||
* @see https://dev.ckeditor.com/ticket/13885
|
||||
*/
|
||||
CKEDITOR.plugins.link = CKEDITOR.plugins.link || {
|
||||
parseLinkAttributes: function (editor, element) {
|
||||
var href = (element && (element.data('cke-saved-href') || element.getAttribute('href'))) || '';
|
||||
var urlMatch = href.match(urlRegex);
|
||||
return {
|
||||
type: 'url',
|
||||
url: {
|
||||
protocol: urlMatch[1],
|
||||
url: urlMatch[2]
|
||||
}
|
||||
};
|
||||
},
|
||||
getLinkAttributes: function (editor, data) {
|
||||
var set = {};
|
||||
|
||||
var protocol = (data.url && typeof data.url.protocol !== 'undefined') ? data.url.protocol : 'http://';
|
||||
var url = (data.url && CKEDITOR.tools.trim(data.url.url)) || '';
|
||||
set['data-cke-saved-href'] = (url.indexOf('/') === 0) ? url : protocol + url;
|
||||
|
||||
// Browser need the "href" fro copy/paste link to work. (#6641)
|
||||
if (set['data-cke-saved-href']) {
|
||||
set.href = set['data-cke-saved-href'];
|
||||
}
|
||||
|
||||
// Remove all attributes which are not currently set.
|
||||
var removed = {};
|
||||
for (var s in set) {
|
||||
if (set.hasOwnProperty(s)) {
|
||||
delete removed[s];
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
set: set,
|
||||
removed: CKEDITOR.tools.objectKeys(removed)
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
})(jQuery, Drupal, drupalSettings, CKEDITOR);
|
||||
|
|
|
@ -55,15 +55,17 @@ class FilterCaption extends FilterBase {
|
|||
// Given the updated node and caption: re-render it with a caption, but
|
||||
// bubble up the value of the class attribute of the captioned element,
|
||||
// this allows it to collaborate with e.g. the filter_align filter.
|
||||
$tag = $node->tagName;
|
||||
$classes = $node->getAttribute('class');
|
||||
$node->removeAttribute('class');
|
||||
$node = ($node->parentNode->tagName === 'a') ? $node->parentNode : $node;
|
||||
$filter_caption = array(
|
||||
'#theme' => 'filter_caption',
|
||||
// We pass the unsanitized string because this is a text format
|
||||
// filter, and after filtering, we always assume the output is safe.
|
||||
// @see \Drupal\filter\Element\ProcessedText::preRenderText()
|
||||
'#node' => FilteredMarkup::create($node->C14N()),
|
||||
'#tag' => $node->tagName,
|
||||
'#tag' => $tag,
|
||||
'#caption' => $caption,
|
||||
'#classes' => $classes,
|
||||
);
|
||||
|
@ -78,7 +80,7 @@ class FilterCaption extends FilterBase {
|
|||
// Import the updated node from the new DOMDocument into the original
|
||||
// one, importing also the child nodes of the updated node.
|
||||
$updated_node = $dom->importNode($updated_node, TRUE);
|
||||
// Finally, replace the original image node with the new image node!
|
||||
// Finally, replace the original node with the new node.
|
||||
$node->parentNode->replaceChild($updated_node, $node);
|
||||
}
|
||||
|
||||
|
|
|
@ -184,6 +184,13 @@ class FilterUnitTest extends KernelTestBase {
|
|||
$this->assertIdentical($expected, $output->getProcessedText());
|
||||
$this->assertIdentical($attached_library, $output->getAttachments());
|
||||
|
||||
// Ensure the caption filter works for linked images.
|
||||
$input = '<a href="http://example.com/llamas/are/awesome/but/kittens/are/cool/too"><img src="llama.jpg" data-caption="Loquacious llama!" /></a>';
|
||||
$expected = '<figure role="group"><a href="http://example.com/llamas/are/awesome/but/kittens/are/cool/too"><img src="llama.jpg" /></a>' . "\n" . '<figcaption>Loquacious llama!</figcaption></figure>';
|
||||
$output = $test($input);
|
||||
$this->assertIdentical($expected, $output->getProcessedText());
|
||||
$this->assertIdentical($attached_library, $output->getAttachments());
|
||||
|
||||
// So far we've tested that the caption filter works correctly. But we also
|
||||
// want to make sure that it works well in tandem with the "Limit allowed
|
||||
// HTML tags" filter, which it is typically used with.
|
||||
|
@ -301,6 +308,13 @@ class FilterUnitTest extends KernelTestBase {
|
|||
$output = $test($input);
|
||||
$this->assertIdentical($expected, $output->getProcessedText());
|
||||
$this->assertIdentical($attached_library, $output->getAttachments());
|
||||
|
||||
// Ensure both filters together work for linked images.
|
||||
$input = '<a href="http://example.com/llamas/are/awesome/but/kittens/are/cool/too"><img src="llama.jpg" data-caption="Loquacious llama!" data-align="center" /></a>';
|
||||
$expected = '<figure role="group" class="align-center"><a href="http://example.com/llamas/are/awesome/but/kittens/are/cool/too"><img src="llama.jpg" /></a>' . "\n" . '<figcaption>Loquacious llama!</figcaption></figure>';
|
||||
$output = $test($input);
|
||||
$this->assertIdentical($expected, $output->getProcessedText());
|
||||
$this->assertIdentical($attached_library, $output->getAttachments());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -170,17 +170,20 @@ function simpletest_run_tests($test_list) {
|
|||
* @param $unescaped_test_classnames
|
||||
* An array of test class names, including full namespaces, to be passed as
|
||||
* a regular expression to PHPUnit's --filter option.
|
||||
* @param int $status
|
||||
* (optional) The exit status code of the PHPUnit process will be assigned to
|
||||
* this variable.
|
||||
*
|
||||
* @return array
|
||||
* The parsed results of PHPUnit's JUnit XML output, in the format of
|
||||
* {simpletest}'s schema.
|
||||
*/
|
||||
function simpletest_run_phpunit_tests($test_id, array $unescaped_test_classnames) {
|
||||
function simpletest_run_phpunit_tests($test_id, array $unescaped_test_classnames, &$status = NULL) {
|
||||
$phpunit_file = simpletest_phpunit_xml_filepath($test_id);
|
||||
$ret = simpletest_phpunit_run_command($unescaped_test_classnames, $phpunit_file);
|
||||
// A return value of 0 = passed test, 1 = failed test, > 1 indicates segfault
|
||||
simpletest_phpunit_run_command($unescaped_test_classnames, $phpunit_file, $status);
|
||||
// A $status of 0 = passed test, 1 = failed test, > 1 indicates segfault
|
||||
// timeout, or other type of failure.
|
||||
if ($ret > 1) {
|
||||
if ($status > 1) {
|
||||
// Something broke during the execution of phpunit.
|
||||
// Return an error record of all failed classes.
|
||||
$rows[] = [
|
||||
|
@ -251,11 +254,14 @@ function simpletest_phpunit_configuration_filepath() {
|
|||
* a regular expression to PHPUnit's --filter option.
|
||||
* @param string $phpunit_file
|
||||
* A filepath to use for PHPUnit's --log-junit option.
|
||||
* @param int $status
|
||||
* (optional) The exit status code of the PHPUnit process will be assigned to
|
||||
* this variable.
|
||||
*
|
||||
* @return string
|
||||
* The results as returned by exec().
|
||||
*/
|
||||
function simpletest_phpunit_run_command(array $unescaped_test_classnames, $phpunit_file) {
|
||||
function simpletest_phpunit_run_command(array $unescaped_test_classnames, $phpunit_file, &$status = NULL) {
|
||||
// Setup an environment variable containing the database connection so that
|
||||
// functional tests can connect to the database.
|
||||
putenv('SIMPLETEST_DB=' . Database::getConnectionInfoAsUrl());
|
||||
|
@ -292,7 +298,8 @@ function simpletest_phpunit_run_command(array $unescaped_test_classnames, $phpun
|
|||
|
||||
// exec in a subshell so that the environment is isolated when running tests
|
||||
// via the simpletest UI.
|
||||
exec(join($command, " "), $output, $ret);
|
||||
$ret = exec(join($command, " "), $output, $status);
|
||||
|
||||
chdir($old_cwd);
|
||||
putenv('SIMPLETEST_DB=');
|
||||
return $ret;
|
||||
|
|
|
@ -23,46 +23,68 @@ const SIMPLETEST_SCRIPT_COLOR_EXCEPTION = 33; // Brown.
|
|||
// Restricting the chunk of queries prevents memory exhaustion.
|
||||
const SIMPLETEST_SCRIPT_SQLITE_VARIABLE_LIMIT = 350;
|
||||
|
||||
const SIMPLETEST_SCRIPT_EXIT_SUCCESS = 0;
|
||||
const SIMPLETEST_SCRIPT_EXIT_FAILURE = 1;
|
||||
const SIMPLETEST_SCRIPT_EXIT_EXCEPTION = 2;
|
||||
|
||||
// Set defaults and get overrides.
|
||||
list($args, $count) = simpletest_script_parse_args();
|
||||
|
||||
if ($args['help'] || $count == 0) {
|
||||
simpletest_script_help();
|
||||
exit;
|
||||
exit(($count == 0) ? SIMPLETEST_SCRIPT_EXIT_FAILURE : SIMPLETEST_SCRIPT_EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
simpletest_script_init();
|
||||
|
||||
$request = Request::createFromGlobals();
|
||||
$kernel = TestRunnerKernel::createFromRequest($request, $autoloader);
|
||||
$kernel->prepareLegacyRequest($request);
|
||||
try {
|
||||
$request = Request::createFromGlobals();
|
||||
$kernel = TestRunnerKernel::createFromRequest($request, $autoloader);
|
||||
$kernel->prepareLegacyRequest($request);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
echo (string) $e;
|
||||
exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
|
||||
}
|
||||
|
||||
if ($args['execute-test']) {
|
||||
simpletest_script_setup_database();
|
||||
simpletest_script_run_one_test($args['test-id'], $args['execute-test']);
|
||||
// Sub-process exited already; this is just for clarity.
|
||||
exit;
|
||||
exit(SIMPLETEST_SCRIPT_EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
if ($args['list']) {
|
||||
// Display all available tests.
|
||||
echo "\nAvailable test groups & classes\n";
|
||||
echo "-------------------------------\n\n";
|
||||
$groups = simpletest_test_get_all($args['module']);
|
||||
try {
|
||||
$groups = simpletest_test_get_all($args['module']);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
echo (string) $e;
|
||||
exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
|
||||
}
|
||||
foreach ($groups as $group => $tests) {
|
||||
echo $group . "\n";
|
||||
foreach ($tests as $class => $info) {
|
||||
echo " - $class\n";
|
||||
}
|
||||
}
|
||||
exit;
|
||||
exit(SIMPLETEST_SCRIPT_EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
simpletest_script_setup_database(TRUE);
|
||||
|
||||
if ($args['clean']) {
|
||||
// Clean up left-over tables and directories.
|
||||
simpletest_clean_environment();
|
||||
try {
|
||||
simpletest_clean_environment();
|
||||
}
|
||||
catch (Exception $e) {
|
||||
echo (string) $e;
|
||||
exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
|
||||
}
|
||||
echo "\nEnvironment cleaned.\n";
|
||||
|
||||
// Get the status messages and print them.
|
||||
|
@ -70,7 +92,7 @@ if ($args['clean']) {
|
|||
foreach ($messages['status'] as $text) {
|
||||
echo " - " . $text . "\n";
|
||||
}
|
||||
exit;
|
||||
exit(SIMPLETEST_SCRIPT_EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
$test_list = simpletest_script_get_test_list();
|
||||
|
@ -85,7 +107,7 @@ for ($i = 0; $i < $args['repeat']; $i++) {
|
|||
}
|
||||
|
||||
// Execute tests.
|
||||
simpletest_script_execute_batch($tests_to_run);
|
||||
$status = simpletest_script_execute_batch($tests_to_run);
|
||||
|
||||
// Stop the timer.
|
||||
simpletest_script_reporter_timer_stop();
|
||||
|
@ -104,11 +126,17 @@ if ($args['xml']) {
|
|||
|
||||
// Clean up all test results.
|
||||
if (!$args['keep-results']) {
|
||||
simpletest_clean_results_table();
|
||||
try {
|
||||
simpletest_clean_results_table();
|
||||
}
|
||||
catch (Exception $e) {
|
||||
echo (string) $e;
|
||||
exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
|
||||
}
|
||||
}
|
||||
|
||||
// Test complete, exit.
|
||||
exit;
|
||||
exit($status);
|
||||
|
||||
/**
|
||||
* Print help text.
|
||||
|
@ -295,7 +323,7 @@ function simpletest_script_parse_args() {
|
|||
else {
|
||||
// Argument not found in list.
|
||||
simpletest_script_print_error("Unknown argument '$arg'.");
|
||||
exit;
|
||||
exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -308,7 +336,7 @@ function simpletest_script_parse_args() {
|
|||
// Validate the concurrency argument
|
||||
if (!is_numeric($args['concurrency']) || $args['concurrency'] <= 0) {
|
||||
simpletest_script_print_error("--concurrency must be a strictly positive integer.");
|
||||
exit;
|
||||
exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if ($args['browser']) {
|
||||
|
@ -343,7 +371,7 @@ function simpletest_script_init() {
|
|||
else {
|
||||
simpletest_script_print_error('Unable to automatically determine the path to the PHP interpreter. Supply the --php command line argument.');
|
||||
simpletest_script_help();
|
||||
exit();
|
||||
exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Get URL from arguments.
|
||||
|
@ -441,7 +469,7 @@ function simpletest_script_setup_database($new = FALSE) {
|
|||
}
|
||||
catch (\InvalidArgumentException $e) {
|
||||
simpletest_script_print_error('Invalid --dburl. Reason: ' . $e->getMessage());
|
||||
exit(1);
|
||||
exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
// Otherwise, use the default database connection from settings.php.
|
||||
|
@ -452,7 +480,7 @@ function simpletest_script_setup_database($new = FALSE) {
|
|||
// If there is no default database connection for tests, we cannot continue.
|
||||
if (!isset($databases['default']['default'])) {
|
||||
simpletest_script_print_error('Missing default database connection for tests. Use --dburl to specify one.');
|
||||
exit(1);
|
||||
exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
|
||||
}
|
||||
Database::addConnectionInfo('default', 'default', $databases['default']['default']);
|
||||
|
||||
|
@ -496,21 +524,33 @@ function simpletest_script_setup_database($new = FALSE) {
|
|||
}
|
||||
catch (\PDOException $e) {
|
||||
simpletest_script_print_error($databases['test-runner']['default']['driver'] . ': ' . $e->getMessage());
|
||||
exit(1);
|
||||
exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
|
||||
}
|
||||
if ($new && $sqlite) {
|
||||
require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'simpletest') . '/simpletest.install';
|
||||
foreach (simpletest_schema() as $name => $table_spec) {
|
||||
if ($schema->tableExists($name)) {
|
||||
$schema->dropTable($name);
|
||||
try {
|
||||
if ($schema->tableExists($name)) {
|
||||
$schema->dropTable($name);
|
||||
}
|
||||
$schema->createTable($name, $table_spec);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
echo (string) $e;
|
||||
exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
|
||||
}
|
||||
$schema->createTable($name, $table_spec);
|
||||
}
|
||||
}
|
||||
// Verify that the Simpletest database schema exists by checking one table.
|
||||
if (!$schema->tableExists('simpletest')) {
|
||||
simpletest_script_print_error('Missing Simpletest database schema. Either install Simpletest module or use the --sqlite parameter.');
|
||||
exit(1);
|
||||
try {
|
||||
if (!$schema->tableExists('simpletest')) {
|
||||
simpletest_script_print_error('Missing Simpletest database schema. Either install Simpletest module or use the --sqlite parameter.');
|
||||
exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
catch (Exception $e) {
|
||||
echo (string) $e;
|
||||
exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -520,6 +560,8 @@ function simpletest_script_setup_database($new = FALSE) {
|
|||
function simpletest_script_execute_batch($test_classes) {
|
||||
global $args, $test_ids;
|
||||
|
||||
$total_status = SIMPLETEST_SCRIPT_EXIT_SUCCESS;
|
||||
|
||||
// Multi-process execution.
|
||||
$children = array();
|
||||
while (!empty($test_classes) || !empty($children)) {
|
||||
|
@ -528,8 +570,16 @@ function simpletest_script_execute_batch($test_classes) {
|
|||
break;
|
||||
}
|
||||
|
||||
$test_id = Database::getConnection('default', 'test-runner')
|
||||
->insert('simpletest_test_id')->useDefaults(array('test_id'))->execute();
|
||||
try {
|
||||
$test_id = Database::getConnection('default', 'test-runner')
|
||||
->insert('simpletest_test_id')
|
||||
->useDefaults(array('test_id'))
|
||||
->execute();
|
||||
}
|
||||
catch (Exception $e) {
|
||||
echo (string) $e;
|
||||
exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
|
||||
}
|
||||
$test_ids[] = $test_id;
|
||||
|
||||
$test_class = array_shift($test_classes);
|
||||
|
@ -539,7 +589,7 @@ function simpletest_script_execute_batch($test_classes) {
|
|||
|
||||
if (!is_resource($process)) {
|
||||
echo "Unable to fork test process. Aborting.\n";
|
||||
exit;
|
||||
exit(SIMPLETEST_SCRIPT_EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
// Register our new child.
|
||||
|
@ -560,7 +610,11 @@ function simpletest_script_execute_batch($test_classes) {
|
|||
if (empty($status['running'])) {
|
||||
// The child exited, unregister it.
|
||||
proc_close($child['process']);
|
||||
if ($status['exitcode']) {
|
||||
if ($status['exitcode'] === SIMPLETEST_SCRIPT_EXIT_FAILURE) {
|
||||
$total_status = max($status['exitcode'], $total_status);
|
||||
}
|
||||
elseif ($status['exitcode']) {
|
||||
$total_status = $status['exitcode'];
|
||||
echo 'FATAL ' . $child['class'] . ': test runner returned a non-zero error code (' . $status['exitcode'] . ').' . "\n";
|
||||
if ($args['die-on-fail']) {
|
||||
list($db_prefix, ) = simpletest_last_test_get($child['test_id']);
|
||||
|
@ -581,19 +635,19 @@ function simpletest_script_execute_batch($test_classes) {
|
|||
}
|
||||
}
|
||||
}
|
||||
return $total_status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a group of phpunit tests.
|
||||
*/
|
||||
function simpletest_script_run_phpunit($test_id, $class) {
|
||||
|
||||
$reflection = new \ReflectionClass($class);
|
||||
if ($reflection->hasProperty('runLimit')) {
|
||||
set_time_limit($reflection->getStaticPropertyValue('runLimit'));
|
||||
}
|
||||
|
||||
$results = simpletest_run_phpunit_tests($test_id, array($class));
|
||||
$results = simpletest_run_phpunit_tests($test_id, array($class), $status);
|
||||
simpletest_process_phpunit_results($results);
|
||||
|
||||
// Map phpunit results to a data structure we can pass to
|
||||
|
@ -628,6 +682,7 @@ function simpletest_script_run_phpunit($test_id, $class) {
|
|||
foreach ($summaries as $class => $summary) {
|
||||
simpletest_script_reporter_display_summary($class, $summary);
|
||||
}
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -648,23 +703,28 @@ function simpletest_script_run_one_test($test_id, $test_class) {
|
|||
}
|
||||
$test = new $class_name($test_id);
|
||||
if (is_subclass_of($test_class, '\PHPUnit_Framework_TestCase')) {
|
||||
simpletest_script_run_phpunit($test_id, $test_class);
|
||||
$status = simpletest_script_run_phpunit($test_id, $test_class);
|
||||
}
|
||||
else {
|
||||
$test->dieOnFail = (bool) $args['die-on-fail'];
|
||||
$test->verbose = (bool) $args['verbose'];
|
||||
$test->run($methods);
|
||||
simpletest_script_reporter_display_summary($test_class, $test->results);
|
||||
|
||||
$status = SIMPLETEST_SCRIPT_EXIT_SUCCESS;
|
||||
// Finished, kill this runner.
|
||||
if ($test->results['#fail'] || $test->results['#exception']) {
|
||||
$status = SIMPLETEST_SCRIPT_EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
// Finished, kill this runner.
|
||||
exit(0);
|
||||
exit($status);
|
||||
}
|
||||
// DrupalTestCase::run() catches exceptions already, so this is only reached
|
||||
// when an exception is thrown in the wrapping test runner environment.
|
||||
catch (Exception $e) {
|
||||
echo (string) $e;
|
||||
exit(1);
|
||||
exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -726,7 +786,13 @@ function simpletest_script_cleanup($test_id, $test_class, $exitcode) {
|
|||
return;
|
||||
}
|
||||
// Retrieve the last database prefix used for testing.
|
||||
list($db_prefix, ) = simpletest_last_test_get($test_id);
|
||||
try {
|
||||
list($db_prefix,) = simpletest_last_test_get($test_id);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
echo (string) $e;
|
||||
exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
|
||||
}
|
||||
|
||||
// If no database prefix was found, then the test was not set up correctly.
|
||||
if (empty($db_prefix)) {
|
||||
|
@ -741,7 +807,13 @@ function simpletest_script_cleanup($test_id, $test_class, $exitcode) {
|
|||
$messages[] = "- Found database prefix '$db_prefix' for test ID $test_id.";
|
||||
|
||||
// Read the log file in case any fatal errors caused the test to crash.
|
||||
simpletest_log_read($test_id, $db_prefix, $test_class);
|
||||
try {
|
||||
simpletest_log_read($test_id, $db_prefix, $test_class);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
echo (string) $e;
|
||||
exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
|
||||
}
|
||||
|
||||
// Check whether a test site directory was setup already.
|
||||
// @see \Drupal\simpletest\TestBase::prepareEnvironment()
|
||||
|
@ -763,12 +835,19 @@ function simpletest_script_cleanup($test_id, $test_class, $exitcode) {
|
|||
}
|
||||
|
||||
// Clear out all database tables from the test.
|
||||
$schema = Database::getConnection('default', 'default')->schema();
|
||||
$count = 0;
|
||||
foreach ($schema->findTables($db_prefix . '%') as $table) {
|
||||
$schema->dropTable($table);
|
||||
$count++;
|
||||
try {
|
||||
$schema = Database::getConnection('default', 'default')->schema();
|
||||
$count = 0;
|
||||
foreach ($schema->findTables($db_prefix . '%') as $table) {
|
||||
$schema->dropTable($table);
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
catch (Exception $e) {
|
||||
echo (string) $e;
|
||||
exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
|
||||
}
|
||||
|
||||
if ($count) {
|
||||
$messages[] = "- Removed $count leftover tables.";
|
||||
}
|
||||
|
@ -792,7 +871,13 @@ function simpletest_script_get_test_list() {
|
|||
|
||||
$test_list = array();
|
||||
if ($args['all'] || $args['module']) {
|
||||
$groups = simpletest_test_get_all($args['module']);
|
||||
try {
|
||||
$groups = simpletest_test_get_all($args['module']);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
echo (string) $e;
|
||||
exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
|
||||
}
|
||||
$all_tests = array();
|
||||
foreach ($groups as $group => $tests) {
|
||||
$all_tests = array_merge($all_tests, array_keys($tests));
|
||||
|
@ -808,14 +893,20 @@ function simpletest_script_get_test_list() {
|
|||
$test_list[] = $test_class;
|
||||
}
|
||||
else {
|
||||
$groups = simpletest_test_get_all();
|
||||
try {
|
||||
$groups = simpletest_test_get_all();
|
||||
}
|
||||
catch (Exception $e) {
|
||||
echo (string) $e;
|
||||
exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
|
||||
}
|
||||
$all_classes = array();
|
||||
foreach ($groups as $group) {
|
||||
$all_classes = array_merge($all_classes, array_keys($group));
|
||||
}
|
||||
simpletest_script_print_error('Test class not found: ' . $class_name);
|
||||
simpletest_script_print_alternatives($class_name, $all_classes, 6);
|
||||
exit(1);
|
||||
exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -824,7 +915,7 @@ function simpletest_script_get_test_list() {
|
|||
foreach ($args['test_names'] as $file) {
|
||||
if (!file_exists($file)) {
|
||||
simpletest_script_print_error('File not found: ' . $file);
|
||||
exit;
|
||||
exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
|
||||
}
|
||||
$content = file_get_contents($file);
|
||||
// Extract a potential namespace.
|
||||
|
@ -903,7 +994,13 @@ function simpletest_script_get_test_list() {
|
|||
}
|
||||
}
|
||||
else {
|
||||
$groups = simpletest_test_get_all();
|
||||
try {
|
||||
$groups = simpletest_test_get_all();
|
||||
}
|
||||
catch (Exception $e) {
|
||||
echo (string) $e;
|
||||
exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
|
||||
}
|
||||
foreach ($args['test_names'] as $group_name) {
|
||||
if (isset($groups[$group_name])) {
|
||||
$test_list = array_merge($test_list, array_keys($groups[$group_name]));
|
||||
|
@ -911,7 +1008,7 @@ function simpletest_script_get_test_list() {
|
|||
else {
|
||||
simpletest_script_print_error('Test group not found: ' . $group_name);
|
||||
simpletest_script_print_alternatives($group_name, array_keys($groups));
|
||||
exit(1);
|
||||
exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -919,7 +1016,7 @@ function simpletest_script_get_test_list() {
|
|||
|
||||
if (empty($test_list)) {
|
||||
simpletest_script_print_error('No valid tests were specified.');
|
||||
exit;
|
||||
exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
|
||||
}
|
||||
return $test_list;
|
||||
}
|
||||
|
@ -993,7 +1090,13 @@ function simpletest_script_reporter_display_summary($class, $results) {
|
|||
function simpletest_script_reporter_write_xml_results() {
|
||||
global $args, $test_ids, $results_map;
|
||||
|
||||
$results = simpletest_script_load_messages_by_test_id($test_ids);
|
||||
try {
|
||||
$results = simpletest_script_load_messages_by_test_id($test_ids);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
echo (string) $e;
|
||||
exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
|
||||
}
|
||||
|
||||
$test_class = '';
|
||||
$xml_files = array();
|
||||
|
@ -1083,7 +1186,13 @@ function simpletest_script_reporter_display_results() {
|
|||
echo "Detailed test results\n";
|
||||
echo "---------------------\n";
|
||||
|
||||
$results = simpletest_script_load_messages_by_test_id($test_ids);
|
||||
try {
|
||||
$results = simpletest_script_load_messages_by_test_id($test_ids);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
echo (string) $e;
|
||||
exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
|
||||
}
|
||||
$test_class = '';
|
||||
foreach ($results as $result) {
|
||||
if (isset($results_map[$result->status])) {
|
||||
|
@ -1231,10 +1340,16 @@ function simpletest_script_load_messages_by_test_id($test_ids) {
|
|||
}
|
||||
|
||||
foreach ($test_id_chunks as $test_id_chunk) {
|
||||
$result_chunk = Database::getConnection('default', 'test-runner')
|
||||
->query("SELECT * FROM {simpletest} WHERE test_id IN ( :test_ids[] ) ORDER BY test_class, message_id", array(
|
||||
':test_ids[]' => $test_id_chunk,
|
||||
))->fetchAll();
|
||||
try {
|
||||
$result_chunk = Database::getConnection('default', 'test-runner')
|
||||
->query("SELECT * FROM {simpletest} WHERE test_id IN ( :test_ids[] ) ORDER BY test_class, message_id", array(
|
||||
':test_ids[]' => $test_id_chunk,
|
||||
))->fetchAll();
|
||||
}
|
||||
catch (Exception $e) {
|
||||
echo (string) $e;
|
||||
exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
|
||||
}
|
||||
if ($result_chunk) {
|
||||
$results = array_merge($results, $result_chunk);
|
||||
}
|
||||
|
@ -1249,14 +1364,20 @@ function simpletest_script_load_messages_by_test_id($test_ids) {
|
|||
function simpletest_script_open_browser() {
|
||||
global $test_ids;
|
||||
|
||||
$connection = Database::getConnection('default', 'test-runner');
|
||||
$results = $connection->select('simpletest')
|
||||
->fields('simpletest')
|
||||
->condition('test_id', $test_ids, 'IN')
|
||||
->orderBy('test_class')
|
||||
->orderBy('message_id')
|
||||
->execute()
|
||||
->fetchAll();
|
||||
try {
|
||||
$connection = Database::getConnection('default', 'test-runner');
|
||||
$results = $connection->select('simpletest')
|
||||
->fields('simpletest')
|
||||
->condition('test_id', $test_ids, 'IN')
|
||||
->orderBy('test_class')
|
||||
->orderBy('message_id')
|
||||
->execute()
|
||||
->fetchAll();
|
||||
}
|
||||
catch (Exception $e) {
|
||||
echo (string) $e;
|
||||
exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
|
||||
}
|
||||
|
||||
// Get the results form.
|
||||
$form = array();
|
||||
|
|
Loading…
Reference in New Issue