From 87cfde30b6d18fd3303e001c345d03b52c87c5cd Mon Sep 17 00:00:00 2001 From: Brian Weaver <weaver.299@osu.edu> Date: Wed, 19 Feb 2020 13:46:55 -0500 Subject: [PATCH] Update scheduler to 1.1 --- composer.json | 2 +- composer.lock | 22 +- vendor/composer/installed.json | 24 +- web/modules/scheduler/.travis.yml | 46 +- web/modules/scheduler/composer.json | 67 +-- .../config/install/scheduler.settings.yml | 1 + ...views.view.scheduler_scheduled_content.yml | 455 ++++++++++++++---- .../config/schema/scheduler.schema.yml | 6 + web/modules/scheduler/drupalci.yml | 12 + web/modules/scheduler/drush.services.yml | 8 + web/modules/scheduler/scheduler.admin.inc | 17 +- web/modules/scheduler/scheduler.api.php | 120 ++++- web/modules/scheduler/scheduler.drush.inc | 6 +- web/modules/scheduler/scheduler.info.yml | 10 +- web/modules/scheduler/scheduler.install | 6 +- web/modules/scheduler/scheduler.module | 87 +++- web/modules/scheduler/scheduler.services.yml | 9 +- ...uler_handler_field_scheduler_countdown.inc | 157 ------ .../scheduler_rules_integration/composer.json | 10 + .../scheduler_rules_integration.info.yml | 9 +- ...stingNodeIsScheduledForPublishingEvent.php | 2 +- ...ingNodeIsScheduledForUnpublishingEvent.php | 2 +- .../NewNodeIsScheduledForPublishingEvent.php | 2 +- ...NewNodeIsScheduledForUnpublishingEvent.php | 2 +- .../SchedulerHasPublishedThisNodeEvent.php | 2 +- .../SchedulerHasUnpublishedThisNodeEvent.php | 2 +- .../src/Plugin/RulesAction/PublishNow.php | 2 +- .../RulesAction/RemovePublishingDate.php | 2 +- .../RulesAction/RemoveUnpublishingDate.php | 2 +- .../Plugin/RulesAction/SetPublishingDate.php | 2 +- .../RulesAction/SetUnpublishingDate.php | 2 +- .../src/Plugin/RulesAction/UnpublishNow.php | 2 +- .../src/Commands/SchedulerCommands.php | 64 +++ .../Controller/LightweightCronController.php | 42 +- .../scheduler/src/Form/SchedulerAdminForm.php | 2 +- .../scheduler/src/Form/SchedulerCronForm.php | 22 +- .../SchedulerPublishOnConstraintValidator.php | 2 +- ...chedulerUnpublishOnConstraintValidator.php | 2 +- web/modules/scheduler/src/SchedulerEvent.php | 2 +- .../scheduler/src/SchedulerManager.php | 294 +++++++---- .../scheduler_access_test.info.yml | 9 +- .../scheduler_api_test.info.yml | 9 +- .../scheduler_api_test.install | 2 +- .../scheduler_api_test.module | 101 +++- .../Functional/SchedulerAdminSettingsTest.php | 8 +- .../tests/src/Functional/SchedulerApiTest.php | 130 ++++- .../src/Functional/SchedulerBasicTest.php | 14 +- .../Functional/SchedulerBrowserTestBase.php | 40 +- .../Functional/SchedulerDefaultTimeTest.php | 58 ++- .../Functional/SchedulerDeleteNodeTest.php | 18 +- .../Functional/SchedulerDevelGenerateTest.php | 20 +- .../Functional/SchedulerFieldsDisplayTest.php | 98 +++- .../SchedulerLightweightCronTest.php | 8 +- .../SchedulerMetaInformationTest.php | 6 +- .../Functional/SchedulerMultilingualTest.php | 24 +- .../Functional/SchedulerNodeAccessTest.php | 2 +- .../SchedulerNonEnabledTypeTest.php | 14 +- .../src/Functional/SchedulerPastDatesTest.php | 89 ++-- .../Functional/SchedulerPermissionsTest.php | 70 ++- .../src/Functional/SchedulerRequiredTest.php | 10 +- .../Functional/SchedulerRevisioningTest.php | 56 +-- .../Functional/SchedulerRulesActionsTest.php | 19 +- .../SchedulerRulesConditionsTest.php | 17 +- .../Functional/SchedulerRulesEventsTest.php | 25 +- .../Functional/SchedulerStatusReportTest.php | 10 +- .../Functional/SchedulerTokenReplaceTest.php | 14 +- .../Functional/SchedulerValidationTest.php | 26 +- 67 files changed, 1676 insertions(+), 749 deletions(-) create mode 100644 web/modules/scheduler/drupalci.yml create mode 100644 web/modules/scheduler/drush.services.yml delete mode 100644 web/modules/scheduler/scheduler_handler_field_scheduler_countdown.inc create mode 100644 web/modules/scheduler/scheduler_rules_integration/composer.json create mode 100644 web/modules/scheduler/src/Commands/SchedulerCommands.php diff --git a/composer.json b/composer.json index 0468fb882b..ed6c28cfd2 100644 --- a/composer.json +++ b/composer.json @@ -157,7 +157,7 @@ "drupal/redirect": "^1.3", "drupal/redis": "1.0", "drupal/roleassign": "^1.0@alpha", - "drupal/scheduler": "1.0", + "drupal/scheduler": "^1.0", "drupal/search_api": "1.1", "drupal/search_api_db": "1.1", "drupal/simple_gmap": "^2.0", diff --git a/composer.lock b/composer.lock index 10bd4605f0..53bbfb9dda 100644 --- a/composer.lock +++ b/composer.lock @@ -6816,23 +6816,24 @@ }, { "name": "drupal/scheduler", - "version": "1.0.0", + "version": "1.1.0", "source": { "type": "git", "url": "https://git.drupalcode.org/project/scheduler.git", - "reference": "8.x-1.0" + "reference": "8.x-1.1" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/scheduler-8.x-1.0.zip", - "reference": "8.x-1.0", - "shasum": "213025b64b417f459b73d260d7287ca1f916d587" + "url": "https://ftp.drupal.org/files/projects/scheduler-8.x-1.1.zip", + "reference": "8.x-1.1", + "shasum": "52ff9c778f183d970ec2d57dfc1ba45282054e97" }, "require": { "drupal/core": "*" }, "require-dev": { - "drupal/devel_generate": "*", + "drupal/coder": "^8.3.3", + "drupal/devel_generate": "^2.0", "drupal/rules": "*" }, "type": "drupal-module", @@ -6841,12 +6842,17 @@ "dev-1.x": "1.x-dev" }, "drupal": { - "version": "8.x-1.0", - "datestamp": "1510690385", + "version": "8.x-1.1", + "datestamp": "1566578555", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" } + }, + "drush": { + "services": { + "drush.services.yml": "^9" + } } }, "notification-url": "https://packages.drupal.org/8/downloads", diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index fdf4eedc43..88ade6e93d 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -7030,24 +7030,25 @@ }, { "name": "drupal/scheduler", - "version": "1.0.0", - "version_normalized": "1.0.0.0", + "version": "1.1.0", + "version_normalized": "1.1.0.0", "source": { "type": "git", "url": "https://git.drupalcode.org/project/scheduler.git", - "reference": "8.x-1.0" + "reference": "8.x-1.1" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/scheduler-8.x-1.0.zip", - "reference": "8.x-1.0", - "shasum": "213025b64b417f459b73d260d7287ca1f916d587" + "url": "https://ftp.drupal.org/files/projects/scheduler-8.x-1.1.zip", + "reference": "8.x-1.1", + "shasum": "52ff9c778f183d970ec2d57dfc1ba45282054e97" }, "require": { "drupal/core": "*" }, "require-dev": { - "drupal/devel_generate": "*", + "drupal/coder": "^8.3.3", + "drupal/devel_generate": "^2.0", "drupal/rules": "*" }, "type": "drupal-module", @@ -7056,12 +7057,17 @@ "dev-1.x": "1.x-dev" }, "drupal": { - "version": "8.x-1.0", - "datestamp": "1510690385", + "version": "8.x-1.1", + "datestamp": "1566578555", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" } + }, + "drush": { + "services": { + "drush.services.yml": "^9" + } } }, "installation-source": "dist", diff --git a/web/modules/scheduler/.travis.yml b/web/modules/scheduler/.travis.yml index 19c0dac9c9..8871a6f56c 100644 --- a/web/modules/scheduler/.travis.yml +++ b/web/modules/scheduler/.travis.yml @@ -3,22 +3,32 @@ language: php # sudo here. sudo: true +notifications: + email: + recipients: + - jonathan1055@sandfordsolutions.com + php: - - 5.5 - 5.6 - 7.0 +services: + - mysql + env: global: - # Make the script re-usable for other modules. - MODULE=scheduler matrix: - - DRUPAL_CORE=8.3.x - - DRUPAL_CORE=8.4.x - - DRUPAL_CORE=8.5.x + - DRUPAL_CORE=8.6.x + - DRUPAL_CORE=8.7.x + - DRUPAL_CORE=8.8.x matrix: fast_finish: true + # Core 8.8 no longer supports PHP5, so don't run that combination. + exclude: + - php: 5.6 + env: DRUPAL_CORE=8.8.x # Be sure to cache composer downloads. cache: @@ -49,18 +59,22 @@ before_script: - DRUPAL_ROOT=$(pwd) - echo $DRUPAL_ROOT - # Make a directory for module and copy the built source into it. + # Make a directory for our module and copy the built source into it. - mkdir $DRUPAL_ROOT/modules/$MODULE - cp -R $TRAVIS_BUILD_DIR/* $DRUPAL_ROOT/modules/$MODULE/ # Install dependencies. - travis_retry git clone --branch 8.x-3.x --depth 1 https://github.com/fago/rules.git modules/rules - - travis_retry git clone --branch 8.x-1.x --depth 1 http://git.drupal.org/project/devel.git modules/devel - - travis_retry git clone --branch 8.x-1.x --depth 1 http://git.drupal.org/project/typed_data.git modules/typed_data + - travis_retry git clone --branch 8.x-2.1 --depth 1 https://git.drupalcode.org/project/devel.git modules/devel + - travis_retry git clone --branch 8.x-1.x --depth 1 https://git.drupalcode.org/project/typed_data.git modules/typed_data - # Run composer install for Drupal 8.1 and up. We need an up-to-date composer - # when installing Drupal 8.1. + # Run composer self-update and install. - travis_retry composer self-update && travis_retry composer install + # If running Core 8.6 or 8.7 the following script will upgrade to phpunit 6 + # which is required in PHP7. The script has now been deleted from Core 8.8. + - if [[ $DRUPAL_CORE == "8.6.x" || $DRUPAL_CORE == "8.7.x" ]]; then + travis_retry composer run-script drupal-phpunit-upgrade; + fi # Coder is already installed as part of composer install. We just need to set # the installed_paths to pick up the Drupal standards. @@ -76,14 +90,14 @@ script: # Run the PHPUnit tests. - ./vendor/bin/phpunit -c ./core/phpunit.xml.dist ./modules/$MODULE/tests/ - # Change directory to our module. + # Check for coding standards. First change directory to our module. - cd $DRUPAL_ROOT/modules/$MODULE - # Check for coding standards. First list all the sniffs to be used. + # List all the sniffs that were used. - $DRUPAL_ROOT/vendor/bin/phpcs -e - # Show the coding standards violations in detail. - - $DRUPAL_ROOT/vendor/bin/phpcs --runtime-set ignore_errors_on_exit 1 --runtime-set ignore_warnings_on_exit 1 + # Show the violations in detail and do not fail for any errors or warnings. + - $DRUPAL_ROOT/vendor/bin/phpcs --runtime-set ignore_warnings_on_exit 1 --runtime-set ignore_errors_on_exit 1 - # Run again to give a summary and total count. - - $DRUPAL_ROOT/vendor/bin/phpcs --runtime-set ignore_errors_on_exit 1 --runtime-set ignore_warnings_on_exit 1 --report=summary + # Run again to give a summary and total count, and fail for errors (not warnings). + - $DRUPAL_ROOT/vendor/bin/phpcs --report=summary --runtime-set ignore_warnings_on_exit 1 diff --git a/web/modules/scheduler/composer.json b/web/modules/scheduler/composer.json index 0085b59527..6a1534ff36 100644 --- a/web/modules/scheduler/composer.json +++ b/web/modules/scheduler/composer.json @@ -1,33 +1,44 @@ { - "name": "drupal/scheduler", - "description": "Automatically publish and unpublish content at specified dates and times.", - "type": "drupal-module", - "homepage": "https://drupal.org/project/scheduler", - "authors": [ - { - "name": "Eric Schaefer (Eric Schaefer)", - "homepage": "https://www.drupal.org/u/eric-schaefer", - "role": "Maintainer" + "name": "drupal/scheduler", + "description": "Automatically publish and unpublish content at specified dates and times.", + "type": "drupal-module", + "license": "GPL-2.0+", + "homepage": "https://drupal.org/project/scheduler", + "require-dev": { + "drupal/devel_generate": "^2.0", + "drupal/coder": "^8.3.3" }, - { - "name": "Jonathan Smith (jonathan1055)", - "homepage": "https://www.drupal.org/u/jonathan1055", - "role": "Maintainer" + "authors": [ + { + "name": "Eric Schaefer (Eric Schaefer)", + "homepage": "https://www.drupal.org/u/eric-schaefer", + "role": "Maintainer" + }, + { + "name": "Jonathan Smith (jonathan1055)", + "homepage": "https://www.drupal.org/u/jonathan1055", + "role": "Maintainer" + }, + { + "name": "Pieter Frenssen (pfrenssen)", + "homepage": "https://www.drupal.org/u/pfrenssen", + "role": "Maintainer" + }, + { + "name": "Rick Manelius (rickmanelius)", + "homepage": "https://www.drupal.org/u/rickmanelius", + "role": "Maintainer" + } + ], + "support": { + "issues": "https://www.drupal.org/project/issues/scheduler", + "source": "http://cgit.drupalcode.org/scheduler" }, - { - "name": "Pieter Frenssen (pfrenssen)", - "homepage": "https://www.drupal.org/u/pfrenssen", - "role": "Maintainer" - }, - { - "name": "Rick Manelius (rickmanelius)", - "homepage": "https://www.drupal.org/u/rickmanelius", - "role": "Maintainer" + "extra": { + "drush": { + "services": { + "drush.services.yml": "^9" + } + } } - ], - "support": { - "issues": "https://www.drupal.org/project/issues/scheduler", - "source": "http://cgit.drupalcode.org/scheduler" - }, - "license": "GPL-2.0+" } diff --git a/web/modules/scheduler/config/install/scheduler.settings.yml b/web/modules/scheduler/config/install/scheduler.settings.yml index 4c82158c86..1af27d4cad 100644 --- a/web/modules/scheduler/config/install/scheduler.settings.yml +++ b/web/modules/scheduler/config/install/scheduler.settings.yml @@ -6,6 +6,7 @@ default_expand_fieldset: 'when_required' default_fields_display_mode: 'vertical_tab' default_publish_enable: false default_publish_past_date: 'error' +default_publish_past_date_created: false default_publish_required: false default_publish_revision: false default_publish_touch: false diff --git a/web/modules/scheduler/config/install/views.view.scheduler_scheduled_content.yml b/web/modules/scheduler/config/install/views.view.scheduler_scheduled_content.yml index 2b1c39d21f..98549cb439 100644 --- a/web/modules/scheduler/config/install/views.view.scheduler_scheduled_content.yml +++ b/web/modules/scheduler/config/install/views.view.scheduler_scheduled_content.yml @@ -7,11 +7,11 @@ dependencies: - user id: scheduler_scheduled_content label: 'Scheduled content' -module: node +module: views description: 'Find and manage scheduled content.' -tag: default -base_table: node_field_data -base_field: nid +tag: '' +base_table: node_field_revision +base_field: vid core: 8.x display: default: @@ -20,8 +20,15 @@ display: type: scheduler cache: type: tag + options: { } query: type: views_query + options: + disable_sql_rewrite: false + distinct: false + replica: false + query_comment: '' + query_tags: { } exposed_form: type: basic options: @@ -36,6 +43,9 @@ display: type: full options: items_per_page: 50 + offset: 0 + id: 0 + total_pages: null tags: previous: '‹ previous' next: 'next ›' @@ -110,8 +120,6 @@ display: empty_column: false responsive: '' operations: - sortable: false - default_sort_order: asc align: '' separator: '' empty_column: false @@ -125,27 +133,80 @@ display: id: node_bulk_form table: node field: node_bulk_form + relationship: nid + group_type: group + admin_label: '' label: '' exclude: false alter: alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' element_default_classes: true empty: '' hide_empty: false empty_zero: false hide_alter_empty: true + action_title: Action + include_exclude: exclude + selected_actions: { } plugin_id: node_bulk_form entity_type: node title: id: title - table: node_field_data + table: node_field_revision field: title + relationship: none + group_type: group + admin_label: '' label: Title exclude: false alter: alter_text: false + make_link: false + absolute: false + trim: false + word_boundary: false + ellipsis: false + strip_tags: false + html: false + element_type: '' element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' element_default_classes: true empty: '' hide_empty: false @@ -156,12 +217,23 @@ display: type: string settings: link_to_entity: true + click_sort_column: value + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false plugin_id: field type: id: type table: node_field_data field: type - relationship: none + relationship: nid group_type: group admin_label: '' label: 'Content Type' @@ -226,12 +298,45 @@ display: id: name table: users_field_data field: name - relationship: uid + relationship: revision_uid + group_type: group + admin_label: '' label: Author exclude: false alter: alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' element_default_classes: true empty: '' hide_empty: false @@ -239,33 +344,91 @@ display: hide_alter_empty: true plugin_id: field type: user_name + click_sort_column: value + settings: + link_to_entity: true + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false entity_type: user entity_field: name status: id: status - table: node_field_data + table: node_field_revision field: status + relationship: none + group_type: group + admin_label: '' label: Status exclude: false alter: alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' element_default_classes: true empty: '' hide_empty: false empty_zero: false hide_alter_empty: true + click_sort_column: value type: boolean settings: format: custom format_custom_true: Published format_custom_false: Unpublished + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false plugin_id: field entity_type: node entity_field: status publish_on: id: publish_on - table: node_field_data + table: node_field_revision field: publish_on relationship: none group_type: group @@ -311,15 +474,28 @@ display: hide_empty: false empty_zero: false hide_alter_empty: true - date_format: short - custom_date_format: '' - timezone: '' + click_sort_column: value + type: timestamp + settings: + date_format: short + custom_date_format: '' + timezone: '' + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false entity_type: node entity_field: publish_on - plugin_id: date + plugin_id: field unpublish_on: id: unpublish_on - table: node_field_data + table: node_field_revision field: unpublish_on relationship: none group_type: group @@ -365,15 +541,28 @@ display: hide_empty: false empty_zero: false hide_alter_empty: true - date_format: short - custom_date_format: '' - timezone: '' + click_sort_column: value + type: timestamp + settings: + date_format: short + custom_date_format: '' + timezone: '' + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false entity_type: node entity_field: unpublish_on - plugin_id: date + plugin_id: field operations: id: operations - table: node + table: node_revision field: operations relationship: none group_type: group @@ -420,59 +609,94 @@ display: empty_zero: false hide_alter_empty: true destination: true + entity_type: node plugin_id: entity_operations filters: - status: - id: status - table: node_field_data - field: status + latest_revision: + id: latest_revision + table: node_revision + field: latest_revision relationship: none group_type: group admin_label: '' operator: '=' - value: '1' + value: '' group: 1 - exposed: true + exposed: false expose: operator_id: '' - label: Status + label: '' description: '' use_operator: false - operator: status_op - identifier: status + operator: '' + identifier: '' required: false remember: false multiple: false remember_roles: - authenticated: authenticated - is_grouped: true + anonymous: '0' + authenticated: '0' + administrator: '0' + is_grouped: false group_info: - label: 'Published status' + label: '' description: '' - identifier: status + identifier: '' optional: true widget: select multiple: false remember: false default_group: All default_group_multiple: { } - group_items: - 1: - title: Published - operator: '=' - value: '1' - 2: - title: Unpublished - operator: '=' - value: '0' - plugin_id: boolean + group_items: { } entity_type: node - entity_field: status + plugin_id: latest_revision + title: + id: title + table: node_field_revision + field: title + relationship: none + group_type: group + admin_label: '' + operator: contains + value: '' + group: 1 + exposed: true + expose: + operator_id: title_op + label: Title + description: '' + use_operator: false + operator: title_op + identifier: title + required: false + remember: false + multiple: false + remember_roles: + anonymous: '0' + authenticated: '0' + administrator: '0' + placeholder: '' + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: string + entity_type: node + entity_field: title type: id: type table: node_field_data field: type - relationship: none + relationship: nid group_type: group admin_label: '' operator: in @@ -481,7 +705,7 @@ display: exposed: true expose: operator_id: type_op - label: Type + label: 'Content type' description: '' use_operator: false operator: type_op @@ -490,8 +714,8 @@ display: remember: false multiple: false remember_roles: - authenticated: authenticated anonymous: '0' + authenticated: '0' administrator: '0' reduce: false is_grouped: false @@ -509,49 +733,57 @@ display: plugin_id: bundle entity_type: node entity_field: type - title: - id: title - table: node_field_data - field: title + status: + id: status + table: node_field_revision + field: status relationship: none group_type: group admin_label: '' - operator: contains + operator: '=' value: '' group: 1 exposed: true expose: - operator_id: title_op - label: Title + operator_id: '' + label: Status description: '' use_operator: false - operator: title_op - identifier: title + operator: status_op + identifier: status required: false remember: false multiple: false remember_roles: - authenticated: authenticated anonymous: '0' + authenticated: '0' administrator: '0' - is_grouped: false + is_grouped: true group_info: - label: '' + label: 'Published status' description: '' - identifier: '' + identifier: status optional: true widget: select multiple: false remember: false default_group: All default_group_multiple: { } - group_items: { } - plugin_id: string + group_items: + 1: + title: Published + operator: '=' + value: '1' + 2: + title: Unpublished + operator: '=' + value: '0' + plugin_id: boolean entity_type: node - entity_field: title + entity_field: status langcode: id: langcode - table: node_field_data + table: node_field_revision field: langcode relationship: none group_type: group @@ -571,8 +803,8 @@ display: remember: false multiple: false remember_roles: - authenticated: authenticated anonymous: '0' + authenticated: '0' administrator: '0' reduce: false is_grouped: false @@ -592,7 +824,7 @@ display: entity_field: langcode publish_on: id: publish_on - table: node_field_data + table: node_field_revision field: publish_on relationship: none group_type: group @@ -606,40 +838,40 @@ display: group: 2 exposed: false expose: - operator_id: publish_on_op - label: 'Publish on' + operator_id: '' + label: '' description: '' - use_operator: true - operator: publish_on_op - identifier: publish_on + use_operator: false + operator: '' + identifier: '' required: false remember: false multiple: false + placeholder: '' + min_placeholder: '' + max_placeholder: '' remember_roles: - authenticated: authenticated anonymous: '0' + authenticated: '0' administrator: '0' is_grouped: false group_info: - label: 'Publish on' - description: null - identifier: publish_on + label: '' + description: '' + identifier: '' optional: true widget: select multiple: false remember: false default_group: All default_group_multiple: { } - group_items: - 1: { } - 2: { } - 3: { } + group_items: { } entity_type: node entity_field: publish_on plugin_id: date unpublish_on: id: unpublish_on - table: node_field_data + table: node_field_revision field: unpublish_on relationship: none group_type: group @@ -653,18 +885,21 @@ display: group: 2 exposed: false expose: - operator_id: unpublish_on_op - label: 'Unpublish on' + operator_id: '' + label: '' description: '' use_operator: false - operator: unpublish_on_op - identifier: unpublish_on + operator: '' + identifier: '' required: false remember: false multiple: false + placeholder: '' + min_placeholder: '' + max_placeholder: '' remember_roles: - authenticated: authenticated anonymous: '0' + authenticated: '0' administrator: '0' is_grouped: false group_info: @@ -683,6 +918,8 @@ display: plugin_id: date sorts: { } title: 'Scheduled Content' + header: { } + footer: { } empty: area_text_custom: id: area_text_custom @@ -697,14 +934,28 @@ display: plugin_id: text_custom arguments: { } relationships: - uid: - id: uid - table: node_field_data - field: uid - admin_label: author - required: true + nid: + id: nid + table: node_field_revision + field: nid + admin_label: 'node id' + required: false + relationship: none + group_type: group + entity_type: node + entity_field: nid + plugin_id: standard + revision_uid: + id: revision_uid + table: node_revision + field: revision_uid + relationship: none + group_type: group + admin_label: 'revision user id' + required: false + entity_type: node + entity_field: revision_uid plugin_id: standard - show_admin_links: false filter_groups: operator: AND groups: @@ -723,7 +974,6 @@ display: - url.query_args - user - 'user.node_grants:view' - cacheable: false max-age: 0 tags: { } overview: @@ -733,6 +983,7 @@ display: type: tab title: Scheduled description: '' + expanded: false parent: system.admin_content weight: -10 context: '0' @@ -745,6 +996,7 @@ display: weight: -10 display_extenders: { } display_description: 'Overview of all scheduled content, as a tab on main ''content admin'' page' + display_comment: 'Revision nid relationship is required because the content type is only stored at ''content'' level, not ''content revision'' level.' display_plugin: page display_title: 'Content Overview' id: overview @@ -757,7 +1009,6 @@ display: - url.query_args - user - 'user.node_grants:view' - cacheable: false max-age: 0 tags: { } user_page: @@ -788,7 +1039,7 @@ display: arguments: uid: id: uid - table: node_field_data + table: node_field_revision field: uid relationship: none group_type: group @@ -813,11 +1064,16 @@ display: sort_order: asc number_of_records: 0 format: default_summary - specify_validation: false + specify_validation: true validate: - type: none + type: 'entity:user' fail: 'not found' - validate_options: { } + validate_options: + operation: view + multiple: 0 + access: false + restrict_roles: false + roles: { } break_phrase: false not: false entity_type: node @@ -847,6 +1103,5 @@ display: - url.query_args - user - 'user.node_grants:view' - cacheable: false max-age: 0 tags: { } diff --git a/web/modules/scheduler/config/schema/scheduler.schema.yml b/web/modules/scheduler/config/schema/scheduler.schema.yml index 10ee7b07ce..765edb4560 100644 --- a/web/modules/scheduler/config/schema/scheduler.schema.yml +++ b/web/modules/scheduler/config/schema/scheduler.schema.yml @@ -28,6 +28,9 @@ scheduler.settings: default_publish_past_date: type: string label: 'Default value for nodetype setting publish_past_date' + default_publish_past_date_created: + type: boolean + label: 'Default value for nodetype setting publish_past_date_created' default_publish_required: type: boolean label: 'Default value for nodetype setting publish_required' @@ -78,6 +81,9 @@ node.type.*.third_party.scheduler: publish_past_date: type: string label: 'Action to be taken for publication dates in the past' + publish_past_date_created: + type: boolean + label: 'Change content creation date for past dates to avoid "changed" being earlier than "created"' publish_required: type: boolean label: 'Require scheduled publishing' diff --git a/web/modules/scheduler/drupalci.yml b/web/modules/scheduler/drupalci.yml new file mode 100644 index 0000000000..8e9aa20ae9 --- /dev/null +++ b/web/modules/scheduler/drupalci.yml @@ -0,0 +1,12 @@ +build: + assessment: + validate_codebase: + phplint: + container_composer: + csslint: + eslint: + phpcs: + testing: + run_tests.standard: + types: 'PHPUnit-Functional' + suppress-deprecations: false diff --git a/web/modules/scheduler/drush.services.yml b/web/modules/scheduler/drush.services.yml new file mode 100644 index 0000000000..53a9056f4c --- /dev/null +++ b/web/modules/scheduler/drush.services.yml @@ -0,0 +1,8 @@ +services: + scheduler.commands: + class: \Drupal\scheduler\Commands\SchedulerCommands + arguments: + - '@scheduler.manager' + - '@messenger' + tags: + - { name: drush.command } diff --git a/web/modules/scheduler/scheduler.admin.inc b/web/modules/scheduler/scheduler.admin.inc index 94fb146f90..26654cf45d 100644 --- a/web/modules/scheduler/scheduler.admin.inc +++ b/web/modules/scheduler/scheduler.admin.inc @@ -92,6 +92,21 @@ function _scheduler_form_node_type_form_alter(array &$form, FormStateInterface $ 'schedule' => t('Schedule the content for publication on the next cron run'), ], ]; + $form['scheduler']['publish']['advanced']['scheduler_publish_past_date_created'] = [ + '#type' => 'checkbox', + '#title' => t("Change content creation time to match the published time for dates before the content was created"), + '#description' => t("The created time will only be altered when the scheduled publishing time is earlier than the existing content creation time"), + '#default_value' => $type->getThirdPartySetting('scheduler', 'publish_past_date_created', $config->get('default_publish_past_date_created')), + // This option is not relevant if the full 'change creation time' option is + // selected, or when past dates are not allowed. Hence only show it when + // the main option is not checked and the past dates option is not 'error'. + '#states' => [ + 'visible' => [ + ':input[name="scheduler_publish_touch"]' => ['checked' => FALSE], + ':input[name="scheduler_publish_past_date"]' => ['!value' => 'error'], + ], + ], + ]; // Unpublishing options. $form['scheduler']['unpublish'] = [ @@ -144,7 +159,6 @@ function _scheduler_form_node_type_form_alter(array &$form, FormStateInterface $ ], ], ]; - // @todo Worthwhile to port this to D8 now form displays are configurable? $form['scheduler']['node_edit_layout']['scheduler_fields_display_mode'] = [ '#type' => 'radios', '#title' => t('Display scheduling options as'), @@ -176,6 +190,7 @@ function scheduler_form_node_type_form_builder($entity_type, NodeTypeInterface $ $type->setThirdPartySetting('scheduler', 'fields_display_mode', $form_state->getValue('scheduler_fields_display_mode')); $type->setThirdPartySetting('scheduler', 'publish_enable', $form_state->getValue('scheduler_publish_enable')); $type->setThirdPartySetting('scheduler', 'publish_past_date', $form_state->getValue('scheduler_publish_past_date')); + $type->setThirdPartySetting('scheduler', 'publish_past_date_created', $form_state->getValue('scheduler_publish_past_date_created')); $type->setThirdPartySetting('scheduler', 'publish_required', $form_state->getValue('scheduler_publish_required')); $type->setThirdPartySetting('scheduler', 'publish_revision', $form_state->getValue('scheduler_publish_revision')); $type->setThirdPartySetting('scheduler', 'publish_touch', $form_state->getValue('scheduler_publish_touch')); diff --git a/web/modules/scheduler/scheduler.api.php b/web/modules/scheduler/scheduler.api.php index e55004c243..3304947102 100644 --- a/web/modules/scheduler/scheduler.api.php +++ b/web/modules/scheduler/scheduler.api.php @@ -78,7 +78,7 @@ function hook_scheduler_allow_publishing(NodeInterface $node) { // If publication is denied then inform the user why. This message will be // displayed during node edit and save. if (!$allowed) { - drupal_set_message(t('The content will only be published after approval by the CEO.'), 'status', FALSE); + \Drupal::messenger()->addMessage(t('The content will only be published after approval by the CEO.'), 'status', FALSE); } } @@ -111,13 +111,129 @@ function hook_scheduler_allow_unpublishing(NodeInterface $node) { // If unpublication is denied then inform the user why. This message will be // displayed during node edit and save. if (!$allowed) { - drupal_set_message(t('The competition will only be unpublished after all prizes have been claimed by the winners.')); + \Drupal::messenger()->addMessage(t('The competition will only be unpublished after all prizes have been claimed by the winners.')); } } return $allowed; } +/** + * Hook function to hide the Publish On field. + * + * This hook is called from scheduler_form_node_form_alter(). It gives modules + * the ability to hide the scheduler publish_on input field on the node edit + * form. Note that it does not give the ability to force the field to be + * displayed, as that could override a more significant setting. It can only be + * used to hide the field. + * + * This hook was introduced for scheduler_content_moderation_integration. + * + * @param array $form + * An associative array containing the structure of the form, as used in + * hook_form_alter(). + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form, as used in hook_form_alter(). + * @param \Drupal\node\NodeInterface $node + * The $node object of the node being editted. + * + * @see https://www.drupal.org/project/scheduler/issues/2798689 + * + * @return bool + * TRUE to hide the publish_on field. + * FALSE or NULL to leave the setting unchanged. + */ +function hook_scheduler_hide_publish_on_field(array $form, FormStateInterface $form_state, NodeInterface $node) { + return FALSE; +} + +/** + * Hook function to hide the Unpublish On field. + * + * This hook is called from scheduler_form_node_form_alter(). It gives modules + * the ability to hide the scheduler unpublish_on input field on the node edit + * form. Note that it does not give the ability to force the field to be + * displayed, as that could override a more significant setting. It can only be + * used to hide the field. + * + * This hook was introduced for scheduler_content_moderation_integration. + * + * @param array $form + * An associative array containing the structure of the form, as used in + * hook_form_alter(). + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form, as used in hook_form_alter(). + * @param \Drupal\node\NodeInterface $node + * The $node object of the node being editted. + * + * @see https://www.drupal.org/project/scheduler/issues/2798689 + * + * @return bool + * TRUE to hide the unpublish_on field. + * FALSE or NULL to leave the setting unchanged. + */ +function hook_scheduler_hide_unpublish_on_field(array $form, FormStateInterface $form_state, NodeInterface $node) { + return FALSE; +} + +/** + * Hook function to process the publish action for a node. + * + * This hook is called from schedulerManger::publish() and allows oher modules + * to process the publish action on a node during a cron run. The other module + * may require different functionality to be executed instead of the default + * publish process. If none of the invoked hook functions return a TRUE value + * then Scheduler will process the node using the default publish action, just + * as if no other hooks had been called. + * + * This hook was introduced for scheduler_content_moderation_integration. + * + * @param \Drupal\node\NodeInterface $node + * The $node object of the node being published. + * + * @see https://www.drupal.org/project/scheduler/issues/2798689 + * + * @return int + * 1 if this function has published the node or performed other such action + * meaning that Scheduler should NOT process the default publish action. + * 0 if nothing has been done and Scheduler should process the default publish + * action just as if this hook function did not exist. + * -1 if an error has occurred and Scheduler should abandon processing this + * node with no further action and move on to the next one. + */ +function hook_scheduler_publish_action(NodeInterface $node) { + return 0; +} + +/** + * Hook function to process the unpublish action for a node. + * + * This hook is called from schedulerManger::unpublish() and allows oher modules + * to process the unpublish action on a node during a cron run. The other module + * may require different functionality to be executed instead of the default + * unpublish process. If none of the invoked hook functions return a TRUE value + * then Scheduler will process the node using the default unpublish action, just + * as if no other hooks had been called. + * + * This hook was introduced for scheduler_content_moderation_integration. + * + * @param \Drupal\node\NodeInterface $node + * The $node object of the node being unpublished. + * + * @see https://www.drupal.org/project/scheduler/issues/2798689 + * + * @return int + * 1 if this function has published the node or performed other such action + * meaning that Scheduler should NOT process the default publish action. + * 0 if nothing has been done and Scheduler should process the default publish + * action just as if this hook function did not exist. + * -1 if an error has occurred and Scheduler should abandon processing this + * node with no further action and move on to the next one. + */ +function hook_scheduler_unpublish_action(NodeInterface $node) { + return 0; +} + /** * @} End of "addtogroup hooks". */ diff --git a/web/modules/scheduler/scheduler.drush.inc b/web/modules/scheduler/scheduler.drush.inc index cba3f47925..09ebbfe3d8 100644 --- a/web/modules/scheduler/scheduler.drush.inc +++ b/web/modules/scheduler/scheduler.drush.inc @@ -12,7 +12,7 @@ function scheduler_drush_command() { $items = []; $items['scheduler-cron'] = [ - 'description' => 'Lighweight cron to process scheduler tasks.', + 'description' => 'Lightweight cron to process scheduler tasks.', 'core' => ['8+'], 'aliases' => ['sch-cron'], 'category' => 'scheduler', @@ -25,10 +25,10 @@ function scheduler_drush_command() { } /** - * Run lighweight scheduler cron. + * Run lightweight scheduler cron. */ function drush_scheduler_cron() { \Drupal::service('scheduler.manager')->runLightweightCron(); $nomsg = drush_get_option('nomsg', NULL); - $nomsg ? NULL : drupal_set_message(t('Scheduler lightweight cron completed')); + $nomsg ? NULL : \Drupal::messenger()->addMessage(t('Scheduler lightweight cron completed')); } diff --git a/web/modules/scheduler/scheduler.info.yml b/web/modules/scheduler/scheduler.info.yml index f034a76842..72a5f50c0c 100644 --- a/web/modules/scheduler/scheduler.info.yml +++ b/web/modules/scheduler/scheduler.info.yml @@ -1,9 +1,10 @@ name: Scheduler type: module description: 'Publish and unpublish content automatically on specified dates and times.' -# core: 8.x +core: 8.x configure: scheduler.admin_form dependencies: + - drupal:system (>= 8.5) - drupal:datetime - drupal:field - drupal:node @@ -14,8 +15,7 @@ libraries: - scheduler/admin - vertical-tabs -# Information added by Drupal.org packaging script on 2017-11-14 -version: '8.x-1.0' -core: '8.x' +# Information added by Drupal.org packaging script on 2019-08-23 +version: '8.x-1.1' project: 'scheduler' -datestamp: 1510690392 +datestamp: 1566566892 diff --git a/web/modules/scheduler/scheduler.install b/web/modules/scheduler/scheduler.install index 2e4982b896..08970676e3 100644 --- a/web/modules/scheduler/scheduler.install +++ b/web/modules/scheduler/scheduler.install @@ -17,7 +17,7 @@ function scheduler_requirements($phase) { if ($phase === 'runtime') { $user = \Drupal::currentUser(); - $now = REQUEST_TIME; + $now = \Drupal::time()->getRequestTime(); $system_date = \Drupal::config('system.date'); $date_default_timezone = $system_date->get('timezone.default') ?: date_default_timezone_get(); $date_formatter = \Drupal::service('date.formatter'); @@ -71,8 +71,6 @@ function scheduler_requirements($phase) { * Implements hook_install(). */ function scheduler_install() { - // Add our base fields to the schema. - \Drupal::service('entity.definition_update_manager')->applyUpdates(); // Set cron access key value, as this is now required in SchedulerCronForm. $config = \Drupal::service('config.factory')->getEditable('scheduler.settings'); $config->set('lightweight_cron_access_key', substr(md5(rand()), 0, 20)) @@ -83,8 +81,6 @@ function scheduler_install() { * Implements hook_uninstall(). */ function scheduler_uninstall() { - // Remove our base fields from the schema. - \Drupal::service('entity.definition_update_manager')->applyUpdates(); // Delete the scheduled content view. \Drupal::configFactory()->getEditable('views.view.scheduler_scheduled_content')->delete(); } diff --git a/web/modules/scheduler/scheduler.module b/web/modules/scheduler/scheduler.module index 9f86b4d2ff..a6d7979669 100644 --- a/web/modules/scheduler/scheduler.module +++ b/web/modules/scheduler/scheduler.module @@ -72,9 +72,37 @@ function scheduler_form_node_form_alter(&$form, FormStateInterface $form_state) $publishing_enabled = $type->getThirdPartySetting('scheduler', 'publish_enable', $config->get('default_publish_enable')); $unpublishing_enabled = $type->getThirdPartySetting('scheduler', 'unpublish_enable', $config->get('default_unpublish_enable')); - // If neither publishing nor unpublishing are enabled for this node type then - // the only thing to do is remove the fields from the form, then exit. - if (!$publishing_enabled && !$unpublishing_enabled) { + // Determine if the scheduler fields have been set to hidden (disabled). + $display = $form_state->getFormObject()->getFormDisplay($form_state); + $publishing_displayed = !empty($display->getComponent('publish_on')); + $unpublishing_displayed = !empty($display->getComponent('unpublish_on')); + + /* @var $node \Drupal\node\NodeInterface */ + $node = $form_state->getFormObject()->getEntity(); + + // Invoke all implementations of hook_scheduler_hide_publish_on_field() to + // allow other modules to hide the field on the node edit form. + if ($publishing_enabled && $publishing_displayed) { + $hook = 'scheduler_hide_publish_on_field'; + foreach (\Drupal::moduleHandler()->getImplementations($hook) as $module) { + $function = $module . '_' . $hook; + $publishing_displayed = ($function($form, $form_state, $node) !== TRUE) && $publishing_displayed; + } + } + // Invoke all implementations of hook_scheduler_hide_unpublish_on_field() to + // allow other modules to hide the field on the node edit form. + if ($unpublishing_enabled && $unpublishing_displayed) { + $hook = 'scheduler_hide_unpublish_on_field'; + foreach (\Drupal::moduleHandler()->getImplementations($hook) as $module) { + $function = $module . '_' . $hook; + $unpublishing_displayed = ($function($form, $form_state, $node) !== TRUE) && $unpublishing_displayed; + } + } + + // If both publishing and unpublishing are either not enabled or are hidden + // for this node type then the only thing to do is remove the fields from the + // form, then exit. + if ((!$publishing_enabled || !$publishing_displayed) && (!$unpublishing_enabled || !$unpublishing_displayed)) { unset($form['publish_on']); unset($form['unpublish_on']); return; @@ -82,9 +110,6 @@ function scheduler_form_node_form_alter(&$form, FormStateInterface $form_state) $date_only_allowed = $config->get('allow_date_only'); - /* @var $node \Drupal\node\NodeInterface */ - $node = $form_state->getFormObject()->getEntity(); - // A publish_on date is required if the content type option is set and the // node is being created or it currently has a scheduled publishing date. $publishing_required = $type->getThirdPartySetting('scheduler', 'publish_required', $config->get('default_publish_required')) @@ -145,7 +170,7 @@ function scheduler_form_node_form_alter(&$form, FormStateInterface $form_state) $descriptions['blank'] = t('Leave the date blank for no scheduled publishing.'); } - $form['publish_on']['#access'] = $publishing_enabled; + $form['publish_on']['#access'] = $publishing_enabled && $publishing_displayed; $form['publish_on']['widget'][0]['value']['#required'] = $publishing_required; $form['publish_on']['widget'][0]['value']['#description'] = Xss::filter(implode(' ', $descriptions)); @@ -156,16 +181,23 @@ function scheduler_form_node_form_alter(&$form, FormStateInterface $form_state) unset($descriptions['blank']); } - $form['unpublish_on']['#access'] = $unpublishing_enabled; + $form['unpublish_on']['#access'] = $unpublishing_enabled && $unpublishing_displayed; $form['unpublish_on']['widget'][0]['value']['#required'] = $unpublishing_required; $form['unpublish_on']['widget'][0]['value']['#description'] = Xss::filter(implode(' ', $descriptions)); if (!\Drupal::currentUser()->hasPermission('schedule publishing of nodes')) { // Do not show the scheduler fields for users who do not have permission. + // Setting #access to FALSE for 'scheduler_settings' is enough to hide the + // fields. Setting FALSE for the individual fields is necessary to keep any + // existing scheduled dates preserved and remain unchanged on saving. $form['scheduler_settings']['#access'] = FALSE; + $form['publish_on']['#access'] = FALSE; + $form['unpublish_on']['#access'] = FALSE; // @todo Find a more elegant solution for bypassing the validation of // scheduler fields when the user does not have permission. + // Note: This scenario is NOT yet covered by any tests, neither in + // SchedulerPermissionsTest.php nor SchedulerRequiredTest.php // @see https://www.drupal.org/node/2651448 $form['publish_on']['widget'][0]['value']['#required'] = FALSE; $form['unpublish_on']['widget'][0]['value']['#required'] = FALSE; @@ -173,14 +205,18 @@ function scheduler_form_node_form_alter(&$form, FormStateInterface $form_state) // Check which widget type is set for the scheduler fields, and give a warning // if the wrong one has been set and provide a hint and link to fix it. - $storage_form_display = $form_state->getStorage()['form_display']; - $content = $storage_form_display->get('content'); - $pluginDefinitions = $storage_form_display->get('pluginManager')->getDefinitions(); + $pluginDefinitions = $display->get('pluginManager')->getDefinitions(); + if ($publishing_enabled && $publishing_displayed) { + $fields_to_check[] = 'publish_on'; + } + if ($unpublishing_enabled && $unpublishing_displayed) { + $fields_to_check[] = 'unpublish_on'; + } $correct_widget_id = 'datetime_timestamp_no_default'; - foreach (['publish_on' => $publishing_enabled, 'unpublish_on' => $unpublishing_enabled] as $field => $enabled) { - $actual_widget_id = $content[$field]['type']; - if ($enabled && $actual_widget_id != $correct_widget_id) { - drupal_set_message(t('The widget for field %field is incorrectly set to %wrong. This should be changed to %correct by an admin user via Field UI <a href="@link">content type form display</a> :not_available', [ + foreach ($fields_to_check as $field) { + $actual_widget_id = $display->getComponent($field)['type']; + if ($actual_widget_id != $correct_widget_id) { + \Drupal::messenger()->addMessage(t('The widget for field %field is incorrectly set to %wrong. This should be changed to %correct by an admin user via Field UI <a href="@link">content type form display</a> :not_available', [ '%field' => (string) $form[$field]['widget']['#title'], '%correct' => (string) $pluginDefinitions[$correct_widget_id]['label'], '%wrong' => (string) $pluginDefinitions[$actual_widget_id]['label'], @@ -298,6 +334,7 @@ function scheduler_node_view(array &$build, EntityInterface $node, EntityViewDis function scheduler_node_presave(EntityInterface $node) { $config = \Drupal::config('scheduler.settings'); $entity = $node->type->entity; + $request_time = \Drupal::time()->getRequestTime(); // If there is no entity object or the class is incorrect then stop here. This // should not really happen but it has been observed, so better to be safe. @@ -327,14 +364,14 @@ function scheduler_node_presave(EntityInterface $node) { if (rand(1, 100) <= $publishing_percent) { // Randomly assign a publish_on value in the range starting with the // created date and up to the selected time range in the future. - $node->set('publish_on', rand($node->created->value + 1, REQUEST_TIME + $time_range)); + $node->set('publish_on', rand($node->created->value + 1, $request_time + $time_range)); } } if ($unpublishing_percent && in_array($node->getType(), $unpublishing_enabled_types)) { if (rand(1, 100) <= $unpublishing_percent) { // Randomly assign an unpublish_on value in the range from the later of // created date/publish_on date up to the time range in the future. - $node->set('unpublish_on', rand(max($node->created->value, $node->publish_on->value), REQUEST_TIME + $time_range)); + $node->set('unpublish_on', rand(max($node->created->value, $node->publish_on->value), $request_time + $time_range)); } } } @@ -349,19 +386,23 @@ function scheduler_node_presave(EntityInterface $node) { // Publish the node immediately if the publication date is in the past. $publish_immediately = $entity->getThirdPartySetting('scheduler', 'publish_past_date', $config->get('default_publish_past_date')) == 'publish'; - if ($publication_allowed && $publish_immediately && $node->publish_on->value <= REQUEST_TIME) { + if ($publication_allowed && $publish_immediately && $node->publish_on->value <= $request_time) { // Trigger the PRE_PUBLISH_INMEDIATELY event so that modules can react // before the node has been published. $event = new SchedulerEvent($node); \Drupal::service('event_dispatcher')->dispatch(SchedulerEvents::PRE_PUBLISH_IMMEDIATELY, $event); $node = $event->getNode(); + // Set the 'changed' timestamp to match what would have been done had this + // content been published via cron. + $node->setChangedTime($node->publish_on->value); // If required, set the created date to match published date. - if ($entity->getThirdPartySetting('scheduler', 'publish_touch', $config->get('default_publish_touch'))) { + if ($entity->getThirdPartySetting('scheduler', 'publish_touch', $config->get('default_publish_touch')) || + ($node->getCreatedTime() > $node->publish_on->value && $entity->getThirdPartySetting('scheduler', 'publish_past_date_created', $config->get('default_publish_past_date_created')))) { $node->setCreatedTime($node->publish_on->value); } $node->publish_on->value = NULL; - $node->setPublished(TRUE); + $node->setPublished(); // Trigger the PUBLISH_IMMEDIATELY event so that modules can react after // the node has been published. @@ -371,14 +412,14 @@ function scheduler_node_presave(EntityInterface $node) { } else { // Ensure the node is unpublished as it will be published by cron later. - $node->setPublished(FALSE); + $node->setUnpublished(); // Only inform the user that the node is scheduled if publication has not // been prevented by other modules. Those modules have to display a // message themselves explaining why publication is denied. if ($publication_allowed) { $date_formatter = \Drupal::service('date.formatter'); - drupal_set_message(t('This post is unpublished and will be published @publish_time.', [ + \Drupal::messenger()->addMessage(t('This post is unpublished and will be published @publish_time.', [ '@publish_time' => $date_formatter->format($node->publish_on->value, 'long'), ]), 'status', FALSE); } @@ -462,7 +503,7 @@ function scheduler_entity_extra_field_info() { // content type. This allows admins to adjust the weight of the group, and it // works for vertical tabs and separate fieldsets. $fields = []; - foreach (node_type_get_types() as $type) { + foreach (NodeType::loadMultiple() as $type) { $publishing_enabled = $type->getThirdPartySetting('scheduler', 'publish_enable', $config->get('default_publish_enable')); $unpublishing_enabled = $type->getThirdPartySetting('scheduler', 'unpublish_enable', $config->get('default_unpublish_enable')); diff --git a/web/modules/scheduler/scheduler.services.yml b/web/modules/scheduler/scheduler.services.yml index 00ab742e8e..512a67a0cc 100644 --- a/web/modules/scheduler/scheduler.services.yml +++ b/web/modules/scheduler/scheduler.services.yml @@ -1,7 +1,14 @@ services: scheduler.manager: class: Drupal\scheduler\SchedulerManager - arguments: ['@date.formatter', '@logger.channel.scheduler', '@module_handler', '@entity.manager', '@config.factory' ] + arguments: + - '@date.formatter' + - '@logger.channel.scheduler' + - '@module_handler' + - '@entity_type.manager' + - '@config.factory' + - '@event_dispatcher' + - '@datetime.time' logger.channel.scheduler: class: Drupal\Core\Logger\LoggerChannel factory: logger.factory:get diff --git a/web/modules/scheduler/scheduler_handler_field_scheduler_countdown.inc b/web/modules/scheduler/scheduler_handler_field_scheduler_countdown.inc deleted file mode 100644 index 9794a588d9..0000000000 --- a/web/modules/scheduler/scheduler_handler_field_scheduler_countdown.inc +++ /dev/null @@ -1,157 +0,0 @@ -<?php - -namespace Drupal\scheduler; - -use Drupal\Core\Form\FormStateInterface; - -/** - * Display scheduled dates in 'countdown' format for use in Views. - * - * Field handler to display a countdown until a scheduled action. - * - * @TODO Is this file actually used and/or needed in Drupal 8? - * - * @see scheduler.views.inc - * @see http://www.ericschaefer.org/blog/2011/01/09/custom-field-handlers-for-views-2-drupal - */ -class SchedulerHandlerFieldSchedulerCountdown extends views_handler_field { - const SECOND_SCALE = 1; - const MINUTE_SCALE = 60; - const HOUR_SCALE = 3600; - const DAY_SCALE = 86400; - const WEEK_SCALE = 604800; - - /** - * Add the timestamp_field into the SQL query. - * - * It is calculated as publish_on - REQUEST_TIME so the result is the number - * of seconds from now until publishing. If publish_on is in the past then - * NULL is returned. - */ - public function query() { - $this->ensure_my_table(); - $this->node_table = $this->query->ensure_table('node', $this->relationship); - $time_field = $this->definition['timestamp_field']; - $this->field_alias = $this->query->add_field(NULL, 'CASE WHEN (' . $time_field . ' > ' . REQUEST_TIME . ') THEN (' . $time_field . ' - ' . REQUEST_TIME . ') ELSE NULL END', $this->table_alias . '_' . $this->field); - } - - /** - * Define our display options and provide defaults. - * - * @return array - * An associative array containing the options. - */ - public function optionDefinition() { - $options = parent::option_definition(); - $options['countdown_display'] = ['default' => 'smart']; - $options['units_display'] = ['default' => 'long']; - return $options; - } - - /** - * Defines the form for the user to select the display options. - */ - public function optionsForm(array &$form, FormStateInterface $form_state) { - parent::options_form($form, $form_state); - $form['countdown_display'] = [ - '#title' => t('Display countdown as'), - '#type' => 'radios', - '#options' => [ - 'smart' => t('Smart mode'), - 'seconds' => t('Seconds'), - 'minutes' => t('Minutes'), - 'hours' => t('Hours'), - 'days' => t('Days'), - 'weeks' => t('Weeks'), - ], - '#default_value' => $this->options['countdown_display'], - ]; - $form['units_display'] = [ - '#title' => t('Display time units'), - '#type' => 'radios', - '#options' => [ - 'long' => t('Long (for example 3 days)'), - 'short' => t('Short (for example 3d)'), - 'none' => t('No units at all'), - ], - '#default_value' => $this->options['units_display'], - ]; - } - - /** - * Callback function to filter out unwanted values. - * - * Keep only the array scale values which are smaller than the countdown value - * being displayed. - */ - public function scaleFilterCallback($array_value) { - return ($this->raw_value >= $array_value); - } - - /** - * Renders the countdown value in the units required. - */ - public function render($values) { - $countdown_display = $this->options['countdown_display']; - $this->raw_value = $values->{$this->field_alias}; - - $scales = [ - 'weeks' => self::WEEK_SCALE, - 'days' => self::DAY_SCALE, - 'hours' => self::HOUR_SCALE, - 'minutes' => self::MINUTE_SCALE, - 'seconds' => self::SECOND_SCALE, - ]; - // If the field has been set to 'Smart', determine the right timescale. - if ($countdown_display == 'smart') { - $scales = array_filter($scales, [$this, 'scale_filter_callback']); - $scale = empty($scales) ? self::SECOND_SCALE : reset($scales); - } - // Otherwise use the fixed display requested. - else { - $scale = $scales[$countdown_display]; - } - - // Get the display value by dividing the original value by the scale. - $scaled_value = round($this->raw_value / $scale); - - switch ($scale) { - case self::SECOND_SCALE: - $long = \Drupal::translation()->formatPlural($scaled_value, '1 second', '@count seconds', ['@count' => $scaled_value]); - $short = t('@counts', ['@count' => $scaled_value]); - break; - - case self::MINUTE_SCALE: - $long = \Drupal::translation()->formatPlural($scaled_value, '1 minute', '@count minutes', ['@count' => $scaled_value]); - $short = t('@countm', ['@count' => $scaled_value]); - break; - - case self::HOUR_SCALE: - $long = \Drupal::translation()->formatPlural($scaled_value, '1 hour', '@count hours', ['@count' => $scaled_value]); - $short = t('@counth', ['@count' => $scaled_value]); - break; - - case self::DAY_SCALE: - $long = \Drupal::translation()->formatPlural($scaled_value, '1 day', '@count days', ['@count' => $scaled_value]); - $short = t('@countd', ['@count' => $scaled_value]); - break; - - case self::WEEK_SCALE: - $long = \Drupal::translation()->formatPlural($scaled_value, '1 week', '@count weeks', ['@count' => $scaled_value]); - $short = t('@countw', ['@count' => $scaled_value]); - break; - } - - switch ($this->options['units_display']) { - case 'long': - return $long; - - case 'short': - return $short; - - default: - return $scaled_value; - } - } - -} diff --git a/web/modules/scheduler/scheduler_rules_integration/composer.json b/web/modules/scheduler/scheduler_rules_integration/composer.json new file mode 100644 index 0000000000..c33f5aea34 --- /dev/null +++ b/web/modules/scheduler/scheduler_rules_integration/composer.json @@ -0,0 +1,10 @@ +{ + "name": "drupal/scheduler_rules_integration", + "description": "Scheduler sub-module providing conditions, actions and events for use with the Rules module.", + "type": "drupal-module", + "license": "GPL-2.0+", + "require": { + "drupal/rules": "^3.x", + "drupal/scheduler": "^1.0" + } +} diff --git a/web/modules/scheduler/scheduler_rules_integration/scheduler_rules_integration.info.yml b/web/modules/scheduler/scheduler_rules_integration/scheduler_rules_integration.info.yml index 973d9edba1..ef4c01c6e6 100644 --- a/web/modules/scheduler/scheduler_rules_integration/scheduler_rules_integration.info.yml +++ b/web/modules/scheduler/scheduler_rules_integration/scheduler_rules_integration.info.yml @@ -1,13 +1,12 @@ name: Scheduler Rules Integration type: module description: 'Scheduler sub-module providing conditions, actions and events for use with the Rules module.' -# core: 8.x +core: 8.x dependencies: - rules:rules - scheduler:scheduler -# Information added by Drupal.org packaging script on 2017-11-14 -version: '8.x-1.0' -core: '8.x' +# Information added by Drupal.org packaging script on 2019-08-23 +version: '8.x-1.1' project: 'scheduler' -datestamp: 1510690392 +datestamp: 1566566892 diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Event/ExistingNodeIsScheduledForPublishingEvent.php b/web/modules/scheduler/scheduler_rules_integration/src/Event/ExistingNodeIsScheduledForPublishingEvent.php index d1c7a02fb7..5706b208ea 100644 --- a/web/modules/scheduler/scheduler_rules_integration/src/Event/ExistingNodeIsScheduledForPublishingEvent.php +++ b/web/modules/scheduler/scheduler_rules_integration/src/Event/ExistingNodeIsScheduledForPublishingEvent.php @@ -18,7 +18,7 @@ class ExistingNodeIsScheduledForPublishingEvent extends Event { /** * The node which is being scheduled and saved. * - * @var Drupal\node\NodeInterface + * @var \Drupal\node\NodeInterface */ public $node; diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Event/ExistingNodeIsScheduledForUnpublishingEvent.php b/web/modules/scheduler/scheduler_rules_integration/src/Event/ExistingNodeIsScheduledForUnpublishingEvent.php index b51a412bdd..10a3163440 100644 --- a/web/modules/scheduler/scheduler_rules_integration/src/Event/ExistingNodeIsScheduledForUnpublishingEvent.php +++ b/web/modules/scheduler/scheduler_rules_integration/src/Event/ExistingNodeIsScheduledForUnpublishingEvent.php @@ -18,7 +18,7 @@ class ExistingNodeIsScheduledForUnpublishingEvent extends Event { /** * The node which is being scheduled and saved. * - * @var Drupal\node\NodeInterface + * @var \Drupal\node\NodeInterface */ public $node; diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Event/NewNodeIsScheduledForPublishingEvent.php b/web/modules/scheduler/scheduler_rules_integration/src/Event/NewNodeIsScheduledForPublishingEvent.php index 4bafa013fe..8010e1df5c 100644 --- a/web/modules/scheduler/scheduler_rules_integration/src/Event/NewNodeIsScheduledForPublishingEvent.php +++ b/web/modules/scheduler/scheduler_rules_integration/src/Event/NewNodeIsScheduledForPublishingEvent.php @@ -18,7 +18,7 @@ class NewNodeIsScheduledForPublishingEvent extends Event { /** * The node which is being scheduled and saved. * - * @var Drupal\node\NodeInterface + * @var \Drupal\node\NodeInterface */ public $node; diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Event/NewNodeIsScheduledForUnpublishingEvent.php b/web/modules/scheduler/scheduler_rules_integration/src/Event/NewNodeIsScheduledForUnpublishingEvent.php index d002da6c3f..2e638595b6 100644 --- a/web/modules/scheduler/scheduler_rules_integration/src/Event/NewNodeIsScheduledForUnpublishingEvent.php +++ b/web/modules/scheduler/scheduler_rules_integration/src/Event/NewNodeIsScheduledForUnpublishingEvent.php @@ -18,7 +18,7 @@ class NewNodeIsScheduledForUnpublishingEvent extends Event { /** * The node which is being scheduled and saved. * - * @var Drupal\node\NodeInterface + * @var \Drupal\node\NodeInterface */ public $node; diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Event/SchedulerHasPublishedThisNodeEvent.php b/web/modules/scheduler/scheduler_rules_integration/src/Event/SchedulerHasPublishedThisNodeEvent.php index 266b7650b3..da4afa62df 100644 --- a/web/modules/scheduler/scheduler_rules_integration/src/Event/SchedulerHasPublishedThisNodeEvent.php +++ b/web/modules/scheduler/scheduler_rules_integration/src/Event/SchedulerHasPublishedThisNodeEvent.php @@ -17,7 +17,7 @@ class SchedulerHasPublishedThisNodeEvent extends Event { /** * The node which has been processed. * - * @var Drupal\node\NodeInterface + * @var \Drupal\node\NodeInterface */ public $node; diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Event/SchedulerHasUnpublishedThisNodeEvent.php b/web/modules/scheduler/scheduler_rules_integration/src/Event/SchedulerHasUnpublishedThisNodeEvent.php index 2ba13099ea..08b6937117 100644 --- a/web/modules/scheduler/scheduler_rules_integration/src/Event/SchedulerHasUnpublishedThisNodeEvent.php +++ b/web/modules/scheduler/scheduler_rules_integration/src/Event/SchedulerHasUnpublishedThisNodeEvent.php @@ -17,7 +17,7 @@ class SchedulerHasUnpublishedThisNodeEvent extends Event { /** * The node which has been processed.. * - * @var Drupal\node\NodeInterface + * @var \Drupal\node\NodeInterface */ public $node; diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/PublishNow.php b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/PublishNow.php index 3727a96662..b15e962371 100644 --- a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/PublishNow.php +++ b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/PublishNow.php @@ -30,7 +30,7 @@ class PublishNow extends RulesActionBase { */ public function doExecute() { $node = $this->getContextValue('node'); - $node->setPublished(TRUE); + $node->setPublished(); } } diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/RemovePublishingDate.php b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/RemovePublishingDate.php index 7969a8e3d1..24951f0297 100644 --- a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/RemovePublishingDate.php +++ b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/RemovePublishingDate.php @@ -37,7 +37,7 @@ public function doExecute() { $type_name = node_get_type_label($node); $arguments = [ '%type' => $type_name, - 'link' => \Drupal::l(t('@type settings', ['@type' => $type_name]), new Url('entity.node_type.edit_form', ['node_type' => $node->getType()])), + 'link' => \Drupal::l($this->t('@type settings', ['@type' => $type_name]), new Url('entity.node_type.edit_form', ['node_type' => $node->getType()])), ]; \Drupal::logger('scheduler')->warning('Scheduler rules action "Remove publishing date" - Scheduled publishing is not enabled for %type content. To prevent this message add the condition "Scheduled publishing is enabled" to your Rule, or enable the Scheduler options via the %type content type settings.', $arguments); } diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/RemoveUnpublishingDate.php b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/RemoveUnpublishingDate.php index 2d48c8cd43..25b4f9852c 100644 --- a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/RemoveUnpublishingDate.php +++ b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/RemoveUnpublishingDate.php @@ -37,7 +37,7 @@ public function doExecute() { $type_name = node_get_type_label($node); $arguments = [ '%type' => $type_name, - 'link' => \Drupal::l(t('@type settings', ['@type' => $type_name]), new Url('entity.node_type.edit_form', ['node_type' => $node->getType()])), + 'link' => \Drupal::l($this->t('@type settings', ['@type' => $type_name]), new Url('entity.node_type.edit_form', ['node_type' => $node->getType()])), ]; \Drupal::logger('scheduler')->warning('Scheduler rules action "Remove unpublishing date" - Scheduled unpublishing is not enabled for %type content. To prevent this message add the condition "Scheduled unpublishing is enabled" to your Rule, or enable the Scheduler options via the %type content type settings.', $arguments); } diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/SetPublishingDate.php b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/SetPublishingDate.php index 6d3d676f1e..cdea76dd95 100644 --- a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/SetPublishingDate.php +++ b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/SetPublishingDate.php @@ -46,7 +46,7 @@ public function doExecute() { $type_name = node_get_type_label($node); $arguments = [ '%type' => $type_name, - 'link' => \Drupal::l(t('@type settings', ['@type' => $type_name]), new Url('entity.node_type.edit_form', ['node_type' => $node->getType()])), + 'link' => \Drupal::l($this->t('@type settings', ['@type' => $type_name]), new Url('entity.node_type.edit_form', ['node_type' => $node->getType()])), ]; \Drupal::logger('scheduler')->warning('Scheduler rules action "Set publishing date" - Scheduled publishing is not enabled for %type content. To prevent this message add the condition "Scheduled publishing is enabled" to your Rule, or enable the Scheduler options via the %type content type settings.', $arguments); } diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/SetUnpublishingDate.php b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/SetUnpublishingDate.php index 5bfc93f68f..aa352bb733 100644 --- a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/SetUnpublishingDate.php +++ b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/SetUnpublishingDate.php @@ -46,7 +46,7 @@ public function doExecute() { $type_name = node_get_type_label($node); $arguments = [ '%type' => $type_name, - 'link' => \Drupal::l(t('@type settings', ['@type' => $type_name]), new Url('entity.node_type.edit_form', ['node_type' => $node->getType()])), + 'link' => \Drupal::l($this->t('@type settings', ['@type' => $type_name]), new Url('entity.node_type.edit_form', ['node_type' => $node->getType()])), ]; \Drupal::logger('scheduler')->warning('Scheduler rules action "Set unpublishing date" - Scheduled unpublishing is not enabled for %type content. To prevent this message add the condition "Scheduled unpublishing is enabled" to your Rule, or enable the Scheduler options via the %type content type settings.', $arguments); } diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/UnpublishNow.php b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/UnpublishNow.php index 2be4278806..498e9d66eb 100644 --- a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/UnpublishNow.php +++ b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/UnpublishNow.php @@ -30,7 +30,7 @@ class UnpublishNow extends RulesActionBase { */ public function doExecute() { $node = $this->getContextValue('node'); - $node->setPublished(FALSE); + $node->setUnpublished(); } } diff --git a/web/modules/scheduler/src/Commands/SchedulerCommands.php b/web/modules/scheduler/src/Commands/SchedulerCommands.php new file mode 100644 index 0000000000..b453ba6f5f --- /dev/null +++ b/web/modules/scheduler/src/Commands/SchedulerCommands.php @@ -0,0 +1,64 @@ +<?php + +namespace Drupal\scheduler\Commands; + +use Drupal\Core\Messenger\MessengerInterface; +use Drupal\scheduler\SchedulerManager; +use Drush\Commands\DrushCommands; + +/** + * Drush 9 Scheduler commands for Drupal Core 8.4+. + */ +class SchedulerCommands extends DrushCommands { + + /** + * The Scheduler manager service. + * + * @var \Drupal\scheduler\SchedulerManager + */ + protected $schedulerManager; + + /** + * The Messenger service. + * + * @var \Drupal\Core\Messenger\MessengerInterface + */ + protected $messenger; + + /** + * SchedulerCommands constructor. + * + * @param \Drupal\scheduler\SchedulerManager $schedulerManager + * Scheduler manager service. + * @param \Drupal\Core\Messenger\MessengerInterface $messenger + * Messenger service. + */ + public function __construct(SchedulerManager $schedulerManager, MessengerInterface $messenger) { + parent::__construct(); + $this->schedulerManager = $schedulerManager; + $this->messenger = $messenger; + } + + /** + * Lightweight cron to process Scheduler module tasks. + * + * @param array $options + * An associative array of options whose values come from cli, aliases, + * config, etc. + * + * @option nomsg + * to avoid the "cron completed" message being written to the terminal. + * @option nolog + * to overide the site setting and not write 'started' and 'completed' + * messages to the dblog. + * + * @command scheduler:cron + * @aliases sch-cron, scheduler-cron + */ + public function cron(array $options = ['nomsg' => NULL, 'nolog' => NULL]) { + $this->manager->runLightweightCron($options); + + $options['nomsg'] ? NULL : $this->messenger->addMessage(dt('Scheduler lightweight cron completed.')); + } + +} diff --git a/web/modules/scheduler/src/Controller/LightweightCronController.php b/web/modules/scheduler/src/Controller/LightweightCronController.php index 44d5c0b346..896497c173 100644 --- a/web/modules/scheduler/src/Controller/LightweightCronController.php +++ b/web/modules/scheduler/src/Controller/LightweightCronController.php @@ -4,6 +4,8 @@ use Drupal\Core\Access\AccessResult; use Drupal\Core\Controller\ControllerBase; +use Drupal\scheduler\SchedulerManager; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Response; /** @@ -13,18 +15,42 @@ */ class LightweightCronController extends ControllerBase { + /** + * The scheduler manager. + * + * @var \Drupal\scheduler\SchedulerManager + */ + protected $schedulerManager; + + /** + * LightweightCronController constructor. + * + * @param \Drupal\scheduler\SchedulerManager $scheduler_manager + * The scheduler manager. + */ + public function __construct(SchedulerManager $scheduler_manager) { + $this->schedulerManager = $scheduler_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('scheduler.manager') + ); + } + /** * Index. * - * @return \Symfony\Component\HttpFoundation\RedirectResponse - * RedirectResponse. + * @return \Symfony\Component\HttpFoundation\Response + * The http response. */ public function index() { - // @TODO: \Drupal calls should be avoided in classes. - // Replace \Drupal::service with dependency injection? - \Drupal::service('scheduler.manager')->runLightweightCron(); + $this->schedulerManager->runLightweightCron(); - return new Response('', 204); + return new Response('', Response::HTTP_NO_CONTENT); } /** @@ -37,9 +63,7 @@ public function index() { * The access result. */ public function access($cron_key) { - // @TODO: \Drupal calls should be avoided in classes. - // Replace \Drupal::config with dependency injection? - $valid_cron_key = \Drupal::config('scheduler.settings') + $valid_cron_key = $this->config('scheduler.settings') ->get('lightweight_cron_access_key'); return AccessResult::allowedIf($valid_cron_key == $cron_key); } diff --git a/web/modules/scheduler/src/Form/SchedulerAdminForm.php b/web/modules/scheduler/src/Form/SchedulerAdminForm.php index 5ae7061bbd..4d00198157 100644 --- a/web/modules/scheduler/src/Form/SchedulerAdminForm.php +++ b/web/modules/scheduler/src/Form/SchedulerAdminForm.php @@ -100,7 +100,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#description' => $this->t('This is the time that will be used if the user does not enter a value. Format: HH:MM:SS.'), '#states' => [ 'visible' => [ - ':input[name="scheduler_allow_date_only"]' => ['checked' => TRUE], + ':input[name="allow_date_only"]' => ['checked' => TRUE], ], ], ]; diff --git a/web/modules/scheduler/src/Form/SchedulerCronForm.php b/web/modules/scheduler/src/Form/SchedulerCronForm.php index f9abb55880..41eef3bd0f 100644 --- a/web/modules/scheduler/src/Form/SchedulerCronForm.php +++ b/web/modules/scheduler/src/Form/SchedulerCronForm.php @@ -2,6 +2,7 @@ namespace Drupal\scheduler\Form; +use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Form\FormStateInterface; @@ -24,19 +25,22 @@ class SchedulerCronForm extends ConfigFormBase { /** * The scheduler manager service. * - * @var Drupal\scheduler\SchedulerManager + * @var \Drupal\scheduler\SchedulerManager */ protected $schedulerManager; /** * Creates a SchedulerCronForm instance. * + * @var \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The factory for configuration objects. * @var \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * The module handler service. - * @var Drupal\scheduler\SchedulerManager $scheduler_manager + * @var \Drupal\scheduler\SchedulerManager $scheduler_manager * The scheduler manager service. */ - public function __construct(ModuleHandlerInterface $module_handler, SchedulerManager $scheduler_manager) { + public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, SchedulerManager $scheduler_manager) { + parent::__construct($config_factory); $this->moduleHandler = $module_handler; $this->schedulerManager = $scheduler_manager; } @@ -45,7 +49,11 @@ public function __construct(ModuleHandlerInterface $module_handler, SchedulerMan * {@inheritdoc} */ public static function create(ContainerInterface $container) { - return new static($container->get('module_handler'), $container->get('scheduler.manager')); + return new static( + $container->get('config.factory'), + $container->get('module_handler'), + $container->get('scheduler.manager') + ); } /** @@ -147,7 +155,7 @@ public function generateRandomKey(array &$form, FormStateInterface $form_state) * The current state of the form. */ public function runLightweightCron(array &$form, FormStateInterface $form_state) { - $this->schedulerManager->runLightweightCron(); + $this->schedulerManager->runLightweightCron(['admin_form' => TRUE]); if ($this->moduleHandler->moduleExists('dblog')) { $url = Url::fromRoute('dblog.overview')->toString(); @@ -158,9 +166,7 @@ public function runLightweightCron(array &$form, FormStateInterface $form_state) // overview does not exist. Show a simple status message. $message = $this->t('Lightweight cron run completed.'); } - // @todo Replace drupal_set_message() with an injectable service in 8.1.x. - // @see https://www.drupal.org/node/2278383 - drupal_set_message($message); + $this->messenger()->addMessage($message); } } diff --git a/web/modules/scheduler/src/Plugin/Validation/Constraint/SchedulerPublishOnConstraintValidator.php b/web/modules/scheduler/src/Plugin/Validation/Constraint/SchedulerPublishOnConstraintValidator.php index 51ccff9c5a..b4963e1dd9 100644 --- a/web/modules/scheduler/src/Plugin/Validation/Constraint/SchedulerPublishOnConstraintValidator.php +++ b/web/modules/scheduler/src/Plugin/Validation/Constraint/SchedulerPublishOnConstraintValidator.php @@ -18,7 +18,7 @@ public function validate($entity, Constraint $constraint) { $default_publish_past_date = \Drupal::config('scheduler.settings')->get('default_publish_past_date'); $scheduler_publish_past_date = $entity->getEntity()->type->entity->getThirdPartySetting('scheduler', 'publish_past_date', $default_publish_past_date); - if ($publish_on && $scheduler_publish_past_date == 'error' && $publish_on < REQUEST_TIME) { + if ($publish_on && $scheduler_publish_past_date == 'error' && $publish_on < \Drupal::time()->getRequestTime()) { $this->context->buildViolation($constraint->messagePublishOnDateNotInFuture) ->atPath('publish_on') ->addViolation(); diff --git a/web/modules/scheduler/src/Plugin/Validation/Constraint/SchedulerUnpublishOnConstraintValidator.php b/web/modules/scheduler/src/Plugin/Validation/Constraint/SchedulerUnpublishOnConstraintValidator.php index 05da8014ff..62464450b3 100644 --- a/web/modules/scheduler/src/Plugin/Validation/Constraint/SchedulerUnpublishOnConstraintValidator.php +++ b/web/modules/scheduler/src/Plugin/Validation/Constraint/SchedulerUnpublishOnConstraintValidator.php @@ -40,7 +40,7 @@ public function validate($entity, Constraint $constraint) { // Check that the unpublish-on date is in the future. Unlike the publish-on // field, there is no option to use a past date, as this is not relevant for // unpublshing. The date must ALWAYS be in the future if it is entered. - if ($unpublish_on && $unpublish_on < REQUEST_TIME) { + if ($unpublish_on && $unpublish_on < \Drupal::time()->getRequestTime()) { $this->context->buildViolation($constraint->messageUnpublishOnDateNotInFuture) ->atPath('unpublish_on') ->addViolation(); diff --git a/web/modules/scheduler/src/SchedulerEvent.php b/web/modules/scheduler/src/SchedulerEvent.php index ca857e4750..b5d8fac9b9 100644 --- a/web/modules/scheduler/src/SchedulerEvent.php +++ b/web/modules/scheduler/src/SchedulerEvent.php @@ -13,7 +13,7 @@ class SchedulerEvent extends Event { /** * Node object. * - * @var Drupal\Core\Entity\EntityInterface + * @var \Drupal\Core\Entity\EntityInterface */ protected $node; diff --git a/web/modules/scheduler/src/SchedulerManager.php b/web/modules/scheduler/src/SchedulerManager.php index de17b181a3..2cc5dccbda 100644 --- a/web/modules/scheduler/src/SchedulerManager.php +++ b/web/modules/scheduler/src/SchedulerManager.php @@ -2,26 +2,31 @@ namespace Drupal\scheduler; -use Drupal\Core\Config\ConfigFactory; -use Drupal\Core\Datetime\DateFormatter; -use Drupal\Core\Entity\EntityManager; +use Drupal\Component\Datetime\TimeInterface; +use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Datetime\DateFormatterInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\Link; +use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\Url; -use Drupal\Core\Extension\ModuleHandler; -use Drupal\node\Entity\Node; use Drupal\node\NodeInterface; use Drupal\scheduler\Exception\SchedulerMissingDateException; use Drupal\scheduler\Exception\SchedulerNodeTypeNotEnabledException; use Psr\Log\LoggerInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** * Defines a scheduler manager. */ class SchedulerManager { + use StringTranslationTrait; + /** * Date formatter service object. * - * @var \Drupal\Core\Datetime\DateFormatter + * @var \Drupal\Core\Datetime\DateFormatterInterface */ protected $dateFormatter; @@ -35,33 +40,49 @@ class SchedulerManager { /** * Module handler service object. * - * @var \Drupal\Core\Extension\ModuleHandler + * @var \Drupal\Core\Extension\ModuleHandlerInterface */ protected $moduleHandler; /** - * Entity Manager service object. + * Entity Type Manager service object. * - * @var \Drupal\Core\Entity\EntityManager + * @var \Drupal\Core\Entity\EntityTypeManagerInterface */ - protected $entityManager; + protected $entityTypeManager; /** * Config Factory service object. * - * @var \Drupal\Core\Config\ConfigFactory + * @var \Drupal\Core\Config\ConfigFactoryInterface */ protected $configFactory; + /** + * The event dispatcher. + * + * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface + */ + protected $eventDispatcher; + + /** + * The time service. + * + * @var \Drupal\Component\Datetime\TimeInterface + */ + protected $time; + /** * Constructs a SchedulerManager object. */ - public function __construct(DateFormatter $dateFormatter, LoggerInterface $logger, ModuleHandler $moduleHandler, EntityManager $entityManager, ConfigFactory $configFactory) { + public function __construct(DateFormatterInterface $dateFormatter, LoggerInterface $logger, ModuleHandlerInterface $moduleHandler, EntityTypeManagerInterface $entityTypeManager, ConfigFactoryInterface $configFactory, EventDispatcherInterface $eventDispatcher, TimeInterface $time) { $this->dateFormatter = $dateFormatter; $this->logger = $logger; $this->moduleHandler = $moduleHandler; - $this->entityManager = $entityManager; + $this->entityTypeManager = $entityTypeManager; $this->configFactory = $configFactory; + $this->eventDispatcher = $eventDispatcher; + $this->time = $time; } /** @@ -74,11 +95,6 @@ public function __construct(DateFormatter $dateFormatter, LoggerInterface $logge * @throws \Drupal\scheduler\Exception\SchedulerNodeTypeNotEnabledException */ public function publish() { - // @TODO: \Drupal calls should be avoided in classes. - // Replace \Drupal::service with dependency injection? - /** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher */ - $dispatcher = \Drupal::service('event_dispatcher'); - $result = FALSE; $action = 'publish'; @@ -87,12 +103,11 @@ public function publish() { $nids = []; $scheduler_enabled_types = array_keys(_scheduler_get_scheduler_enabled_node_types($action)); if (!empty($scheduler_enabled_types)) { - // @TODO: \Drupal calls should be avoided in classes. - // Replace \Drupal::entityQuery with dependency injection? - $query = \Drupal::entityQuery('node') + $query = $this->entityTypeManager->getStorage('node')->getQuery() ->exists('publish_on') - ->condition('publish_on', REQUEST_TIME, '<=') + ->condition('publish_on', $this->time->getRequestTime(), '<=') ->condition('type', $scheduler_enabled_types, 'IN') + ->latestRevision() ->sort('publish_on') ->sort('nid'); // Disable access checks for this query. @@ -111,9 +126,8 @@ public function publish() { // unlike 7.x where each translation was a separate node. This means that // the list of node ids returned above may have some translations that need // processing now and others that do not. - $nodes = Node::loadMultiple($nids); - // @TODO: Node::loadMultiple calls should be avoided in classes. - // Replace with dependency injection? + /** @var \Drupal\node\NodeInterface[] $nodes */ + $nodes = $this->loadNodes($nids); foreach ($nodes as $node_multilingual) { // The API calls could return nodes of types which are not enabled for @@ -131,7 +145,7 @@ public function publish() { // If the current translation does not have a publish on value, or it is // later than the date we are processing then move on to the next. $publish_on = $node->publish_on->value; - if (empty($publish_on) || $publish_on > REQUEST_TIME) { + if (empty($publish_on) || $publish_on > $this->time->getRequestTime()) { continue; } @@ -140,13 +154,13 @@ public function publish() { continue; } - // $node->set('changed', $publish_on) will fail badly if an API call has + // $node->setChangedTime($publish_on) will fail badly if an API call has // removed the date. Trap this as an exception here and give a // meaningful message. // @TODO This will now never be thrown due to the empty(publish_on) // check above to cater for translations. Remove this exception? if (empty($node->publish_on->value)) { - $field_definitions = $this->entityManager->getFieldDefinitions('node', $node->getType()); + $field_definitions = $this->entityTypeManager->getFieldDefinitions('node', $node->getType()); $field = (string) $field_definitions['publish_on']->getLabel(); throw new SchedulerMissingDateException(sprintf("Node %d '%s' will not be published because field '%s' has no value", $node->id(), $node->getTitle(), $field)); } @@ -154,46 +168,79 @@ public function publish() { // Trigger the PRE_PUBLISH event so that modules can react before the // node is published. $event = new SchedulerEvent($node); - $dispatcher->dispatch(SchedulerEvents::PRE_PUBLISH, $event); + $this->eventDispatcher->dispatch(SchedulerEvents::PRE_PUBLISH, $event); $node = $event->getNode(); - // Update timestamps. - $node->set('changed', $publish_on); + // Update 'changed' timestamp. + $node->setChangedTime($publish_on); $old_creation_date = $node->getCreatedTime(); - if ($node->type->entity->getThirdPartySetting('scheduler', 'publish_touch', $this->setting('default_publish_touch'))) { + $msg_extra = ''; + // If required, set the created date to match published date. + if ($node->type->entity->getThirdPartySetting('scheduler', 'publish_touch', $this->setting('default_publish_touch')) || + ($node->getCreatedTime() > $publish_on && $node->type->entity->getThirdPartySetting('scheduler', 'publish_past_date_created', $this->setting('default_publish_past_date_created'))) + ) { $node->setCreatedTime($publish_on); + $msg_extra = $this->t('The previous creation date was @old_creation_date, now updated to match the publishing date.', [ + '@old_creation_date' => $this->dateFormatter->format($old_creation_date, 'short'), + ]); } $create_publishing_revision = $node->type->entity->getThirdPartySetting('scheduler', 'publish_revision', $this->setting('default_publish_revision')); if ($create_publishing_revision) { $node->setNewRevision(); // Use a core date format to guarantee a time is included. - // @TODO: 't' calls should be avoided in classes. - // Replace with dependency injection? - $node->revision_log = t('Node published by Scheduler on @now. Previous creation date was @date.', [ - '@now' => $this->dateFormatter->format(REQUEST_TIME, 'short'), - '@date' => $this->dateFormatter->format($old_creation_date, 'short'), - ]); + $revision_log_message = rtrim($this->t('Published by Scheduler. The scheduled publishing date was @publish_on.', [ + '@publish_on' => $this->dateFormatter->format($publish_on, 'short'), + ]) . ' ' . $msg_extra); + $node->setRevisionLogMessage($revision_log_message) + ->setRevisionCreationTime($this->time->getRequestTime()); } // Unset publish_on so the node will not get rescheduled by subsequent // calls to $node->save(). $node->publish_on->value = NULL; + // Invoke all implementations of hook_scheduler_publish_action() to + // allow other modules to do the "publishing" process instead of + // Scheduler. + $hook = 'scheduler_publish_action'; + $processed = FALSE; + $failed = FALSE; + foreach ($this->moduleHandler->getImplementations($hook) as $module) { + $function = $module . '_' . $hook; + $return = $function($node); + $processed = $processed || ($return === 1); + $failed = $failed || ($return === -1); + } + // Log the fact that a scheduled publication is about to take place. - $view_link = $node->link(t('View node')); - $nodetype_url = Url::fromRoute('entity.node_type.edit_form', ['node_type' => $node->getType()]); - // @TODO: \Drupal calls should be avoided in classes. - // Replace \Drupal::l with dependency injection? - $nodetype_link = \Drupal::l(node_get_type_label($node) . ' ' . t('settings'), $nodetype_url); + $view_link = $node->toLink($this->t('View node')); + $node_type = $this->entityTypeManager->getStorage('node_type')->load($node->bundle()); + $node_type_link = $node_type->toLink($this->t('@label settings', ['@label' => $node_type->label()]), 'edit-form'); $logger_variables = [ - '@type' => node_get_type_label($node), + '@type' => $node_type->label(), '%title' => $node->getTitle(), - 'link' => $nodetype_link . ' ' . $view_link, + 'link' => $node_type_link->toString() . ' ' . $view_link->toString(), + '@hook' => 'hook_' . $hook, ]; - $this->logger->notice('@type: scheduled publishing of %title.', $logger_variables); - // Use the actions system to publish the node. - $this->entityManager->getStorage('action')->load('node_publish_action')->getPlugin()->execute($node); + if ($failed) { + // At least one hook function returned a failure or exception, so stop + // processing this node and move on to the next one. + $this->logger->warning('Publishing failed for %title. Calls to @hook returned a failure code.', $logger_variables); + continue; + } + elseif ($processed) { + // The node had 'publishing' processed by a module implementing the + // hook, so no need to do anything more, apart from log this result. + $this->logger->notice('@type: scheduled processing of %title completed by calls to @hook.', $logger_variables); + } + else { + // None of the above hook calls processed the node and there were no + // errors detected so fall back to the standard actions system to + // publish the node. + $this->logger->notice('@type: scheduled publishing of %title.', $logger_variables); + $this->entityTypeManager->getStorage('action')->load('node_publish_action')->getPlugin()->execute($node); + } // Invoke the event to tell Rules that Scheduler has published the node. if ($this->moduleHandler->moduleExists('scheduler_rules_integration')) { @@ -203,7 +250,7 @@ public function publish() { // Trigger the PUBLISH event so that modules can react after the node is // published. $event = new SchedulerEvent($node); - $dispatcher->dispatch(SchedulerEvents::PUBLISH, $event); + $this->eventDispatcher->dispatch(SchedulerEvents::PUBLISH, $event); $event->getNode()->save(); $result = TRUE; @@ -223,11 +270,6 @@ public function publish() { * @throws \Drupal\scheduler\Exception\SchedulerNodeTypeNotEnabledException */ public function unpublish() { - // @TODO: \Drupal calls should be avoided in classes. - // Replace \Drupal::service with dependency injection? - /** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher */ - $dispatcher = \Drupal::service('event_dispatcher'); - $result = FALSE; $action = 'unpublish'; @@ -236,12 +278,11 @@ public function unpublish() { $nids = []; $scheduler_enabled_types = array_keys(_scheduler_get_scheduler_enabled_node_types($action)); if (!empty($scheduler_enabled_types)) { - // @TODO: \Drupal calls should be avoided in classes. - // Replace \Drupal::entityQuery with dependency injection? - $query = \Drupal::entityQuery('node') + $query = $this->entityTypeManager->getStorage('node')->getQuery() ->exists('unpublish_on') - ->condition('unpublish_on', REQUEST_TIME, '<=') + ->condition('unpublish_on', $this->time->getRequestTime(), '<=') ->condition('type', $scheduler_enabled_types, 'IN') + ->latestRevision() ->sort('unpublish_on') ->sort('nid'); // Disable access checks for this query. @@ -256,9 +297,8 @@ public function unpublish() { // Allow other modules to alter the list of nodes to be unpublished. $this->moduleHandler->alter('scheduler_nid_list', $nids, $action); - // @TODO: Node::loadMultiple calls should be avoided in classes. - // Replace with dependency injection? - $nodes = Node::loadMultiple($nids); + /** @var \Drupal\node\NodeInterface[] $nodes */ + $nodes = $this->loadNodes($nids); foreach ($nodes as $node_multilingual) { // The API calls could return nodes of types which are not enabled for // scheduled unpublishing. Do not process these. @@ -274,7 +314,7 @@ public function unpublish() { // If the current translation does not have an unpublish on value, or it // is later than the date we are processing then move on to the next. $unpublish_on = $node->unpublish_on->value; - if (empty($unpublish_on) || $unpublish_on > REQUEST_TIME) { + if (empty($unpublish_on) || $unpublish_on > $this->time->getRequestTime()) { continue; } @@ -283,7 +323,7 @@ public function unpublish() { // by one of the hook functions we provide, and is still being blocked // now that the unpublishing time has been reached. $publish_on = $node->publish_on->value; - if (!empty($publish_on) && $publish_on <= REQUEST_TIME) { + if (!empty($publish_on) && $publish_on <= $this->time->getRequestTime()) { continue; } @@ -292,13 +332,13 @@ public function unpublish() { continue; } - // $node->set('changed', $unpublish_on) will fail badly if an API call + // $node->setChangedTime($unpublish_on) will fail badly if an API call // has removed the date. Trap this as an exception here and give a // meaningful message. // @TODO This will now never be thrown due to the empty(unpublish_on) // check above to cater for translations. Remove this exception? if (empty($unpublish_on)) { - $field_definitions = $this->entityManager->getFieldDefinitions('node', $node->getType()); + $field_definitions = $this->entityTypeManager->getFieldDefinitions('node', $node->getType()); $field = (string) $field_definitions['unpublish_on']->getLabel(); throw new SchedulerMissingDateException(sprintf("Node %d '%s' will not be unpublished because field '%s' has no value", $node->id(), $node->getTitle(), $field)); } @@ -306,53 +346,79 @@ public function unpublish() { // Trigger the PRE_UNPUBLISH event so that modules can react before the // node is unpublished. $event = new SchedulerEvent($node); - $dispatcher->dispatch(SchedulerEvents::PRE_UNPUBLISH, $event); + $this->eventDispatcher->dispatch(SchedulerEvents::PRE_UNPUBLISH, $event); $node = $event->getNode(); - // Update timestamps. - $old_change_date = $node->getChangedTime(); - $node->set('changed', $unpublish_on); + // Update 'changed' timestamp. + $node->setChangedTime($unpublish_on); $create_unpublishing_revision = $node->type->entity->getThirdPartySetting('scheduler', 'unpublish_revision', $this->setting('default_unpublish_revision')); if ($create_unpublishing_revision) { $node->setNewRevision(); // Use a core date format to guarantee a time is included. - // @TODO: 't' calls should be avoided in classes. - // Replace with dependency injection? - $node->revision_log = t('Node unpublished by Scheduler on @now. Previous change date was @date.', [ - '@now' => $this->dateFormatter->format(REQUEST_TIME, 'short'), - '@date' => $this->dateFormatter->format($old_change_date, 'short'), + $revision_log_message = $this->t('Unpublished by Scheduler. The scheduled unpublishing date was @unpublish_on.', [ + '@unpublish_on' => $this->dateFormatter->format($unpublish_on, 'short'), ]); + // Create the new revision, setting message and revision timestamp. + $node->setRevisionLogMessage($revision_log_message) + ->setRevisionCreationTime($this->time->getRequestTime()); } // Unset unpublish_on so the node will not get rescheduled by subsequent - // calls to $node->save(). Save the value for use when calling Rules. + // calls to $node->save(). $node->unpublish_on->value = NULL; - // Log the fact that a scheduled unpublication is about to take place. - $view_link = $node->link(t('View node')); - $nodetype_url = Url::fromRoute('entity.node_type.edit_form', ['node_type' => $node->getType()]); - // @TODO: \Drupal calls should be avoided in classes. - // Replace \Drupal::l with dependency injection? - $nodetype_link = \Drupal::l(node_get_type_label($node) . ' ' . t('settings'), $nodetype_url); + // Invoke all implementations of hook_scheduler_unpublish_action() to + // allow other modules to do the "unpublishing" process instead of + // Scheduler. + $hook = 'scheduler_unpublish_action'; + $processed = FALSE; + $failed = FALSE; + foreach ($this->moduleHandler->getImplementations($hook) as $module) { + $function = $module . '_' . $hook; + $return = $function($node); + $processed = $processed || ($return === 1); + $failed = $failed || ($return === -1); + } + + // Set up the log variables. + $view_link = $node->toLink($this->t('View node')); + $node_type = $this->entityTypeManager->getStorage('node_type')->load($node->bundle()); + $node_type_link = $node_type->toLink($this->t('@label settings', ['@label' => $node_type->label()]), 'edit-form'); $logger_variables = [ - '@type' => node_get_type_label($node), + '@type' => $node_type->label(), '%title' => $node->getTitle(), - 'link' => $nodetype_link . ' ' . $view_link, + 'link' => $node_type_link->toString() . ' ' . $view_link->toString(), + '@hook' => 'hook_' . $hook, ]; - $this->logger->notice('@type: scheduled unpublishing of %title.', $logger_variables); - // Use the actions system to publish the node. - $this->entityManager->getStorage('action')->load('node_unpublish_action')->getPlugin()->execute($node); + if ($failed) { + // At least one hook function returned a failure or exception, so stop + // processing this node and move on to the next one. + $this->logger->warning('Unpublishing failed for %title. Calls to @hook returned a failure code.', $logger_variables); + continue; + } + elseif ($processed) { + // The node has 'unpublishing' processed by a module implementing the + // hook, so no need to do anything more, apart from log this result. + $this->logger->notice('@type: scheduled processing of %title completed by calls to @hook.', $logger_variables); + } + else { + // None of the above hook calls processed the node and there were no + // errors detected so fall back to the standard actions system to + // unpublish the node. + $this->logger->notice('@type: scheduled unpublishing of %title.', $logger_variables); + $this->entityTypeManager->getStorage('action')->load('node_unpublish_action')->getPlugin()->execute($node); + } // Invoke event to tell Rules that Scheduler has unpublished this node. if ($this->moduleHandler->moduleExists('scheduler_rules_integration')) { _scheduler_rules_integration_dispatch_cron_event($node, 'unpublish'); } - // Trigger the UNPUBLISH event so that modules can react before the node + // Trigger the UNPUBLISH event so that modules can react after the node // is unpublished. $event = new SchedulerEvent($node); - $dispatcher->dispatch(SchedulerEvents::UNPUBLISH, $event); + $this->eventDispatcher->dispatch(SchedulerEvents::UNPUBLISH, $event); $event->getNode()->save(); $result = TRUE; @@ -426,11 +492,25 @@ public function nidList($action) { * This function is called from the external crontab job via url * /scheduler/cron/{access key} or it can be run interactively from the * Scheduler configuration page at /admin/config/content/scheduler/cron. + * It is also executed when running Scheduler Cron via drush. + * + * @param array $options + * Options passed from drush command or admin form. */ - public function runLightweightCron() { - $log = $this->setting('log'); + public function runLightweightCron(array $options = []) { + // When calling via drush the log messages can be avoided by using --nolog. + $log = $this->setting('log') && empty($options['nolog']); if ($log) { - $this->logger->notice('Lightweight cron run activated.'); + if (array_key_exists('nolog', $options)) { + $trigger = 'drush command'; + } + elseif (array_key_exists('admin_form', $options)) { + $trigger = 'admin user form'; + } + else { + $trigger = 'url'; + } + $this->logger->notice('Lightweight cron run activated by @trigger.', ['@trigger' => $trigger]); } scheduler_cron(); if (ob_get_level() > 0) { @@ -440,9 +520,8 @@ public function runLightweightCron() { } } if ($log) { - // @TODO: \Drupal calls should be avoided in classes. - // Replace \Drupal::l with dependency injection? - $this->logger->notice('Lightweight cron run completed.', ['link' => \Drupal::l(t('settings'), Url::fromRoute('scheduler.cron_form'))]); + $link = Link::fromTextAndUrl($this->t('settings'), Url::fromRoute('scheduler.cron_form')); + $this->logger->notice('Lightweight cron run completed.', ['link' => $link->toString()]); } } @@ -459,4 +538,31 @@ protected function setting($key) { return $this->configFactory->get('scheduler.settings')->get($key); } + /** + * Helper method to load latest revision of each node. + * + * @param array $nids + * Array of node ids. + * + * @return array + * Array of loaded nodes. + * + * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException + * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException + */ + protected function loadNodes(array $nids) { + $node_storage = $this->entityTypeManager->getStorage('node'); + $nodes = []; + + // Load the latest revision for each node. + foreach ($nids as $nid) { + $node = $node_storage->load($nid); + $revision_ids = $node_storage->revisionIds($node); + $vid = end($revision_ids); + $nodes[] = $node_storage->loadRevision($vid); + } + + return $nodes; + } + } diff --git a/web/modules/scheduler/tests/modules/scheduler_access_test/scheduler_access_test.info.yml b/web/modules/scheduler/tests/modules/scheduler_access_test/scheduler_access_test.info.yml index e23f609c9c..2a67f10fe1 100644 --- a/web/modules/scheduler/tests/modules/scheduler_access_test/scheduler_access_test.info.yml +++ b/web/modules/scheduler/tests/modules/scheduler_access_test/scheduler_access_test.info.yml @@ -2,12 +2,11 @@ name: 'Scheduler Node Access Test' type: module description: 'Support module for Scheduler restricted node access testing.' package: Testing -# core: 8.x +core: 8.x dependencies: - scheduler:scheduler -# Information added by Drupal.org packaging script on 2017-11-14 -version: '8.x-1.0' -core: '8.x' +# Information added by Drupal.org packaging script on 2019-08-23 +version: '8.x-1.1' project: 'scheduler' -datestamp: 1510690392 +datestamp: 1566566892 diff --git a/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_test.info.yml b/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_test.info.yml index 1427f62a70..28cf6d1a03 100644 --- a/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_test.info.yml +++ b/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_test.info.yml @@ -2,12 +2,11 @@ name: 'Scheduler API Test' type: module description: 'Support module for Scheduler API-related testing.' package: Testing -# core: 8.x +core: 8.x dependencies: - scheduler:scheduler -# Information added by Drupal.org packaging script on 2017-11-14 -version: '8.x-1.0' -core: '8.x' +# Information added by Drupal.org packaging script on 2019-08-23 +version: '8.x-1.1' project: 'scheduler' -datestamp: 1510690392 +datestamp: 1566566892 diff --git a/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_test.install b/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_test.install index d1509db922..b3861648cf 100644 --- a/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_test.install +++ b/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_test.install @@ -22,7 +22,7 @@ function scheduler_api_test_uninstall() { ->execute(); if ($nids = $nids_query->fetchCol()) { entity_delete_multiple('node', $nids); - drupal_set_message(t('@number %type node(s) have been deleted.', [ + \Drupal::messenger()->addMessage(t('@number %type node(s) have been deleted.', [ '@number' => count($nids), '%type' => 'scheduler_api_test', ])); diff --git a/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_test.module b/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_test.module index 70f0286c2a..30e035ab10 100644 --- a/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_test.module +++ b/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_test.module @@ -6,6 +6,7 @@ */ use Drupal\node\Entity\Node; +use Drupal\node\NodeInterface; /** * Implements hook_scheduler_nid_list(). @@ -16,18 +17,18 @@ function scheduler_api_test_scheduler_nid_list($action) { // Check to see what test nodes exist. $query = \Drupal::entityQuery('node'); $nodes = Node::loadMultiple($query->execute()); - + $request_time = \Drupal::time()->getRequestTime(); foreach ($nodes as $nid => $node) { // If publishing and this is the publish test node, set a date and add // the node id to the list. if ($action == 'publish' && $node->title->value == 'API TEST nid_list publish me') { - $node->set('publish_on', REQUEST_TIME)->save(); + $node->set('publish_on', $request_time)->save(); $nids[] = $nid; } // If unpublishing and this is the unpublish test node, set a date and add // the node id to the list. if ($action == 'unpublish' && $node->title->value == 'API TEST nid_list unpublish me') { - $node->set('unpublish_on', REQUEST_TIME)->save(); + $node->set('unpublish_on', $request_time)->save(); $nids[] = $nid; } } @@ -40,7 +41,7 @@ function scheduler_api_test_scheduler_nid_list($action) { function scheduler_api_test_scheduler_nid_list_alter(&$nids, $action) { $query = \Drupal::entityQuery('node'); $nodes = Node::loadMultiple($query->execute()); - + $request_time = \Drupal::time()->getRequestTime(); foreach ($nodes as $nid => $node) { if ($action == 'publish' && $node->title->value == 'API TEST nid_list_alter do not publish me') { // Remove the node id. @@ -48,7 +49,7 @@ function scheduler_api_test_scheduler_nid_list_alter(&$nids, $action) { } if ($action == 'publish' && $node->title->value == 'API TEST nid_list_alter publish me') { // Set a publish_on date and add the node id. - $node->set('publish_on', REQUEST_TIME)->save(); + $node->set('publish_on', $request_time)->save(); $nids[] = $nid; } if ($action == 'unpublish' && $node->title->value == 'API TEST nid_list_alter do not unpublish me') { @@ -57,7 +58,7 @@ function scheduler_api_test_scheduler_nid_list_alter(&$nids, $action) { } if ($action == 'unpublish' && $node->title->value == 'API TEST nid_list_alter unpublish me') { // Set an unpublish_on date and add the node id. - $node->set('unpublish_on', REQUEST_TIME)->save(); + $node->set('unpublish_on', $request_time)->save(); $nids[] = $nid; } } @@ -67,7 +68,7 @@ function scheduler_api_test_scheduler_nid_list_alter(&$nids, $action) { /** * Implements hook_scheduler_allow_publishing(). */ -function scheduler_api_test_scheduler_allow_publishing($node) { +function scheduler_api_test_scheduler_allow_publishing(NodeInterface $node) { // If there is no 'Approved for Publishing' field then allow publishing. if (!isset($node->field_approved_publishing)) { $allowed = TRUE; @@ -77,14 +78,14 @@ function scheduler_api_test_scheduler_allow_publishing($node) { $allowed = $node->field_approved_publishing->value; // If publication is denied then inform the user why. if (!$allowed) { - drupal_set_message(t('%title is scheduled for publishing, but will not be published until approved.', ['%title' => $node->title->value]), 'status', FALSE); + \Drupal::messenger()->addMessage(t('%title is scheduled for publishing, but will not be published until approved.', ['%title' => $node->title->value]), 'status', FALSE); // If the time is in the past it means that the action has been prevented. // Write a dblog message to show this. Give a link to view the node but // cater for no nid as the node may be new and not yet saved. - if ($node->publish_on->value <= REQUEST_TIME) { + if ($node->publish_on->value <= \Drupal::time()->getRequestTime()) { \Drupal::logger('scheduler_api_test')->warning('Publishing of "%title" is prevented until approved.', [ '%title' => $node->title->value, - 'link' => $node->id() ? $node->link(t('View node')) : '', + 'link' => $node->id() ? $node->toLink(t('View node'))->toString() : '', ]); } } @@ -95,7 +96,7 @@ function scheduler_api_test_scheduler_allow_publishing($node) { /** * Implements hook_scheduler_allow_unpublishing(). */ -function scheduler_api_test_scheduler_allow_unpublishing($node) { +function scheduler_api_test_scheduler_allow_unpublishing(NodeInterface $node) { // If there is no 'Approved for Unpublishing' field then allow unpublishing. if (!isset($node->field_approved_unpublishing)) { $allowed = TRUE; @@ -105,17 +106,89 @@ function scheduler_api_test_scheduler_allow_unpublishing($node) { $allowed = $node->field_approved_unpublishing->value; // If unpublication is denied then inform the user why. if (!$allowed) { - drupal_set_message(t('%title is scheduled for unpublishing, but will not be unpublished until approved.', ['%title' => $node->title->value]), 'status', FALSE); + \Drupal::messenger()->addMessage(t('%title is scheduled for unpublishing, but will not be unpublished until approved.', ['%title' => $node->title->value]), 'status', FALSE); // If the time is in the past it means that the action has been prevented. // Write a dblog message to show this. Give a link to view the node but // cater for no nid as the node may be new and not yet saved. - if ($node->unpublish_on->value <= REQUEST_TIME) { + if ($node->unpublish_on->value <= \Drupal::time()->getRequestTime()) { \Drupal::logger('scheduler_api_test')->warning('Unpublishing of "%title" is prevented until approved.', [ '%title' => $node->title->value, - 'link' => $node->id() ? $node->link(t('View node')) : '', + 'link' => $node->id() ? $node->toLink(t('View node'))->toString() : '', ]); } } } return $allowed; } + +/** + * Implements hook_scheduler_hide_publish_on_field(). + */ +function scheduler_api_test_scheduler_hide_publish_on_field($form, $form_state, $node) { + // Hide the publish_on field if the node title contains orange or green. + if (stristr($node->title->value, 'orange') || stristr($node->title->value, 'green')) { + \Drupal::messenger()->addMessage(t('Scheduler_Api_Test: The publish_on field is hidden for orange or green node titles.'), 'status', FALSE); + return TRUE; + } + else { + return FALSE; + } +} + +/** + * Implements hook_scheduler_hide_unpublish_on_field(). + */ +function scheduler_api_test_scheduler_hide_unpublish_on_field($form, $form_state, $node) { + // Hide the publish_on field if the node title contains yellow or green. + if (stristr($node->title->value, 'yellow') || stristr($node->title->value, 'green')) { + \Drupal::messenger()->addMessage(t('Scheduler_Api_Test: The unpublish_on field is hidden for yellow or green node titles.'), 'status', FALSE); + return TRUE; + } + else { + return FALSE; + } +} + +/** + * Implements hook_scheduler_publish_action(). + */ +function scheduler_api_test_scheduler_publish_action($node) { + if (stristr($node->title->value, 'red')) { + // Nodes with red in the title are simulated to cause a failure and should + // then be skipped by Scheduler. + $node->set('title', $node->title->value . ' - publishing failed in API test module'); + \Drupal::messenger()->addMessage(t('Scheduler_Api_Test: Red nodes should cause Scheduler to abandon publishing.'), 'status', FALSE); + return -1; + } + elseif (stristr($node->title->value, 'yellow')) { + // Nodes with yellow in the title are simulated to be processed by this + // hook, and will not be published by Scheduler. + $node->set('title', $node->title->value . ' - publishing processed by API test module'); + $node->setPublished(); + \Drupal::messenger()->addMessage(t('Scheduler_Api_Test: Yellow nodes should not have publishing processed by Scheduler.'), 'status', FALSE); + return 1; + } + return 0; +} + +/** + * Implements hook_scheduler_unpublish_action(). + */ +function scheduler_api_test_scheduler_unpublish_action($node) { + if (stristr($node->title->value, 'blue')) { + // Nodes with blue in the title are simulated to cause a failure and should + // then be skipped by Scheduler. + $node->set('title', $node->title->value . ' - unpublishing failed in API test module'); + \Drupal::messenger()->addMessage(t('Scheduler_Api_Test: Blue nodes should cause Scheduler to abandon unpublishing.'), 'status', FALSE); + return -1; + } + if (stristr($node->title->value, 'orange')) { + // Nodes with orange in the title are simulated to be processed by this + // hook, and will not be published by Scheduler. + $node->set('title', $node->title->value . ' - unpublishing processed by API test module'); + $node->setUnpublished(); + \Drupal::messenger()->addMessage(t('Scheduler_Api_Test: Orange nodes should not have unpublishing processed by Scheduler.'), 'status', FALSE); + return 1; + } + return 0; +} diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerAdminSettingsTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerAdminSettingsTest.php index 7c8a999793..4c90cc3466 100644 --- a/web/modules/scheduler/tests/src/Functional/SchedulerAdminSettingsTest.php +++ b/web/modules/scheduler/tests/src/Functional/SchedulerAdminSettingsTest.php @@ -29,20 +29,20 @@ public function testAdminSettings() { 'allow_date_only' => TRUE, 'default_time' => '6:30', ]; - $this->drupalPostForm('admin/config/content/scheduler', $settings, t('Save configuration')); + $this->drupalPostForm('admin/config/content/scheduler', $settings, 'Save configuration'); // Verify that the values have been saved correctly. $this->assertTrue($this->config('scheduler.settings')->get('allow_date_only'), 'The config setting for allow_date_only is stored correctly.'); - $this->assertEqual($this->config('scheduler.settings')->get('default_time'), $this->seconds_formatted, 'The config setting for default_time is stored correctly.'); + $this->assertEquals($this->seconds_formatted, $this->config('scheduler.settings')->get('default_time'), 'The config setting for default_time is stored correctly.'); // Try to save an invalid time value. $settings = [ 'allow_date_only' => TRUE, 'default_time' => '123', ]; - $this->drupalPostForm('admin/config/content/scheduler', $settings, t('Save configuration')); + $this->drupalPostForm('admin/config/content/scheduler', $settings, 'Save configuration'); // Verify that an error is displayed and the value has not been saved. - $this->assertEqual($this->config('scheduler.settings')->get('default_time'), $this->seconds_formatted, 'The config setting for default_time has not changed.'); + $this->assertEquals($this->seconds_formatted, $this->config('scheduler.settings')->get('default_time'), 'The config setting for default_time has not changed.'); $this->assertText('The default time should be in the format HH:MM:SS', 'When an invalid default time is entered the correct error message is displayed.'); // Show the status report, which includes the Scheduler timecheck. diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerApiTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerApiTest.php index 278beaadce..53fc962796 100644 --- a/web/modules/scheduler/tests/src/Functional/SchedulerApiTest.php +++ b/web/modules/scheduler/tests/src/Functional/SchedulerApiTest.php @@ -68,7 +68,7 @@ public function testAllowedPublishing() { 'publish_on[0][value][date]' => date('Y-m-d', time() + 3), 'publish_on[0][value][time]' => date('H:i:s', time() + 3), ]; - $this->drupalPostForm('node/add/' . $this->customName, $edit, t('Save')); + $this->drupalPostForm('node/add/' . $this->customName, $edit, 'Save'); $this->assertText('is scheduled for publishing, but will not be published until approved.', 'The message is shown when scheduling a node which is not yet allowed to be published.'); // Create a node that is scheduled but not approved for publication. Then @@ -105,7 +105,7 @@ public function testAllowedPublishing() { // Check that a node can be approved and published via edit form. $node = $this->createUnapprovedNode('publish_on'); - $this->drupalPostForm('node/' . $node->id() . '/edit', ['field_approved_publishing[value]' => '1'], t('Save')); + $this->drupalPostForm('node/' . $node->id() . '/edit', ['field_approved_publishing[value]' => '1'], 'Save'); $this->nodeStorage->resetCache([$node->id()]); $node = $this->nodeStorage->load($node->id()); $this->assertTrue($node->isPublished(), 'An approved node with a date in the past is published immediately after saving via edit form.'); @@ -135,7 +135,7 @@ public function testAllowedUnpublishing() { 'unpublish_on[0][value][date]' => date('Y-m-d', time() + 3), 'unpublish_on[0][value][time]' => date('H:i:s', time() + 3), ]; - $this->drupalPostForm('node/add/' . $this->customName, $edit, t('Save')); + $this->drupalPostForm('node/add/' . $this->customName, $edit, 'Save'); $this->assertText('is scheduled for unpublishing, but will not be unpublished until approved.', 'The message is shown when scheduling a node which is not yet allowed to be unpublished.'); // Create a node that is scheduled but not approved for unpublication. Then @@ -254,16 +254,16 @@ public function testApiNodeAction() { // Edit the node and set a publish-on date in the past. $edit = [ - 'publish_on[0][value][date]' => date('Y-m-d', strtotime('-2 day', REQUEST_TIME)), - 'publish_on[0][value][time]' => date('H:i:s', strtotime('-2 day', REQUEST_TIME)), + 'publish_on[0][value][date]' => date('Y-m-d', strtotime('-2 day', $this->requestTime)), + 'publish_on[0][value][time]' => date('H:i:s', strtotime('-2 day', $this->requestTime)), ]; - $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save')); + $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save'); // Verify that the values have been altered as expected. $this->nodeStorage->resetCache([$node->id()]); $node = $this->nodeStorage->load($node->id()); $this->assertTrue($node->isSticky(), 'API action "PRE_PUBLISH_IMMEDIATELY" has changed the node to sticky.'); $this->assertTrue($node->isPromoted(), 'API action "PUBLISH_IMMEDIATELY" has changed the node to promoted.'); - $this->assertEqual($node->title->value, 'Published immediately', 'API action "PUBLISH_IMMEDIATELY" has changed the node title correctly.'); + $this->assertEquals('Published immediately', $node->title->value, 'API action "PUBLISH_IMMEDIATELY" has changed the node title correctly.'); } /** @@ -377,4 +377,120 @@ public function testNidListAlter() { $this->assertFalse($node4->isPublished(), 'After cron, node 4 "' . $node4->title->value . '" is unpublished.'); } + /** + * Test the hooks which allow hiding of scheduler input fields. + * + * This covers hook_scheduler_hide_publish_on_field and + * hook_scheduler_hide_unpublish_on_field. + */ + public function testHideField() { + $this->drupalLogin($this->schedulerUser); + + // Create test nodes. + $node1 = $this->drupalCreateNode([ + 'type' => $this->type, + 'title' => 'Red will not have either field hidden', + ]); + $node2 = $this->drupalCreateNode([ + 'type' => $this->type, + 'title' => 'Orange will have the publish-on field hidden', + ]); + $node3 = $this->drupalCreateNode([ + 'type' => $this->type, + 'title' => 'Yellow will have the unpublish-on field hidden', + ]); + $node4 = $this->drupalCreateNode([ + 'type' => $this->type, + 'title' => 'Green will have both Scheduler fields hidden', + ]); + + // Node 1 'red' should have both fields displayed. + $this->drupalGet('node/' . $node1->id() . '/edit'); + $this->assertTrue($this->xpath('//input[@id = "edit-publish-on-0-value-date"]'), 'The red publish-on field is shown.'); + $this->assertTrue($this->xpath('//input[@id = "edit-unpublish-on-0-value-date"]'), 'The red unpublish-on field is shown.'); + + // Node 2 'orange' should have only the publish-on field hidden. + $this->drupalGet('node/' . $node2->id() . '/edit'); + $this->assertFalse($this->xpath('//input[@id = "edit-publish-on-0-value-date"]'), 'The orange publish-on field is hidden.'); + $this->assertTrue($this->xpath('//input[@id = "edit-unpublish-on-0-value-date"]'), 'The orange unpublish-on field is shown.'); + + // Node 3 'yellow' should have only the unpublish-on field hidden. + $this->drupalGet('node/' . $node3->id() . '/edit'); + $this->assertTrue($this->xpath('//input[@id = "edit-publish-on-0-value-date"]'), 'The yellow publish-on field is shown.'); + $this->assertFalse($this->xpath('//input[@id = "edit-unpublish-on-0-value-date"]'), 'The yellow unpublish-on field is hidden.'); + + // Node 4 'green' should have both publish-on and unpublish-on hidden. + $this->drupalGet('node/' . $node4->id() . '/edit'); + $this->assertFalse($this->xpath('//input[@id = "edit-publish-on-0-value-date"]'), 'The green publish-on field is hidden.'); + $this->assertFalse($this->xpath('//input[@id = "edit-unpublish-on-0-value-date"]'), 'The green unpublish-on field is hidden.'); + } + + /** + * Test when other modules process the publish and unpublish actions. + * + * This covers hook_scheduler_publish_action and + * hook_scheduler_unpublish_action. + */ + public function testHookPublishUnpublishAction() { + $this->drupalLogin($this->schedulerUser); + + // Create test nodes. + $node1 = $this->drupalCreateNode([ + 'type' => $this->type, + 'status' => FALSE, + 'title' => 'Red will cause a failure on publishing', + 'publish_on' => strtotime('-1 day'), + ]); + $node2 = $this->drupalCreateNode([ + 'type' => $this->type, + 'status' => TRUE, + 'title' => 'Orange will be unpublished by the API test module not Scheduler', + 'unpublish_on' => strtotime('-1 day'), + ]); + $node3 = $this->drupalCreateNode([ + 'type' => $this->type, + 'status' => FALSE, + 'title' => 'Yellow will be published by the API test module not Scheduler', + 'publish_on' => strtotime('-1 day'), + ]); + // 'green' nodes will have both fields hidden so is harder to test manually. + // Therefore introduce a different colour. + $node4 = $this->drupalCreateNode([ + 'type' => $this->type, + 'status' => TRUE, + 'title' => 'Blue will cause a failure on unpublishing', + 'unpublish_on' => strtotime('-1 day'), + ]); + + // Simulate a cron run. + scheduler_cron(); + + // Check the red node. + $this->nodeStorage->resetCache([$node1->id()]); + $node1 = $this->nodeStorage->load($node1->id()); + $this->assertFalse($node1->isPublished(), 'The red node is still unpublished.'); + $this->assertTrue($node1->publish_on->value, 'The red node still has a publish-on date.'); + + // Check the orange node. + $this->nodeStorage->resetCache([$node2->id()]); + $node2 = $this->nodeStorage->load($node2->id()); + $this->assertFalse($node2->isPublished(), 'The orange node was unpublished by the API test module.'); + $this->assertTrue(stristr($node2->title->value, 'unpublishing processed by API test module'), 'The orange node was processed by the API test module.'); + $this->assertFalse($node2->unpublish_on->value, 'The orange node no longer has an unpublish-on date.'); + + // Check the yellow node. + $this->nodeStorage->resetCache([$node3->id()]); + $node3 = $this->nodeStorage->load($node3->id()); + $this->assertTrue($node3->isPublished(), 'The yellow node was published by the API test module.'); + $this->assertTrue(stristr($node3->title->value, 'publishing processed by API test module'), 'The yellow node was processed by the API test module.'); + $this->assertFalse($node3->publish_on->value, 'The yellow node no longer has a publish-on date.'); + + // Check the blue node. + $this->nodeStorage->resetCache([$node4->id()]); + $node4 = $this->nodeStorage->load($node4->id()); + $this->assertTrue($node4->isPublished(), 'The green node is still published.'); + $this->assertTrue($node4->unpublish_on->value, 'The green node still has an unpublish-on date.'); + + } + } diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerBasicTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerBasicTest.php index 3e7920f696..3c569a7be3 100644 --- a/web/modules/scheduler/tests/src/Functional/SchedulerBasicTest.php +++ b/web/modules/scheduler/tests/src/Functional/SchedulerBasicTest.php @@ -14,16 +14,16 @@ class SchedulerBasicTest extends SchedulerBrowserTestBase { */ public function testPublishingAndUnpublishing() { // Login is required here before creating the publish_on date and time - // values so that date.formatter can utilise the current users timezone. The - // constraints receive values which have been converted using the users - // timezone so they need to be consistent. + // values so that $this->dateFormatter can utilise the current users + // timezone. The constraints receive values which have been converted using + // the users timezone so they need to be consistent. $this->drupalLogin($this->schedulerUser); // Create node values. Set time to one hour in the future. $edit = [ 'title[0][value]' => 'Publish This Node', - 'publish_on[0][value][date]' => \Drupal::service('date.formatter')->format(time() + 3600, 'custom', 'Y-m-d'), - 'publish_on[0][value][time]' => \Drupal::service('date.formatter')->format(time() + 3600, 'custom', 'H:i:s'), + 'publish_on[0][value][date]' => $this->dateFormatter->format(time() + 3600, 'custom', 'Y-m-d'), + 'publish_on[0][value][time]' => $this->dateFormatter->format(time() + 3600, 'custom', 'H:i:s'), ]; $this->helpTestScheduler($edit); @@ -43,7 +43,7 @@ public function testPublishingAndUnpublishing() { * Schedules content, runs cron and asserts status. */ protected function helpTestScheduler($edit) { - $this->drupalPostForm('node/add/' . $this->type, $edit, t('Save')); + $this->drupalPostForm('node/add/' . $this->type, $edit, 'Save'); // Verify that the node was created. $node = $this->drupalGetNodeByTitle($edit['title[0][value]']); $this->assertTrue($node, sprintf('"%s" was created sucessfully.', $edit['title[0][value]'])); @@ -63,7 +63,7 @@ protected function helpTestScheduler($edit) { } // Modify the scheduler field data to a time in the past, then run cron. - $node->$key = REQUEST_TIME - 1; + $node->$key = $this->requestTime - 1; $node->save(); $this->cronRun(); diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerBrowserTestBase.php b/web/modules/scheduler/tests/src/Functional/SchedulerBrowserTestBase.php index 828554a893..2ee9876e57 100644 --- a/web/modules/scheduler/tests/src/Functional/SchedulerBrowserTestBase.php +++ b/web/modules/scheduler/tests/src/Functional/SchedulerBrowserTestBase.php @@ -54,14 +54,14 @@ abstract class SchedulerBrowserTestBase extends BrowserTestBase { protected $typeName; /** - * The node type. + * The node type object. * * @var \Drupal\node\Entity\NodeType */ protected $nodetype; /** - * The node storage. + * The node storage object. * * @var \Drupal\Core\Entity\EntityStorageInterface */ @@ -74,18 +74,32 @@ abstract class SchedulerBrowserTestBase extends BrowserTestBase { */ protected $database; + /** + * The request time stored as interger for direct re-use in many tests. + * + * @var int + */ + protected $requestTime; + + /** + * The date formatter service. + * + * @var \Drupal\Core\Datetime\DateFormatterInterface + */ + protected $dateFormatter; + /** * {@inheritdoc} */ public function setUp() { parent::setUp(); - // Create a 'Basic Page' content type, with 'page' as the identifier. - // The test files should use $this->type and $this->typeName and not use + // Create a test content type with id 'testpage' and name 'Test Page'. + // The tests should use $this->type and $this->typeName and not use // $this->nodetype->get('type') or $this->nodetype->get('name'), nor have - // the hard-coded strings 'page' and 'Basic page'. - $this->type = 'page'; - $this->typeName = 'Basic page'; + // the hard-coded strings 'testpage' or 'Test Page'. + $this->type = 'testpage'; + $this->typeName = 'Test Page'; /** @var NodeTypeInterface $nodetype */ $this->nodetype = $this->drupalCreateContentType([ 'type' => $this->type, @@ -99,17 +113,17 @@ public function setUp() { // Define nodeStorage for use in many tests. /** @var EntityStorageInterface $nodeStorage */ - $this->nodeStorage = $this->container->get('entity.manager')->getStorage('node'); + $this->nodeStorage = $this->container->get('entity_type.manager')->getStorage('node'); // Create an administrator user having the main admin permissions, full - // rights on the 'page' content type and all of the Scheduler permissions. + // rights on the test content type and all of the Scheduler permissions. // 'access site reports' is required for admin/reports/dblog. // 'administer site configuration' is required for admin/reports/status. $this->adminUser = $this->drupalCreateUser([ - 'administer nodes', 'access content', 'access content overview', 'access site reports', + 'administer nodes', 'administer site configuration', 'create ' . $this->type . ' content', 'edit own ' . $this->type . ' content', @@ -134,6 +148,12 @@ public function setUp() { // Store the database connection for re-use in the actual tests. $this->database = $this->container->get('database'); + // Determine the request time and save for re-use in the actual tests. + $this->requestTime = $this->container->get('datetime.time')->getRequestTime(); + + // Store the core dateFormatter service for re-use in the actual tests. + $this->dateFormatter = $this->container->get('date.formatter'); + } /** diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerDefaultTimeTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerDefaultTimeTest.php index a0d0a22de9..a90807e8bb 100644 --- a/web/modules/scheduler/tests/src/Functional/SchedulerDefaultTimeTest.php +++ b/web/modules/scheduler/tests/src/Functional/SchedulerDefaultTimeTest.php @@ -2,6 +2,9 @@ namespace Drupal\Tests\scheduler\Functional; +use DateTime; +use DateInterval; + /** * Tests the default time functionality. * @@ -15,23 +18,25 @@ class SchedulerDefaultTimeTest extends SchedulerBrowserTestBase { public function testDefaultTime() { $this->drupalLogin($this->schedulerUser); $config = $this->config('scheduler.settings'); - $date_formatter = \Drupal::service('date.formatter'); - // For this test we use an offset of 6 hours 30 minutes (23400 seconds). - $seconds = 23400; + // For this test we use a default time of 6:30am. + $default_time = '06:30:00'; + $config->set('default_time', $default_time)->save(); + + // Create DateTime objects to hold the two scheduling dates. This is better + // than using raw unix timestamps because it caters for daylight-saving + // shifts properly. + // @see https://www.drupal.org/project/scheduler/issues/2957490 + $publish_time = new DateTime(); + $publish_time->add(new DateInterval('P1D'))->setTime(6, 30); - // If the test happens to be run at a time when '+1 day' puts the calculated - // publishing date into a different daylight-saving period then formatted - // time can be an hour different. To avoid these failures we use a fixed - // string when asserting the message and looking for field values. - // @see https://www.drupal.org/node/2809627 - $seconds_formatted = '06:30:00'; - $config->set('default_time', $seconds_formatted)->save(); + $unpublish_time = new DateTime(); + $unpublish_time->add(new DateInterval('P2D'))->setTime(6, 30); - // We cannot easily test the exact validation messages as they contain the - // REQUEST_TIME, which can be one or more seconds in the past. Best we can + // We cannot easily test the full validation message as they contain the + // current time which can be one or two seconds in the past. The best we can // do is check the fixed part of the message as it is when passed to t() in - // Datetime::validateDatetime. This will only work in English. + // Datetime::validateDatetime. Tests only needs to work in English anyway. $publish_validation_message = 'The Publish on date is invalid.'; $unpublish_validation_message = 'The Unpublish on date is invalid.'; @@ -41,11 +46,11 @@ public function testDefaultTime() { // Test that entering a time is required. $edit = [ 'title[0][value]' => 'No time ' . $this->randomString(15), - 'publish_on[0][value][date]' => $date_formatter->format(strtotime('+1 day', REQUEST_TIME), 'custom', 'Y-m-d'), - 'unpublish_on[0][value][date]' => $date_formatter->format(strtotime('+2 day', REQUEST_TIME), 'custom', 'Y-m-d'), + 'publish_on[0][value][date]' => $publish_time->format('Y-m-d'), + 'unpublish_on[0][value][date]' => $unpublish_time->format('Y-m-d'), ]; // Create a node and check that the expected error messages are shown. - $this->drupalPostForm('node/add/' . $this->type, $edit, t('Save')); + $this->drupalPostForm('node/add/' . $this->type, $edit, 'Save'); $this->assertSession()->pageTextContains($publish_validation_message, 'By default it is required to enter a time when scheduling content for publication.'); $this->assertSession()->pageTextContains($unpublish_validation_message, 'By default it is required to enter a time when scheduling content for unpublication.'); @@ -53,26 +58,27 @@ public function testDefaultTime() { $config->set('allow_date_only', TRUE)->save(); // Create a node and check that the expected error messages are not shown. - $this->drupalPostForm('node/add/' . $this->type, $edit, t('Save')); + $this->drupalPostForm('node/add/' . $this->type, $edit, 'Save'); $this->assertSession()->pageTextNotContains($publish_validation_message, 'If the default time option is enabled the user can skip the time when scheduling content for publication.'); $this->assertSession()->pageTextNotContains($unpublish_validation_message, 'If the default time option is enabled the user can skip the time when scheduling content for unpublication.'); + // Get the pattern of the 'long' default date format. + $date_format_storage = $this->container->get('entity_type.manager')->getStorage('date_format'); + $long_pattern = $date_format_storage->load('long')->getPattern(); + // Check that the scheduled information is shown after saving. - $publish_time = strtotime('+1 day midnight', REQUEST_TIME) + $seconds; - $unpublish_time = strtotime('+2 day midnight', REQUEST_TIME) + $seconds; - $args = ['@publish_time' => $date_formatter->format($publish_time, 'long')]; - $this->assertRaw(t('This post is unpublished and will be published @publish_time.', $args), 'The user is informed that the content will be published on the requested date, on the default time.'); + $this->assertText(sprintf('This post is unpublished and will be published %s', $publish_time->format($long_pattern)), 'The user is informed that the content will be published on the requested date, on the default time.'); - // Protect in case the node was not created. + // Protect this section in case the node was not created. if ($node = $this->drupalGetNodeByTitle($edit['title[0][value]'])) { // Check that the correct scheduled dates are stored in the node. - $this->assertEqual($node->publish_on->value, $publish_time, 'The node publish_on value is stored correctly.'); - $this->assertEqual($node->unpublish_on->value, $unpublish_time, 'The node unpublish_on value is stored correctly.'); + $this->assertEquals($publish_time->getTimestamp(), (int) $node->publish_on->value, 'The node publish_on value is stored correctly.'); + $this->assertEquals($unpublish_time->getTimestamp(), (int) $node->unpublish_on->value, 'The node unpublish_on value is stored correctly.'); // Check that the default time has been added to the form on edit. $this->drupalGet('node/' . $node->id() . '/edit'); - $this->assertFieldByName('publish_on[0][value][time]', $seconds_formatted, 'The default time offset has been added to the date field when scheduling content for publication.'); - $this->assertFieldByName('unpublish_on[0][value][time]', $seconds_formatted, 'The default time offset has been added to the date field when scheduling content for unpublication.'); + $this->assertFieldByName('publish_on[0][value][time]', $default_time, 'The default time offset has been added to the date field when scheduling content for publication.'); + $this->assertFieldByName('unpublish_on[0][value][time]', $default_time, 'The default time offset has been added to the date field when scheduling content for unpublication.'); } else { diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerDeleteNodeTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerDeleteNodeTest.php index 1ca309ac0e..46356e9415 100644 --- a/web/modules/scheduler/tests/src/Functional/SchedulerDeleteNodeTest.php +++ b/web/modules/scheduler/tests/src/Functional/SchedulerDeleteNodeTest.php @@ -2,8 +2,6 @@ namespace Drupal\Tests\scheduler\Functional; -use Drupal\node\Entity\NodeType; - /** * Tests deletion of nodes enabled for Scheduler. * @@ -52,14 +50,14 @@ public function testDeleteNodeWhenSchedulingIsRequired() { // normally made hidden from browsers but will be in the page source. // It is also good when testing for the absense of something to also test // for the presence of text, hence the second assertion for each check. - $this->assertNoRaw(t('Error message'), 'No error messages are shown when trying to delete a published node with no scheduling information.'); - $this->assertRaw(t('Are you sure you want to delete the content'), 'The deletion warning message is shown immediately when trying to delete a published node with no scheduling information.'); + $this->assertNoText('Error message', 'No error messages are shown when trying to delete a published node with no scheduling information.'); + $this->assertText('Are you sure you want to delete the content', 'The deletion warning message is shown immediately when trying to delete a published node with no scheduling information.'); // Do the same test for the unpublished node. $this->drupalGet('node/' . $unpublished_node->id() . '/edit'); $this->clickLink('Delete'); - $this->assertNoRaw(t('Error message'), 'No error messages are shown when trying to delete an unpublished node with no scheduling information.'); - $this->assertRaw(t('Are you sure you want to delete the content'), 'The deletion warning message is shown immediately when trying to delete an unpublished node with no scheduling information.'); + $this->assertNoText('Error message', 'No error messages are shown when trying to delete an unpublished node with no scheduling information.'); + $this->assertText('Are you sure you want to delete the content', 'The deletion warning message is shown immediately when trying to delete an unpublished node with no scheduling information.'); } /** @@ -89,14 +87,14 @@ public function testDeleteNodeWithPastDates() { // Attempt to delete the published node and check for no validation error. $this->drupalGet('node/' . $published_node->id() . '/edit'); $this->clickLink('Delete'); - $this->assertNoRaw(t('Error message'), 'No error messages are shown when trying to delete a node with an unpublish date in the past.'); - $this->assertRaw(t('Are you sure you want to delete the content'), 'The deletion warning message is shown immediately when trying to delete a node with an unpublish date in the past.'); + $this->assertNoText('Error message', 'No error messages are shown when trying to delete a node with an unpublish date in the past.'); + $this->assertText('Are you sure you want to delete the content', 'The deletion warning message is shown immediately when trying to delete a node with an unpublish date in the past.'); // Attempt to delete the unpublished node and check for no validation error. $this->drupalGet('node/' . $unpublished_node->id() . '/edit'); $this->clickLink('Delete'); - $this->assertNoRaw(t('Error message'), 'No error messages are shown when trying to delete a node with a publish date in the past.'); - $this->assertRaw(t('Are you sure you want to delete the content'), 'The deletion warning message is shown immediately when trying to delete a node with a publish date in the past.'); + $this->assertNoText('Error message', 'No error messages are shown when trying to delete a node with a publish date in the past.'); + $this->assertText('Are you sure you want to delete the content', 'The deletion warning message is shown immediately when trying to delete a node with a publish date in the past.'); } } diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerDevelGenerateTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerDevelGenerateTest.php index f1f5fc1a1c..3b0f7228ae 100644 --- a/web/modules/scheduler/tests/src/Functional/SchedulerDevelGenerateTest.php +++ b/web/modules/scheduler/tests/src/Functional/SchedulerDevelGenerateTest.php @@ -6,6 +6,10 @@ * Tests the Scheduler interaction with Devel Generate module. * * @group scheduler + * @group legacy + * @todo Remove the 'legacy' tag when Devel no longer uses the deprecated + * $published parameter for setPublished(), and does not use functions + * drupal_set_message(), format_date() and db_query_range(). */ class SchedulerDevelGenerateTest extends SchedulerBrowserTestBase { @@ -57,19 +61,19 @@ public function setUp() { */ protected function countScheduledNodes($type, $field, $num_nodes, $num_scheduled, $time_range = NULL) { // Check that the expected number of nodes have been created. - $count = \Drupal::entityQuery('node') + $count = $this->nodeStorage->getQuery() ->condition('type', $type) ->count() ->execute(); - $this->assertEqual($count, $num_nodes, sprintf('The expected number of %s is %s, found %s', $type, $num_nodes, $count)); + $this->assertEquals($num_nodes, $count, sprintf('The expected number of %s is %s, found %s', $type, $num_nodes, $count)); // Check that the expected number of nodes have been scheduled. - $count = \Drupal::entityQuery('node') + $count = $this->nodeStorage->getQuery() ->condition('type', $type) ->exists($field) ->count() ->execute(); - $this->assertEqual($count, $num_scheduled, sprintf('The expected number of scheduled %s is %s, found %s', $field, $num_scheduled, $count)); + $this->assertEquals($num_scheduled, $count, sprintf('The expected number of scheduled %s is %s, found %s', $field, $num_scheduled, $count)); if (isset($time_range)) { // Define the minimum and maximum times that we expect the scheduled dates @@ -78,10 +82,10 @@ protected function countScheduledNodes($type, $field, $num_nodes, $num_scheduled // slowly creep forward during sucessive calls. Tests can fail incorrectly // for this reason, hence the best approximation is to use time() when // calculating the upper end of the range. - $min = REQUEST_TIME - $time_range; + $min = $this->requestTime - $time_range; $max = time() + $time_range; - $query = \Drupal::entityQueryAggregate('node'); + $query = $this->nodeStorage->getAggregateQuery(); $result = $query ->condition('type', $type) ->aggregate($field, 'min') @@ -91,8 +95,8 @@ protected function countScheduledNodes($type, $field, $num_nodes, $num_scheduled $max_found = $result[0]["{$field}_max"]; // Assert that the found values are within the expcted range. - $this->assertGreaterThanOrEqual($min, $min_found, sprintf('The minimum value for %s is %s, smaller than the expected %s', $field, format_date($min_found, 'custom', 'j M, H:i:s'), format_date($min, 'custom', 'j M, H:i:s'))); - $this->assertLessThanOrEqual($max, $max_found, sprintf('The maximum value for %s is %s which is larger than expected %s', $field, format_date($max_found, 'custom', 'j M, H:i:s'), format_date($max, 'custom', 'j M, H:i:s'))); + $this->assertGreaterThanOrEqual($min, $min_found, sprintf('The minimum value for %s is %s, smaller than the expected %s', $field, $this->dateFormatter->format($min_found, 'custom', 'j M, H:i:s'), $this->dateFormatter->format($min, 'custom', 'j M, H:i:s'))); + $this->assertLessThanOrEqual($max, $max_found, sprintf('The maximum value for %s is %s which is larger than expected %s', $field, $this->dateFormatter->format($max_found, 'custom', 'j M, H:i:s'), $this->dateFormatter->format($max, 'custom', 'j M, H:i:s'))); } } diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerFieldsDisplayTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerFieldsDisplayTest.php index ceba0168f1..3781fdad6f 100644 --- a/web/modules/scheduler/tests/src/Functional/SchedulerFieldsDisplayTest.php +++ b/web/modules/scheduler/tests/src/Functional/SchedulerFieldsDisplayTest.php @@ -2,8 +2,6 @@ namespace Drupal\Tests\scheduler\Functional; -use Drupal\node\Entity\NodeType; - /** * Tests the display of the date entry fields (vertical tab, fieldset). * @@ -18,19 +16,38 @@ class SchedulerFieldsDisplayTest extends SchedulerBrowserTestBase { */ public static $modules = ['field_ui']; + /** + * {@inheritdoc} + */ + public function setUp() { + parent::setUp(); + + // Create a custom user with admin permissions but also permission to use + // the field_ui module 'node form display' tab. + $this->adminUser2 = $this->drupalCreateUser([ + 'access content', + 'administer content types', + 'administer node form display', + 'create ' . $this->type . ' content', + 'schedule publishing of nodes', + ]); + } + /** * Tests date input is displayed as vertical tab or an expandable fieldset. + * + * This test covers scheduler_form_node_form_alter(). */ - public function testFieldsDisplay() { + public function testVerticalTabOrFieldset() { $this->drupalLogin($this->adminUser); // Check that the dates are shown in a vertical tab by default. - $this->drupalGet('node/add/page'); + $this->drupalGet('node/add/' . $this->type); $this->assertTrue($this->xpath('//div[contains(@class, "form-type-vertical-tabs")]//details[@id = "edit-scheduler-settings"]'), 'By default the scheduler dates are shown in a vertical tab.'); // Check that the dates are shown as a fieldset when configured to do so. $this->nodetype->setThirdPartySetting('scheduler', 'fields_display_mode', 'fieldset')->save(); - $this->drupalGet('node/add/page'); + $this->drupalGet('node/add/' . $this->type); $this->assertFalse($this->xpath('//div[contains(@class, "form-type-vertical-tabs")]//details[@id = "edit-scheduler-settings"]'), 'The scheduler dates are not shown in a vertical tab when they are configured to show as a fieldset.'); $this->assertTrue($this->xpath('//details[@id = "edit-scheduler-settings"]'), 'The scheduler dates are shown in a fieldset when they are configured to show as a fieldset.'); $this->assertTrue($this->xpath('//details[@id = "edit-scheduler-settings" and not(@open = "open")]'), 'The scheduler dates fieldset is collapsed by default.'); @@ -38,19 +55,19 @@ public function testFieldsDisplay() { // Check that the fieldset is expanded if either of the scheduling dates // are required. $this->nodetype->setThirdPartySetting('scheduler', 'publish_required', TRUE)->save(); - $this->drupalGet('node/add/page'); + $this->drupalGet('node/add/' . $this->type); $this->assertTrue($this->xpath('//details[@id = "edit-scheduler-settings" and @open = "open"]'), 'The scheduler dates are shown in an expanded fieldset when the publish-on date is required.'); $this->nodetype->setThirdPartySetting('scheduler', 'publish_required', FALSE) ->setThirdPartySetting('scheduler', 'unpublish_required', TRUE)->save(); - $this->drupalGet('node/add/page'); + $this->drupalGet('node/add/' . $this->type); $this->assertTrue($this->xpath('//details[@id = "edit-scheduler-settings" and @open = "open"]'), 'The scheduler dates are shown in an expanded fieldset when the unpublish-on date is required.'); // Check that the fieldset is expanded if the 'always' option is set. $this->nodetype->setThirdPartySetting('scheduler', 'publish_required', FALSE) ->setThirdPartySetting('scheduler', 'unpublish_required', FALSE) ->setThirdPartySetting('scheduler', 'expand_fieldset', 'always')->save(); - $this->drupalGet('node/add/page'); + $this->drupalGet('node/add/' . $this->type); $this->assertTrue($this->xpath('//details[@id = "edit-scheduler-settings" and @open = "open"]'), 'The scheduler dates are shown in an expanded fieldset when the option to always expand is turned on.'); // Check that the fieldset is expanded if the node already has a publish-on @@ -74,6 +91,12 @@ public function testFieldsDisplay() { $node = $this->drupalCreateNode($options); $this->drupalGet('node/' . $node->id() . '/edit'); $this->assertTrue($this->xpath('//details[@id = "edit-scheduler-settings" and @open = "open"]'), 'The scheduler dates are shown in an expanded fieldset when an unpublish-on date already exists.'); + + // Check that the display reverts to a vertical tab again when specifically + // configured to do so. + $this->nodetype->setThirdPartySetting('scheduler', 'fields_display_mode', 'vertical_tab')->save(); + $this->drupalGet('node/add/' . $this->type); + $this->assertTrue($this->xpath('//div[contains(@class, "form-type-vertical-tabs")]//details[@id = "edit-scheduler-settings"]'), 'The scheduler dates are shown in a vertical tab when that option is set.'); } /** @@ -82,14 +105,7 @@ public function testFieldsDisplay() { * This test covers scheduler_entity_extra_field_info(). */ public function testManageFormDisplay() { - - // Create a custom administrator user with permissions to use the field_ui - // module 'node form display' tab. - $this->adminUser = $this->drupalCreateUser([ - 'administer content types', - 'administer node form display', - ]); - $this->drupalLogin($this->adminUser); + $this->drupalLogin($this->adminUser2); // Check that the weight input field is displayed when the content type is // enabled for scheduling. This field still exists even with tabledrag on. @@ -104,4 +120,54 @@ public function testManageFormDisplay() { $this->assertNoFieldById('edit-fields-scheduler-settings-weight', NULL, 'The scheduler settings row is not shown when the content type is not enabled for scheduling.'); } + /** + * Tests the edit form when scheduler fields have been disabled. + * + * This test covers scheduler_form_node_form_alter(). + */ + public function testDisabledFields() { + $this->drupalLogin($this->adminUser2); + + // 1. Set the publish_on field to 'hidden' in the node edit form. + $edit = [ + 'fields[publish_on][region]' => 'hidden', + ]; + $this->drupalPostForm('admin/structure/types/manage/' . $this->type . '/form-display', $edit, 'Save'); + + // Check that a scheduler vertical tab is displayed. + $this->drupalGet('node/add/' . $this->type); + $this->assertTrue($this->xpath('//div[contains(@class, "form-type-vertical-tabs")]//details[@id = "edit-scheduler-settings"]'), 'The scheduler input is a vertical tab.'); + // Check the publish_on field is not shown, but the unpublish_on field is. + $this->assertNoFieldByName('publish_on[0][value][date]', NULL, 'The Publish-on field is not shown - 1'); + $this->assertFieldByName('unpublish_on[0][value][date]', NULL, 'The Unpublish-on field is shown - 1'); + + // 2. Set publish_on to be displayed but hide the unpublish_on field. + $edit = [ + 'fields[publish_on][region]' => 'content', + 'fields[unpublish_on][region]' => 'hidden', + ]; + $this->drupalPostForm('admin/structure/types/manage/' . $this->type . '/form-display', $edit, 'Save'); + + // Check that a scheduler vertical tab is displayed. + $this->drupalGet('node/add/' . $this->type); + $this->assertTrue($this->xpath('//div[contains(@class, "form-type-vertical-tabs")]//details[@id = "edit-scheduler-settings"]'), 'The scheduler input is a vertical tab.'); + // Check the publish_on field is not shown, but the unpublish_on field is. + $this->assertFieldByName('publish_on[0][value][date]', NULL, 'The Publish-on field is shown - 2'); + $this->assertNoFieldByName('unpublish_on[0][value][date]', NULL, 'The Unpublish-on field is not shown - 2'); + + // 3. Set both fields to be hidden. + $edit = [ + 'fields[publish_on][region]' => 'hidden', + 'fields[unpublish_on][region]' => 'hidden', + ]; + $this->drupalPostForm('admin/structure/types/manage/' . $this->type . '/form-display', $edit, 'Save'); + + // Check that no vertical tab is displayed. + $this->drupalGet('node/add/' . $this->type); + $this->assertFalse($this->xpath('//div[contains(@class, "form-type-vertical-tabs")]//details[@id = "edit-scheduler-settings"]'), 'The scheduler vertical tab is not shown.'); + // Check the neither field is displayed. + $this->assertNoFieldByName('publish_on[0][value][date]', NULL, 'The Publish-on field is not shown - 3'); + $this->assertNoFieldByName('unpublish_on[0][value][date]', NULL, 'The Unpublish-on field is not shown - 3'); + } + } diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerLightweightCronTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerLightweightCronTest.php index 1bad0e0e43..e2878457f1 100644 --- a/web/modules/scheduler/tests/src/Functional/SchedulerLightweightCronTest.php +++ b/web/modules/scheduler/tests/src/Functional/SchedulerLightweightCronTest.php @@ -5,7 +5,7 @@ use Drupal\Core\Url; /** - * Tests the Scheduler lighweight cron urls and admin form. + * Tests the Scheduler lightweight cron urls and admin form. * * @group scheduler */ @@ -56,7 +56,7 @@ public function testLightweightCronSettingsForm() { $this->assertTrue(strlen($key) == 20, 'Default lightweight cron key string length is 20'); // Check that a new random key can be generated. - $this->drupalPostForm($this->routeCronForm, [], t('Generate new random key')); + $this->drupalPostForm($this->routeCronForm, [], 'Generate new random key'); $new_key_xpath = $this->xpath('//input[@id="edit-lightweight-access-key"]/@value'); $new_key = $new_key_xpath[0]->getText(); $this->assertTrue(!empty($new_key), 'Lightweight cron key field is not empty after generating new key'); @@ -64,11 +64,11 @@ public function testLightweightCronSettingsForm() { $this->assertNotEqual($key, $new_key, 'Lightweight cron key has changed.'); // Check that the 'run lightweight cron' button works. - $this->drupalPostForm($this->routeCronForm, [], t("Run Scheduler's lightweight cron now")); + $this->drupalPostForm($this->routeCronForm, [], "Run Scheduler's lightweight cron now"); $this->assertText('Lightweight cron run completed.', 'Lightweight cron runs OK manually'); // Check that the form cannot be saved if the cron key is blank. - $this->drupalPostForm($this->routeCronForm, ['lightweight_access_key' => ''], t('Save configuration')); + $this->drupalPostForm($this->routeCronForm, ['lightweight_access_key' => ''], 'Save configuration'); $this->assertText('Lightweight cron access key field is required.', 'Saving configuration with a blank cron key throws the expected validation message'); $this->assertNoText('The configuration options have been saved.', 'Saving configuration with a blank cron key is not possible'); } diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerMetaInformationTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerMetaInformationTest.php index 859f0c558b..f918d2a487 100644 --- a/web/modules/scheduler/tests/src/Functional/SchedulerMetaInformationTest.php +++ b/web/modules/scheduler/tests/src/Functional/SchedulerMetaInformationTest.php @@ -34,10 +34,10 @@ public function testMetaInformation() { // Set a scheduler unpublish date on the node. $unpublish_date = strtotime('+1 day'); $edit = [ - 'unpublish_on[0][value][date]' => \Drupal::service('date.formatter')->format($unpublish_date, 'custom', 'Y-m-d'), - 'unpublish_on[0][value][time]' => \Drupal::service('date.formatter')->format($unpublish_date, 'custom', 'H:i:s'), + 'unpublish_on[0][value][date]' => $this->dateFormatter->format($unpublish_date, 'custom', 'Y-m-d'), + 'unpublish_on[0][value][time]' => $this->dateFormatter->format($unpublish_date, 'custom', 'H:i:s'), ]; - $this->drupalPostForm('node/' . $published_node->id() . '/edit', $edit, t('Save')); + $this->drupalPostForm('node/' . $published_node->id() . '/edit', $edit, 'Save'); // The node page should now have an X-Robots-Tag header with an // unavailable_after-directive and RFC850 date- and time-value. diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerMultilingualTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerMultilingualTest.php index 7823bc79f5..e434e9f613 100644 --- a/web/modules/scheduler/tests/src/Functional/SchedulerMultilingualTest.php +++ b/web/modules/scheduler/tests/src/Functional/SchedulerMultilingualTest.php @@ -89,19 +89,19 @@ private function checkStatus($nid, $description, array $st) { $this->nodeStorage->resetCache([$nid]); $node = $this->nodeStorage->load($nid); - foreach ($st as $key => $status) { + foreach ($st as $key => $expected_status) { if ($key == 0) { // Key 0 is the original, so we just check $node. - $this->assertEqual($node->isPublished(), $status, - sprintf('%s: The original content (%s) is %s', $description, $this->languages[$key]['name'], ($status ? 'published' : 'unpublished'))); + $this->assertEquals($expected_status, $node->isPublished(), + sprintf('%s: The original content (%s) is %s', $description, $this->languages[$key]['name'], ($expected_status ? 'published' : 'unpublished'))); } else { // Key > 0 are the translations, which we get using the Content // Translation Manager getTranslationMetadata() function. $trans = $this->ctm->getTranslationMetadata($node->getTranslation($this->languages[$key]['code'])); $trans = $node->getTranslation($this->languages[$key]['code']); - $this->assertEqual($trans->isPublished(), $status, - sprintf('%s: Translation %d (%s) is %s', $description, $key, $this->languages[$key]['name'], ($status ? 'published' : 'unpublished'))); + $this->assertEquals($expected_status, $trans->isPublished(), + sprintf('%s: Translation %d (%s) is %s', $description, $key, $this->languages[$key]['name'], ($expected_status ? 'published' : 'unpublished'))); } } } @@ -117,9 +117,9 @@ public function testPublishingTranslations($publish_on_translatable, $unpublish_ // parameters passed in. $this->drupalGet('admin/config/regional/content-language'); $settings = [ - 'edit-settings-node-page-settings-language-language-alterable' => TRUE, - 'edit-settings-node-page-fields-publish-on' => $publish_on_translatable, - 'edit-settings-node-page-fields-unpublish-on' => $unpublish_on_translatable, + 'edit-settings-node-' . $this->type . '-settings-language-language-alterable' => TRUE, + 'edit-settings-node-' . $this->type . '-fields-publish-on' => $publish_on_translatable, + 'edit-settings-node-' . $this->type . '-fields-unpublish-on' => $unpublish_on_translatable, ]; // The submit shows the updated values, so no need for second get. $this->submitForm($settings, 'Save configuration'); @@ -160,8 +160,8 @@ public function testPublishingTranslations($publish_on_translatable, $unpublish_ $this->drupalGet('node/' . $node->id() . '/translations/add/' . $this->languages[0]['code'] . '/' . $this->languages[2]['code']); $edit = [ 'title[0][value]' => $this->languages[2]['name'] . '(2) - Publish in the future', - 'publish_on[0][value][date]' => date('Y-m-d', strtotime('+2 day', REQUEST_TIME)), - 'publish_on[0][value][time]' => date('H:i:s', strtotime('+2 day', REQUEST_TIME)), + 'publish_on[0][value][date]' => date('Y-m-d', strtotime('+2 day', $this->requestTime)), + 'publish_on[0][value][time]' => date('H:i:s', strtotime('+2 day', $this->requestTime)), ]; $this->submitForm($edit, $save_button_text); @@ -169,8 +169,8 @@ public function testPublishingTranslations($publish_on_translatable, $unpublish_ $this->drupalGet('node/' . $node->id() . '/translations/add/' . $this->languages[0]['code'] . '/' . $this->languages[3]['code']); $edit = [ 'title[0][value]' => $this->languages[3]['name'] . '(3) - Publish in the past', - 'publish_on[0][value][date]' => date('Y-m-d', strtotime('-2 day', REQUEST_TIME)), - 'publish_on[0][value][time]' => date('H:i:s', strtotime('-2 day', REQUEST_TIME)), + 'publish_on[0][value][date]' => date('Y-m-d', strtotime('-2 day', $this->requestTime)), + 'publish_on[0][value][time]' => date('H:i:s', strtotime('-2 day', $this->requestTime)), ]; $this->submitForm($edit, $save_button_text); diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerNodeAccessTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerNodeAccessTest.php index 7f5f25f3da..f9a56f58b0 100644 --- a/web/modules/scheduler/tests/src/Functional/SchedulerNodeAccessTest.php +++ b/web/modules/scheduler/tests/src/Functional/SchedulerNodeAccessTest.php @@ -57,7 +57,7 @@ public function testNodeAccess() { 'type' => $this->type, 'status' => $data['status'], 'title' => 'Test node to be ' . $data['after'], - $field => REQUEST_TIME + 1, + $field => $this->requestTime + 1, ]; $node = $this->drupalCreateNode($settings); $this->drupalGet('node/' . $node->id()); diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerNonEnabledTypeTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerNonEnabledTypeTest.php index 79b162a38d..d4988fa45e 100644 --- a/web/modules/scheduler/tests/src/Functional/SchedulerNonEnabledTypeTest.php +++ b/web/modules/scheduler/tests/src/Functional/SchedulerNonEnabledTypeTest.php @@ -19,15 +19,13 @@ public function setUp() { $this->contentName = 'not_for_scheduler'; $this->contentType = $this->drupalCreateContentType(['type' => $this->contentName, 'name' => 'Not for Scheduler']); - // Create an administrator user. - $this->adminUser = $this->drupalCreateUser([ + // Create an admin user with permission to create the new content type. + $this->adminUser2 = $this->drupalCreateUser([ 'access content', + 'administer nodes', 'administer scheduler', 'create ' . $this->contentName . ' content', - 'edit own ' . $this->contentName . ' content', - 'delete own ' . $this->contentName . ' content', 'view own unpublished content', - 'administer nodes', 'schedule publishing of nodes', 'access site reports', ]); @@ -80,7 +78,7 @@ protected function checkNonEnabledTypes($publishing_enabled, $unpublishing_enabl 'title' => $title, 'status' => 0, 'type' => $this->contentName, - 'publish_on' => REQUEST_TIME - 2, + 'publish_on' => $this->requestTime - 2, ]; $node = $this->drupalCreateNode($edit); @@ -107,7 +105,7 @@ protected function checkNonEnabledTypes($publishing_enabled, $unpublishing_enabl 'title' => $title, 'status' => 1, 'type' => $this->contentName, - 'unpublish_on' => REQUEST_TIME - 1, + 'unpublish_on' => $this->requestTime - 1, ]; $node = $this->drupalCreateNode($edit); @@ -138,7 +136,7 @@ protected function checkNonEnabledTypes($publishing_enabled, $unpublishing_enabl */ public function testNonEnabledNodeType() { // Log in. - $this->drupalLogin($this->adminUser); + $this->drupalLogin($this->adminUser2); // 1. By default check that the scheduler date fields are not displayed. $this->checkNonEnabledTypes(FALSE, FALSE, 1); diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerPastDatesTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerPastDatesTest.php index e4c83058fd..b5c8c5e9b7 100644 --- a/web/modules/scheduler/tests/src/Functional/SchedulerPastDatesTest.php +++ b/web/modules/scheduler/tests/src/Functional/SchedulerPastDatesTest.php @@ -2,7 +2,7 @@ namespace Drupal\Tests\scheduler\Functional; -use Drupal\Component\Utility\SafeMarkup; +use Drupal\Component\Utility\Html; /** * Tests the options and processing when dates are entered in the past. @@ -18,70 +18,105 @@ public function testSchedulerPastDates() { // Log in. $this->drupalLogin($this->schedulerUser); - // Ensure that neither of the scheduling dates are set to be required. - $this->nodetype->setThirdPartySetting('scheduler', 'publish_required', FALSE) - ->setThirdPartySetting('scheduler', 'unpublish_required', FALSE)->save(); - // Create an unpublished page node. + /** @var NodeInterface $node */ $node = $this->drupalCreateNode(['type' => $this->type, 'status' => FALSE]); + $created_time = $node->getCreatedTime(); // Test the default behavior: an error message should be shown when the user // enters a publication date that is in the past. $edit = [ 'title[0][value]' => 'Past ' . $this->randomString(10), - 'publish_on[0][value][date]' => \Drupal::service('date.formatter')->format(strtotime('-1 day', REQUEST_TIME), 'custom', 'Y-m-d'), - 'publish_on[0][value][time]' => \Drupal::service('date.formatter')->format(strtotime('-1 day', REQUEST_TIME), 'custom', 'H:i:s'), + 'publish_on[0][value][date]' => $this->dateFormatter->format(strtotime('-1 day', $this->requestTime), 'custom', 'Y-m-d'), + 'publish_on[0][value][time]' => $this->dateFormatter->format(strtotime('-1 day', $this->requestTime), 'custom', 'H:i:s'), ]; - $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save')); - $this->assertRaw(t("The 'publish on' date must be in the future"), 'An error message is shown by default when the publication date is in the past.'); + $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save'); + $this->assertText("The 'publish on' date must be in the future", 'An error message is shown by default when the publication date is in the past.'); // Test the 'error' behavior explicitly. $this->nodetype->setThirdPartySetting('scheduler', 'publish_past_date', 'error')->save(); - $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save')); - $this->assertRaw(t("The 'publish on' date must be in the future"), 'An error message is shown when the publication date is in the past and the "error" behavior is chosen.'); + $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save'); + $this->assertText("The 'publish on' date must be in the future", 'An error message is shown when the publication date is in the past and the "error" behavior is chosen.'); // Test the 'publish' behavior: the node should be published immediately. $this->nodetype->setThirdPartySetting('scheduler', 'publish_past_date', 'publish')->save(); - $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save')); - $this->assertNoText(t("The 'publish on' date must be in the future"), 'No error message is shown when the publication date is in the past and the "publish" behavior is chosen.'); - $this->assertText(sprintf('%s %s has been updated.', $this->typeName, SafeMarkup::checkPlain($edit['title[0][value]'])), 'The node is saved successfully when the publication date is in the past and the "publish" behavior is chosen.'); + $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save'); + $this->assertNoText("The 'publish on' date must be in the future", 'No error message is shown when the publication date is in the past and the "publish" behavior is chosen.'); + $this->assertText(sprintf('%s %s has been updated.', $this->typeName, Html::escape($edit['title[0][value]'])), 'The node is saved successfully when the publication date is in the past and the "publish" behavior is chosen.'); - // Reload the changed node and check that it is published. + // Reload the node. $this->nodeStorage->resetCache([$node->id()]); - - /** @var NodeInterface $node */ $node = $this->nodeStorage->load($node->id()); + + // Check that the node is published and has the expected timestamps. $this->assertTrue($node->isPublished(), 'The node has been published immediately when the publication date is in the past and the "publish" behavior is chosen.'); $this->assertNull($node->publish_on->value, 'The node publish_on date has been removed after publishing when the "publish" behavior is chosen.'); + $this->assertEquals($node->getChangedTime(), strtotime('-1 day', $this->requestTime), 'The changed time of the node has been updated to the publish_on time when published immediately.'); + $this->assertEquals($node->getCreatedTime(), $created_time, 'The created time of the node has not been changed when the "publish" behavior is chosen.'); // Test the 'schedule' behavior: the node should be unpublished and become - // published on the next cron run. + // published on the next cron run. Use a new unpublished node. $this->nodetype->setThirdPartySetting('scheduler', 'publish_past_date', 'schedule')->save(); - $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save')); - $this->assertNoText(t("The 'publish on' date must be in the future"), 'No error message is shown when the publication date is in the past and the "schedule" behavior is chosen.'); - $this->assertText(sprintf('%s %s has been updated.', $this->typeName, SafeMarkup::checkPlain($edit['title[0][value]'])), 'The node is saved successfully when the publication date is in the past and the "schedule" behavior is chosen.'); + $node = $this->drupalCreateNode(['type' => $this->type, 'status' => FALSE]); + $created_time = $node->getCreatedTime(); + $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save'); + $this->assertNoText("The 'publish on' date must be in the future", 'No error message is shown when the publication date is in the past and the "schedule" behavior is chosen.'); + $this->assertText(sprintf('%s %s has been updated.', $this->typeName, Html::escape($edit['title[0][value]'])), 'The node is saved successfully when the publication date is in the past and the "schedule" behavior is chosen.'); $this->assertText('will be published', 'The node is scheduled to be published when the publication date is in the past and the "schedule" behavior is chosen.'); - // Reload the node and check that it is unpublished but scheduled correctly. + // Reload the node. $this->nodeStorage->resetCache([$node->id()]); $node = $this->nodeStorage->load($node->id()); + + // Check that the node is unpublished but scheduled correctly. $this->assertFalse($node->isPublished(), 'The node has been unpublished when the publication date is in the past and the "schedule" behavior is chosen.'); - $this->assertEqual($node->publish_on->value, strtotime('-1 day', REQUEST_TIME), 'The node has the correct publish_on date stored.'); + $this->assertEquals(strtotime('-1 day', $this->requestTime), (int) $node->publish_on->value, 'The node has the correct publish_on date stored.'); // Simulate a cron run and check that the node is published. scheduler_cron(); $this->nodeStorage->resetCache([$node->id()]); $node = $this->nodeStorage->load($node->id()); $this->assertTrue($node->isPublished(), 'The node with publication date in the past and the "schedule" behavior has now been published by cron.'); + $this->assertEquals($node->getChangedTime(), strtotime('-1 day', $this->requestTime), 'The changed time of the node has been updated to the publish_on time when published via cron.'); + $this->assertEquals($node->getCreatedTime(), $created_time, 'The created time of the node has not been changed when the "schedule" behavior is chosen.'); + + // Test the option to alter the creation time if the publishing time is + // earlier than the node created time. + $this->nodetype->setThirdPartySetting('scheduler', 'publish_past_date_created', TRUE)->save(); + + $past_date_options = [ + 'publish' => 'publish', + 'schedule' => 'schedule', + ]; + + foreach ($past_date_options as $key => $option) { + $this->nodetype->setThirdPartySetting('scheduler', 'publish_past_date', $key)->save(); + + // Create a new node, edit and save. + $node = $this->drupalCreateNode(['type' => $this->type, 'status' => FALSE]); + $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save'); + + if ($option == 'schedule') { + scheduler_cron(); + } + + // Reload the node. + $this->nodeStorage->resetCache([$node->id()]); + $node = $this->nodeStorage->load($node->id()); + + // Check that the created time has been altered to match publishing time. + $this->assertEquals($node->getCreatedTime(), strtotime('-1 day', $this->requestTime), sprintf('The created time of the node has not been changed when the %s option is chosen.', $option)); + + } // Check that an Unpublish date in the past fails validation. $edit = [ 'title[0][value]' => 'Unpublish in the past ' . $this->randomString(10), - 'unpublish_on[0][value][date]' => \Drupal::service('date.formatter')->format(REQUEST_TIME - 3600, 'custom', 'Y-m-d'), - 'unpublish_on[0][value][time]' => \Drupal::service('date.formatter')->format(REQUEST_TIME - 3600, 'custom', 'H:i:s'), + 'unpublish_on[0][value][date]' => $this->dateFormatter->format($this->requestTime - 3600, 'custom', 'Y-m-d'), + 'unpublish_on[0][value][time]' => $this->dateFormatter->format($this->requestTime - 3600, 'custom', 'H:i:s'), ]; - $this->drupalPostForm('node/add/' . $this->type, $edit, t('Save')); - $this->assertRaw(t("The 'unpublish on' date must be in the future"), 'An error message is shown when the unpublish date is in the past.'); + $this->drupalPostForm('node/add/' . $this->type, $edit, 'Save'); + $this->assertText("The 'unpublish on' date must be in the future", 'An error message is shown when the unpublish date is in the past.'); } } diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerPermissionsTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerPermissionsTest.php index c0632580ba..2dbb46bf39 100644 --- a/web/modules/scheduler/tests/src/Functional/SchedulerPermissionsTest.php +++ b/web/modules/scheduler/tests/src/Functional/SchedulerPermissionsTest.php @@ -2,7 +2,7 @@ namespace Drupal\Tests\scheduler\Functional; -use Drupal\Component\Utility\SafeMarkup; +use Drupal\Component\Utility\Html; /** * Tests the permissions of the Scheduler module. @@ -14,7 +14,7 @@ class SchedulerPermissionsTest extends SchedulerBrowserTestBase { /** * Tests that users without permission do not see the scheduler date fields. */ - public function testUserPermissions() { + public function testUserPermissionsAdd() { // Create a user who can add the content type but who does not have the // permission to use the scheduler functionality. $this->webUser = $this->drupalCreateUser([ @@ -51,7 +51,7 @@ public function testUserPermissions() { $edit['status[value]'] = TRUE; } $this->drupalPostForm('node/add/' . $this->type, $edit, $checkbox ? 'Save' : 'Save and publish'); - $this->assertText(sprintf('%s %s has been created.', $this->typeName, SafeMarkup::checkPlain($title)), 'A node can be created and published when the user does not have scheduler permissions.'); + $this->assertText(sprintf('%s %s has been created.', $this->typeName, Html::escape($title)), 'A node can be created and published when the user does not have scheduler permissions.'); $this->assertTrue($this->drupalGetNodeByTitle($title)->isPublished(), 'The new node is published'); // Check that a new node can be saved as unpublished. @@ -61,7 +61,7 @@ public function testUserPermissions() { $edit['status[value]'] = FALSE; } $this->drupalPostForm('node/add/' . $this->type, $edit, $checkbox ? 'Save' : 'Save as unpublished'); - $this->assertText(sprintf('%s %s has been created.', $this->typeName, SafeMarkup::checkPlain($title)), 'A node can be created and saved as unpublished when the user does not have scheduler permissions.'); + $this->assertText(sprintf('%s %s has been created.', $this->typeName, Html::escape($title)), 'A node can be created and saved as unpublished when the user does not have scheduler permissions.'); $this->assertFalse($this->drupalGetNodeByTitle($title)->isPublished(), 'The new node is unpublished'); // Set publishing and unpublishing to required, to make it a stronger test. @@ -76,4 +76,66 @@ public function testUserPermissions() { // permission" } + /** + * Tests that users without permission can edit existing scheduled content. + */ + public function testUserPermissionsEdit() { + // Create a user who can add the content type but who does not have the + // permission to use the scheduler functionality. + $this->webUser = $this->drupalCreateUser([ + 'access content', + 'administer nodes', + 'create ' . $this->type . ' content', + 'edit own ' . $this->type . ' content', + 'delete own ' . $this->type . ' content', + 'view own unpublished content', + ]); + $this->drupalLogin($this->webUser); + + $publish_time = strtotime('+ 6 hours', $this->requestTime); + $unpublish_time = strtotime('+ 10 hours', $this->requestTime); + + // Create nodes with publish_on and unpublish_on dates. + $unpublished_node = $this->drupalCreateNode([ + 'type' => $this->type, + 'status' => FALSE, + 'publish_on' => $publish_time, + ]); + $published_node = $this->drupalCreateNode([ + 'type' => $this->type, + 'status' => TRUE, + 'unpublish_on' => $unpublish_time, + ]); + + // Verify that the publish_on date is stored as expected before editing. + $this->assertEqual($unpublished_node->publish_on->value, $publish_time, 'The publish_on value is stored correctly before edit.'); + + // Edit the unpublished node and save. + $title = 'For Publishing ' . $this->randomString(10); + $this->drupalPostForm('node/' . $unpublished_node->id() . '/edit', ['title[0][value]' => $title], 'Save'); + + // Check the updated title, to verify that edit and save was sucessful. + $unpublished_node = $this->nodeStorage->load($unpublished_node->id()); + $this->assertEqual($unpublished_node->title->value, $title, 'The unpublished node title has been updated correctly after edit.'); + + // Test that the publish_on date is still stored and is unchanged. + $this->assertEqual($unpublished_node->publish_on->value, $publish_time, 'The node publish_on value is still stored correctly after edit.'); + + // Do the same for unpublishing. + // Verify that the unpublish_on date is stored as expected before editing. + $this->assertEqual($published_node->unpublish_on->value, $unpublish_time, 'The unpublish_on value is stored correctly before edit.'); + + // Edit the published node and save. + $title = 'For Unpublishing ' . $this->randomString(10); + $this->drupalPostForm('node/' . $published_node->id() . '/edit', ['title[0][value]' => $title], 'Save'); + + // Check the updated title, to verify that edit and save was sucessful. + $published_node = $this->nodeStorage->load($published_node->id()); + $this->assertEqual($published_node->title->value, $title, 'The published node title has been updated correctly after edit.'); + + // Test that the unpublish_on date is still stored and is unchanged. + $this->assertEqual($published_node->unpublish_on->value, $unpublish_time, 'The node unpublish_on value is still stored correctly after edit.'); + + } + } diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerRequiredTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerRequiredTest.php index 00ec4c478d..ed7a5d1d25 100644 --- a/web/modules/scheduler/tests/src/Functional/SchedulerRequiredTest.php +++ b/web/modules/scheduler/tests/src/Functional/SchedulerRequiredTest.php @@ -2,9 +2,6 @@ namespace Drupal\Tests\scheduler\Functional; -use Drupal\node\Entity\NodeType; -use Drupal\Component\Utility\SafeMarkup; - /** * Tests the options for scheduling dates to be required during add/edit. * @@ -154,7 +151,8 @@ public function testRequiredScheduling() { ], ]; - $fields = \Drupal::entityManager()->getFieldDefinitions('node', $this->type); + $fields = $this->container->get('entity_field.manager') + ->getFieldDefinitions('node', $this->type); foreach ($test_cases as $test_case) { // Set required (un)publishing as stipulated by the test case. @@ -207,7 +205,7 @@ public function testRequiredScheduling() { 'unpublish_on[0][value][time]' => '', ]; // Add or edit the node. - $this->drupalPostForm($path, $values, t('Save')); + $this->drupalPostForm($path, $values, 'Save'); // Check for the expected result. switch ($test_case['expected']) { @@ -217,7 +215,7 @@ public function testRequiredScheduling() { break; case 'not required': - $string = sprintf('%s %s has been %s.', $this->typeName, SafeMarkup::checkPlain($title), ($test_case['operation'] == 'add' ? 'created' : 'updated')); + $string = sprintf('%s %s has been %s.', $this->typeName, $title, ($test_case['operation'] == 'add' ? 'created' : 'updated')); $this->assertText($string, $test_case['id'] . '. ' . $test_case['message']); break; } diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerRevisioningTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerRevisioningTest.php index 8810e8df96..a4d7711fd6 100644 --- a/web/modules/scheduler/tests/src/Functional/SchedulerRevisioningTest.php +++ b/web/modules/scheduler/tests/src/Functional/SchedulerRevisioningTest.php @@ -26,7 +26,7 @@ class SchedulerRevisioningTest extends SchedulerBrowserTestBase { protected function schedule(NodeInterface $node, $action = 'publish') { // Simulate scheduling by setting the (un)publication date in the past and // running cron. - $node->{$action . '_on'} = strtotime('-1 day', REQUEST_TIME); + $node->{$action . '_on'} = strtotime('-5 hour', $this->requestTime); $node->save(); scheduler_cron(); $this->nodeStorage->resetCache([$node->id()]); @@ -34,56 +34,51 @@ protected function schedule(NodeInterface $node, $action = 'publish') { } /** - * Check if the latest revision log message of a node matches a given string. + * Check if the number of revisions for a node matches a given value. * * @param int $nid * The node id of the node to check. * @param string $value - * The value with which the log message will be compared. + * The value with which the number of revisions will be compared. * @param string $message * The message to display along with the assertion. - * @param string $group - * The type of assertion - examples are "Browser", "PHP". * * @return bool * TRUE if the assertion succeeded, FALSE otherwise. */ - protected function assertRevisionLogMessage($nid, $value, $message = '', $group = 'Other') { - // Retrieve the latest revision log message for this node. - $log_message = $this->database->select('node_revision', 'r') - ->fields('r', ['revision_log']) + protected function assertRevisionCount($nid, $value, $message = '') { + $count = \Drupal::database()->select('node_revision', 'r') ->condition('nid', $nid) - ->orderBy('vid', 'DESC') - ->range(0, 1) + ->countQuery() ->execute() ->fetchColumn(); - - return $this->assertEqual($log_message, $value, $message, $group); + return $this->assertEquals($value, (int) $count, $message); } /** - * Check if the number of revisions for a node matches a given value. + * Check if the latest revision log message of a node matches a given string. * * @param int $nid * The node id of the node to check. * @param string $value - * The value with which the number of revisions will be compared. + * The value with which the log message will be compared. * @param string $message * The message to display along with the assertion. - * @param string $group - * The type of assertion - examples are "Browser", "PHP". * * @return bool * TRUE if the assertion succeeded, FALSE otherwise. */ - protected function assertRevisionCount($nid, $value, $message = '', $group = 'Other') { - $count = \Drupal::database()->select('node_revision', 'r') + protected function assertRevisionLogMessage($nid, $value, $message = '') { + // Retrieve the latest revision log message for this node. + $log_message = $this->database->select('node_revision', 'r') + ->fields('r', ['revision_log']) ->condition('nid', $nid) - ->countQuery() + ->orderBy('vid', 'DESC') + ->range(0, 1) ->execute() ->fetchColumn(); - return $this->assertEqual($count, $value, $message, $group); + return $this->assertEquals($value, $log_message, $message); } /** @@ -91,7 +86,7 @@ protected function assertRevisionCount($nid, $value, $message = '', $group = 'Ot */ public function testRevisioning() { // Create a scheduled node that is not automatically revisioned. - $created = strtotime('-2 day', REQUEST_TIME); + $created = strtotime('-2 day', $this->requestTime); $settings = [ 'type' => $this->type, 'revision' => 0, @@ -118,17 +113,15 @@ public function testRevisioning() { // Test scheduled publication with revisioning enabled. $node = $this->schedule($node); $this->assertRevisionCount($node->id(), 2, 'A new revision was created when revisioning is enabled.'); - $expected_message = sprintf('Node published by Scheduler on %s. Previous creation date was %s.', - \Drupal::service('date.formatter')->format(REQUEST_TIME, 'short'), - \Drupal::service('date.formatter')->format($created, 'short')); + $expected_message = sprintf('Published by Scheduler. The scheduled publishing date was %s.', + $this->dateFormatter->format(strtotime('-5 hour', $this->requestTime), 'short')); $this->assertRevisionLogMessage($node->id(), $expected_message, 'The correct message was found in the node revision log after scheduled publishing.'); // Test scheduled unpublication with revisioning enabled. $node = $this->schedule($node, 'unpublish'); $this->assertRevisionCount($node->id(), 3, 'A new revision was created when a node was unpublished with revisioning enabled.'); - $expected_message = sprintf('Node unpublished by Scheduler on %s. Previous change date was %s.', - \Drupal::service('date.formatter')->format(REQUEST_TIME, 'short'), - \Drupal::service('date.formatter')->format(REQUEST_TIME, 'short')); + $expected_message = sprintf('Unpublished by Scheduler. The scheduled unpublishing date was %s.', + $this->dateFormatter->format(strtotime('-5 hour', $this->requestTime), 'short')); $this->assertRevisionLogMessage($node->id(), $expected_message, 'The correct message was found in the node revision log after scheduled unpublishing.'); } @@ -140,7 +133,7 @@ public function testAlterCreationDate() { $this->nodetype->setThirdPartySetting('scheduler', 'publish_past_date', 'schedule')->save(); // Create a node with a 'created' date two days in the past. - $created = strtotime('-2 day', REQUEST_TIME); + $created = strtotime('-2 day', $this->requestTime); $settings = [ 'type' => $this->type, 'created' => $created, @@ -155,7 +148,7 @@ public function testAlterCreationDate() { // Get the created date from the node and check that it has not changed. $created_after_cron = $node->created->value; $this->assertTrue($node->isPublished(), 'The node has been published.'); - $this->assertEqual($created, $created_after_cron, 'The node creation date is not changed by default.'); + $this->assertEquals($created_after_cron, $created, 'The node creation date is not changed by default.'); // Set option to change the created date to match the publish_on date. $this->nodetype->setThirdPartySetting('scheduler', 'publish_touch', TRUE)->save(); @@ -164,7 +157,8 @@ public function testAlterCreationDate() { $node = $this->schedule($node, 'publish'); // Check that the created date has changed to match the publish_on date. $created_after_cron = $node->created->value; - $this->assertEqual(strtotime('-1 day', REQUEST_TIME), $created_after_cron, "With 'touch' option set, the node creation date is changed to match the publishing date."); + $this->assertEqual(strtotime('-5 hour', $this->requestTime), $created_after_cron, "With 'touch' option set, the node creation date is changed to match the publishing date."); + } } diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerRulesActionsTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerRulesActionsTest.php index 9bd335f9f3..63ed424e67 100644 --- a/web/modules/scheduler/tests/src/Functional/SchedulerRulesActionsTest.php +++ b/web/modules/scheduler/tests/src/Functional/SchedulerRulesActionsTest.php @@ -8,6 +8,9 @@ * Tests the six actions that Scheduler provides for use in Rules module. * * @group scheduler + * @group legacy + * @todo Remove the 'legacy' tag when Rules no longer uses deprecated code. + * @see https://www.drupal.org/project/scheduler/issues/2924353 */ class SchedulerRulesActionsTest extends SchedulerBrowserTestBase { @@ -57,7 +60,7 @@ public function testPublishOnActions() { $rule1->addAction('scheduler_set_publishing_date_action', ContextConfig::create() ->map('node', 'node') - ->setValue('date', REQUEST_TIME + 1800) + ->setValue('date', $this->requestTime + 1800) ) ->addAction('rules_system_message', ContextConfig::create() @@ -113,7 +116,7 @@ public function testPublishOnActions() { $edit = [ 'body[0][value]' => $this->randomString(30), ]; - $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save')); + $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save'); $this->nodeStorage->resetCache([$node->id()]); $node = $this->nodeStorage->load($node->id()); // Check that neither of the rules are triggered, no publish and unpublish @@ -129,7 +132,7 @@ public function testPublishOnActions() { 'title[0][value]' => 'Trigger Action Rule 1', 'body[0][value]' => $this->randomString(30), ]; - $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, t('Save')); + $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, 'Save'); $this->nodeStorage->resetCache([$this->node->id()]); $node = $this->nodeStorage->load($this->node->id()); // Check that rule 1 is triggered and rule 2 is not. Check that a publishing @@ -145,7 +148,7 @@ public function testPublishOnActions() { 'title[0][value]' => 'Trigger Action Rule 2', 'body[0][value]' => $this->randomString(30), ]; - $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, t('Save')); + $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, 'Save'); $this->nodeStorage->resetCache([$this->node->id()]); $node = $this->nodeStorage->load($this->node->id()); // Check that rule 2 is triggered and rule 1 is not. Check that the @@ -179,7 +182,7 @@ public function testUnpublishOnActions() { $rule3->addAction('scheduler_set_unpublishing_date_action', ContextConfig::create() ->map('node', 'node') - ->setValue('date', REQUEST_TIME + 1800) + ->setValue('date', $this->requestTime + 1800) ) ->addAction('rules_system_message', ContextConfig::create() @@ -235,7 +238,7 @@ public function testUnpublishOnActions() { $edit = [ 'body[0][value]' => $this->randomString(30), ]; - $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save')); + $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save'); $this->nodeStorage->resetCache([$node->id()]); $node = $this->nodeStorage->load($node->id()); // Check that neither of the rules are triggered, no publish and unpublish @@ -251,7 +254,7 @@ public function testUnpublishOnActions() { 'title[0][value]' => 'Trigger Action Rule 3', 'body[0][value]' => $this->randomString(30), ]; - $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, t('Save')); + $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, 'Save'); $this->nodeStorage->resetCache([$this->node->id()]); $node = $this->nodeStorage->load($this->node->id()); // Check that rule 3 is triggered and rule 4 is not. Check that an @@ -267,7 +270,7 @@ public function testUnpublishOnActions() { 'title[0][value]' => 'Trigger Action Rule 4', 'body[0][value]' => $this->randomString(30), ]; - $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, t('Save')); + $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, 'Save'); $this->nodeStorage->resetCache([$this->node->id()]); $node = $this->nodeStorage->load($this->node->id()); // Check that rule 4 is triggered and rule 3 is not. Check that the diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerRulesConditionsTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerRulesConditionsTest.php index 62a07efc36..7a246a99ce 100644 --- a/web/modules/scheduler/tests/src/Functional/SchedulerRulesConditionsTest.php +++ b/web/modules/scheduler/tests/src/Functional/SchedulerRulesConditionsTest.php @@ -8,6 +8,9 @@ * Tests the four conditions that Scheduler provides for use in Rules module. * * @group scheduler + * @group legacy + * @todo Remove the 'legacy' tag when Rules no longer uses deprecated code. + * @see https://www.drupal.org/project/scheduler/issues/2924353 */ class SchedulerRulesConditionsTest extends SchedulerBrowserTestBase { @@ -225,7 +228,7 @@ public function testNodeIsScheduledConditions() { $edit = [ 'body[0][value]' => $this->randomString(30), ]; - $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, t('Save')); + $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, 'Save'); $this->assertText($message5, '"' . $message5 . '" is shown'); $this->assertText($message6, '"' . $message6 . '" is shown'); @@ -234,10 +237,10 @@ public function testNodeIsScheduledConditions() { // Edit the node and set a publish_on date. $edit = [ - 'publish_on[0][value][date]' => date('Y-m-d', strtotime('+1 day', REQUEST_TIME)), - 'publish_on[0][value][time]' => date('H:i:s', strtotime('+1 day', REQUEST_TIME)), + 'publish_on[0][value][date]' => date('Y-m-d', strtotime('+1 day', $this->requestTime)), + 'publish_on[0][value][time]' => date('H:i:s', strtotime('+1 day', $this->requestTime)), ]; - $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, t('Save')); + $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, 'Save'); $this->assertNoText($message5, '"' . $message5 . '" is not shown'); $this->assertText($message6, '"' . $message6 . '" is shown'); @@ -246,10 +249,10 @@ public function testNodeIsScheduledConditions() { // Edit the node and set an unpublish_on date. $edit = [ - 'unpublish_on[0][value][date]' => date('Y-m-d', strtotime('+2 day', REQUEST_TIME)), - 'unpublish_on[0][value][time]' => date('H:i:s', strtotime('+2 day', REQUEST_TIME)), + 'unpublish_on[0][value][date]' => date('Y-m-d', strtotime('+2 day', $this->requestTime)), + 'unpublish_on[0][value][time]' => date('H:i:s', strtotime('+2 day', $this->requestTime)), ]; - $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, t('Save')); + $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, 'Save'); $this->assertNoText($message5, '"' . $message5 . '" is not shown'); $this->assertNoText($message6, '"' . $message6 . '" is not shown'); diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerRulesEventsTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerRulesEventsTest.php index f7276ad61d..f600246442 100644 --- a/web/modules/scheduler/tests/src/Functional/SchedulerRulesEventsTest.php +++ b/web/modules/scheduler/tests/src/Functional/SchedulerRulesEventsTest.php @@ -8,6 +8,9 @@ * Tests the six events that Scheduler provides for use in Rules module. * * @group scheduler + * @group legacy + * @todo Remove the 'legacy' tag when Rules no longer uses deprecated code. + * @see https://www.drupal.org/project/scheduler/issues/2924353 */ class SchedulerRulesEventsTest extends SchedulerBrowserTestBase { @@ -49,6 +52,11 @@ public function testRulesEvents() { 5 => ['scheduler_existing_node_is_scheduled_for_unpublishing_event', 'An existing node is saved and is scheduled for unpublishing.'], 6 => ['scheduler_has_unpublished_this_node_event', 'Scheduler has unpublished this node during cron.'], ]; + // PHPCS throws a false-positive 'variable $var is undefined' message when + // the variable is defined by list( ) syntax. To avoid the unwanted warnings + // we can wrap the section with @codingStandardsIgnoreStart and IgnoreEnd. + // @see https://www.drupal.org/project/coder/issues/2876245 + // @codingStandardsIgnoreStart foreach ($rule_data as $i => list($event_name, $description)) { $rule[$i] = $this->expressionManager->createRule(); $message[$i] = 'RULES message ' . $i . '. ' . $description; @@ -63,6 +71,7 @@ public function testRulesEvents() { ]); $config_entity->save(); } + // @codingStandardsIgnoreEnd $this->drupalLogin($this->schedulerUser); @@ -72,7 +81,7 @@ public function testRulesEvents() { 'title[0][value]' => 'Test for no events', 'body[0][value]' => $this->randomString(30), ]; - $this->drupalPostForm('node/add/' . $this->type, $edit, t('Save')); + $this->drupalPostForm('node/add/' . $this->type, $edit, 'Save'); $node = $this->drupalGetNodeByTitle($edit['title[0][value]']); $this->assertNoText($message[1], '"' . $message[1] . '" is not shown'); $this->assertNoText($message[2], '"' . $message[2] . '" is not shown'); @@ -85,7 +94,7 @@ public function testRulesEvents() { $edit = [ 'body[0][value]' => $this->randomString(30), ]; - $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save')); + $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save'); $this->assertNoText($message[1], '"' . $message[1] . '" is not shown'); $this->assertNoText($message[2], '"' . $message[2] . '" is not shown'); $this->assertNoText($message[3], '"' . $message[3] . '" is not shown'); @@ -101,7 +110,7 @@ public function testRulesEvents() { 'publish_on[0][value][time]' => date('H:i:s', time() + 3), 'body[0][value]' => $this->randomString(30), ]; - $this->drupalPostForm('node/add/' . $this->type, $edit, t('Save')); + $this->drupalPostForm('node/add/' . $this->type, $edit, 'Save'); $node = $this->drupalGetNodeByTitle($edit['title[0][value]']); $this->assertText($message[1], '"' . $message[1] . '" IS shown'); $this->assertNoText($message[2], '"' . $message[2] . '" is not shown'); @@ -115,7 +124,7 @@ public function testRulesEvents() { 'title[0][value]' => 'Edit node with publish-on date', 'body[0][value]' => $this->randomString(30), ]; - $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save')); + $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save'); $this->assertNoText($message[1], '"' . $message[1] . '" is not shown'); $this->assertText($message[2], '"' . $message[2] . '" IS shown'); $this->assertNoText($message[3], '"' . $message[3] . '" is not shown'); @@ -143,7 +152,7 @@ public function testRulesEvents() { 'unpublish_on[0][value][time]' => date('H:i:s', time() + 3), 'body[0][value]' => $this->randomString(30), ]; - $this->drupalPostForm('node/add/' . $this->type, $edit, t('Save')); + $this->drupalPostForm('node/add/' . $this->type, $edit, 'Save'); $node = $this->drupalGetNodeByTitle($edit['title[0][value]']); $this->assertNoText($message[1], '"' . $message[1] . '" is not shown'); $this->assertNoText($message[2], '"' . $message[2] . '" is not shown'); @@ -157,7 +166,7 @@ public function testRulesEvents() { 'title[0][value]' => 'Edit node with unpublish-on date', 'body[0][value]' => $this->randomString(30), ]; - $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save')); + $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save'); $this->assertNoText($message[1], '"' . $message[1] . '" is not shown'); $this->assertNoText($message[2], '"' . $message[2] . '" is not shown'); $this->assertNoText($message[3], '"' . $message[3] . '" is not shown'); @@ -187,7 +196,7 @@ public function testRulesEvents() { 'unpublish_on[0][value][time]' => date('H:i:s', time() + 4), 'body[0][value]' => $this->randomString(30), ]; - $this->drupalPostForm('node/add/' . $this->type, $edit, t('Save')); + $this->drupalPostForm('node/add/' . $this->type, $edit, 'Save'); $node = $this->drupalGetNodeByTitle($edit['title[0][value]']); $this->assertText($message[1], '"' . $message[1] . '" IS shown'); $this->assertNoText($message[2], '"' . $message[2] . '" is not shown'); @@ -201,7 +210,7 @@ public function testRulesEvents() { 'title[0][value]' => 'Edit node with both dates', 'body[0][value]' => $this->randomString(30), ]; - $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save')); + $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save'); $node = $this->drupalGetNodeByTitle($edit['title[0][value]']); $this->assertNoText($message[1], '"' . $message[1] . '" is not shown'); $this->assertText($message[2], '"' . $message[2] . '" IS shown'); diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerStatusReportTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerStatusReportTest.php index b58c1f169b..ef4196825d 100644 --- a/web/modules/scheduler/tests/src/Functional/SchedulerStatusReportTest.php +++ b/web/modules/scheduler/tests/src/Functional/SchedulerStatusReportTest.php @@ -2,6 +2,8 @@ namespace Drupal\Tests\scheduler\Functional; +use Drupal\Core\Url; + /** * Tests the Scheduler section of the status report. * @@ -19,13 +21,13 @@ public function testStatusReport() { $this->assertText('Scheduler Time Check'); $this->assertText('In most cases the server time should match Coordinated Universal Time (UTC) / Greenwich Mean Time (GMT)'); - $admin_regional_settings = \Drupal::url('system.regional_settings'); + $admin_regional_settings = Url::fromRoute('system.regional_settings'); $this->assertLink('changed by admin users'); - $this->assertLinkByHref($admin_regional_settings); + $this->assertLinkByHref($admin_regional_settings->toString()); - $account_edit = \Drupal::url('entity.user.edit_form', ['user' => $this->adminUser->id()]); + $account_edit = Url::fromRoute('entity.user.edit_form', ['user' => $this->adminUser->id()]); $this->assertLink('user account'); - $this->assertLinkByHref($account_edit); + $this->assertLinkByHref($account_edit->toString()); } } diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerTokenReplaceTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerTokenReplaceTest.php index ceb4487466..784f960034 100644 --- a/web/modules/scheduler/tests/src/Functional/SchedulerTokenReplaceTest.php +++ b/web/modules/scheduler/tests/src/Functional/SchedulerTokenReplaceTest.php @@ -14,11 +14,9 @@ class SchedulerTokenReplaceTest extends SchedulerBrowserTestBase { */ public function testSchedulerTokenReplacement() { $this->drupalLogin($this->schedulerUser); - $date_formatter = \Drupal::service('date.formatter'); - // Define timestamps for consistent use when repeated throughout this test. - $publish_on_timestamp = REQUEST_TIME + 3600; - $unpublish_on_timestamp = REQUEST_TIME + 7200; + $publish_on_timestamp = $this->requestTime + 3600; + $unpublish_on_timestamp = $this->requestTime + 7200; // Create an unpublished page with scheduled dates. $node = $this->drupalCreateNode([ @@ -48,7 +46,7 @@ public function testSchedulerTokenReplacement() { $edit = [ 'body[0][value]' => 'Publish on: [node:scheduler-publish' . $test_data['token_format'] . ']. Unpublish on: [node:scheduler-unpublish' . $test_data['token_format'] . '].', ]; - $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save')); + $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save'); $this->drupalGet('node/' . $node->id()); // Refresh the node and get the body output value. @@ -57,11 +55,11 @@ public function testSchedulerTokenReplacement() { $body_output = \Drupal::token()->replace($node->body->value, ['node' => $node]); // Create the expected text for the body. - $publish_on_date = $date_formatter->format($publish_on_timestamp, $test_data['date_format'], $test_data['custom']); - $unpublish_on_date = $date_formatter->format($unpublish_on_timestamp, $test_data['date_format'], $test_data['custom']); + $publish_on_date = $this->dateFormatter->format($publish_on_timestamp, $test_data['date_format'], $test_data['custom']); + $unpublish_on_date = $this->dateFormatter->format($unpublish_on_timestamp, $test_data['date_format'], $test_data['custom']); $expected_output = 'Publish on: ' . $publish_on_date . '. Unpublish on: ' . $unpublish_on_date . '.'; // Check that the actual text matches the expected value. - $this->assertEqual($body_output, $expected_output, 'Scheduler tokens replaced correctly for ' . $test_data['token_format'] . ' format.'); + $this->assertEquals($expected_output, $body_output, 'Scheduler tokens replaced correctly for ' . $test_data['token_format'] . ' format.'); } } diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerValidationTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerValidationTest.php index 346ac01536..fa5dc21b45 100644 --- a/web/modules/scheduler/tests/src/Functional/SchedulerValidationTest.php +++ b/web/modules/scheduler/tests/src/Functional/SchedulerValidationTest.php @@ -2,8 +2,6 @@ namespace Drupal\Tests\scheduler\Functional; -use Drupal\Component\Utility\SafeMarkup; - /** * Tests the validation when editing a node. * @@ -30,8 +28,8 @@ public function testValidationDuringEdit() { 'status' => FALSE, ]); $edit = [ - 'publish_on[0][value][date]' => date('Y-m-d', strtotime('+1 day', REQUEST_TIME)), - 'publish_on[0][value][time]' => date('H:i:s', strtotime('+1 day', REQUEST_TIME)), + 'publish_on[0][value][date]' => date('Y-m-d', strtotime('+1 day', $this->requestTime)), + 'publish_on[0][value][time]' => date('H:i:s', strtotime('+1 day', $this->requestTime)), ]; $this->drupalGet('node/' . $node->id() . '/edit'); @@ -43,8 +41,8 @@ public function testValidationDuringEdit() { $checkbox = $this->xpath('//input[@type="checkbox" and @id="edit-status-value"]'); $this->submitForm($edit, $checkbox ? 'Save' : 'Save and keep unpublished'); - $this->assertRaw(t("If you set a 'publish on' date then you must also set an 'unpublish on' date."), 'Validation prevents entering a publish-on date with no unpublish-on date if unpublishing is required.'); - $this->assertNoRaw(t('@type %title has been updated.', ['@type' => $this->typeName, '%title' => SafeMarkup::checkPlain($node->title->value)]), 'The node has not been saved.'); + $this->assertText("If you set a 'publish on' date then you must also set an 'unpublish on' date.", 'Validation prevents entering a publish-on date with no unpublish-on date if unpublishing is required.'); + $this->assertNoText(sprintf('%s %s has been updated.', $this->typeName, $node->title->value), 'The node has not been saved.'); // Create an unpublished page node, then edit the node and check that if the // status is changed to published, then an unpublish-on date is also needed. @@ -59,8 +57,8 @@ public function testValidationDuringEdit() { $edit = []; } $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, $checkbox ? 'Save' : 'Save and publish'); - $this->assertRaw(t("Either you must set an 'unpublish on' date or save this node as unpublished."), 'Validation prevents publishing the node directly without an unpublish-on date if unpublishing is required.'); - $this->assertNoRaw(t('@type %title has been updated.', ['@type' => $this->typeName, '%title' => SafeMarkup::checkPlain($node->title->value)]), 'The node has not been saved.'); + $this->assertText("Either you must set an 'unpublish on' date or save this node as unpublished.", 'Validation prevents publishing the node directly without an unpublish-on date if unpublishing is required.'); + $this->assertNoText(sprintf('%s %s has been updated.', $this->typeName, $node->title->value), 'The node has not been saved.'); // Create an unpublished node, edit the node and check that if both dates // are entered then the unpublish date is later than the publish date. @@ -69,14 +67,14 @@ public function testValidationDuringEdit() { 'status' => FALSE, ]); $edit = [ - 'publish_on[0][value][date]' => \Drupal::service('date.formatter')->format(REQUEST_TIME + 8100, 'custom', 'Y-m-d'), - 'publish_on[0][value][time]' => \Drupal::service('date.formatter')->format(REQUEST_TIME + 8100, 'custom', 'H:i:s'), - 'unpublish_on[0][value][date]' => \Drupal::service('date.formatter')->format(REQUEST_TIME + 1800, 'custom', 'Y-m-d'), - 'unpublish_on[0][value][time]' => \Drupal::service('date.formatter')->format(REQUEST_TIME + 1800, 'custom', 'H:i:s'), + 'publish_on[0][value][date]' => $this->dateFormatter->format($this->requestTime + 8100, 'custom', 'Y-m-d'), + 'publish_on[0][value][time]' => $this->dateFormatter->format($this->requestTime + 8100, 'custom', 'H:i:s'), + 'unpublish_on[0][value][date]' => $this->dateFormatter->format($this->requestTime + 1800, 'custom', 'Y-m-d'), + 'unpublish_on[0][value][time]' => $this->dateFormatter->format($this->requestTime + 1800, 'custom', 'H:i:s'), ]; $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, $checkbox ? 'Save' : 'Save and keep unpublished'); - $this->assertRaw(t("The 'unpublish on' date must be later than the 'publish on' date."), 'Validation prevents entering an unpublish-on date which is earlier than the publish-on date.'); - $this->assertNoRaw(t('@type %title has been updated.', ['@type' => $this->typeName, '%title' => SafeMarkup::checkPlain($node->title->value)]), 'The node has not been saved.'); + $this->assertText("The 'unpublish on' date must be later than the 'publish on' date.", 'Validation prevents entering an unpublish-on date which is earlier than the publish-on date.'); + $this->assertNoText(sprintf('%s %s has been updated.', $this->typeName, $node->title->value), 'The node has not been saved.'); } } -- GitLab