diff --git a/composer.lock b/composer.lock index d2e2e81f9..e910e6a5d 100644 --- a/composer.lock +++ b/composer.lock @@ -1278,17 +1278,17 @@ }, { "name": "drupal/address", - "version": "1.2.0", + "version": "1.3.0", "source": { "type": "git", "url": "https://git.drupal.org/project/address", - "reference": "8.x-1.2" + "reference": "8.x-1.3" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/address-8.x-1.2.zip", - "reference": "8.x-1.2", - "shasum": "041445ac14087be943c0c1c562b9bf800d87f7e8" + "url": "https://ftp.drupal.org/files/projects/address-8.x-1.3.zip", + "reference": "8.x-1.3", + "shasum": "170551d6ecf4a08bac178f31b9869a626279d9eb" }, "require": { "commerceguys/addressing": "~1.0", @@ -1301,8 +1301,8 @@ "dev-1.x": "1.x-dev" }, "drupal": { - "version": "8.x-1.2", - "datestamp": "1505896144", + "version": "8.x-1.3", + "datestamp": "1511382784", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" @@ -1583,7 +1583,7 @@ "source": { "type": "git", "url": "https://git.drupal.org/project/commerce_license", - "reference": "6504af9fb47979a78a1f619b582a08b68506bbe6" + "reference": "4afdcf9970abc8d648d5e9ec9d79ec0a56280822" }, "require": { "drupal/commerce": "*", @@ -1601,7 +1601,7 @@ }, "drupal": { "version": "8.x-2.x-dev", - "datestamp": "1511174884", + "datestamp": "1511209685", "security-coverage": { "status": "not-covered", "message": "Dev releases are not covered by Drupal security advisories." @@ -1643,7 +1643,7 @@ "source": "http://cgit.drupalcode.org/commerce_license", "issues": "https://www.drupal.org/project/issues/commerce_license" }, - "time": "2017-11-20 20:23:24" + "time": "2017-11-24 16:17:19" }, { "name": "drupal/commerce_order", @@ -7603,16 +7603,16 @@ }, { "name": "phpspec/prophecy", - "version": "v1.7.2", + "version": "1.7.3", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6" + "reference": "e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6", - "reference": "c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf", + "reference": "e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf", "shasum": "" }, "require": { @@ -7624,7 +7624,7 @@ }, "require-dev": { "phpspec/phpspec": "^2.5|^3.2", - "phpunit/phpunit": "^4.8 || ^5.6.5" + "phpunit/phpunit": "^4.8.35 || ^5.7" }, "type": "library", "extra": { @@ -7662,7 +7662,7 @@ "spy", "stub" ], - "time": "2017-09-04T11:05:03+00:00" + "time": "2017-11-24T13:59:53+00:00" }, { "name": "phpunit/php-code-coverage", diff --git a/docroot/modules/contrib/address/README.md b/docroot/modules/contrib/address/README.md index 422c6206d..a0fcdf67b 100644 --- a/docroot/modules/contrib/address/README.md +++ b/docroot/modules/contrib/address/README.md @@ -1,27 +1,31 @@ -Address -======= -[![Build Status](https://travis-ci.org/bojanz/address.svg?branch=8.x-1.x)](https://travis-ci.org/bojanz/address) +# Address Provides functionality for storing, validating and displaying international postal addresses. The Drupal 8 heir to the addressfield module, powered by the [commerceguys/addressing](https://github.com/commerceguys/addressing) library. -Installation -------------- -This module needs to be installed via Composer, which will download the required libraries. - -1. Add the Drupal Packagist repository - - ```sh - composer config repositories.drupal composer https://packages.drupal.org/8 - ``` -This allows Composer to find Address and the other Drupal modules. - -2. Download Address +## Installation +Since the module requires external libraries, Composer or Ludwig must be used. +### Composer +If your site is [managed via Composer](https://www.drupal.org/node/2718229), use Composer to +download the module, which will also download the required libraries: ```sh composer require "drupal/address ~1.0" ``` -This will download the latest release of Address. -Use 1.x-dev instead of ~1.0 to get the -dev release instead. +~1.0 downloads the latest release, use 1.x-dev to get the -dev release instead. +Use ```composer update drupal/address --with-dependencies``` to update to a new release. + +### Ludwig +Otherwise, download and install [Ludwig](https://www.drupal.org/project/ludwig) which will allow you +to download the libraries separately: +1) Download Address into your modules folder. +2) Use one of Ludwig's methods to download libraries: + + a) Run the ```ludwig:download``` Drupal Console command or the ```ludwig-download``` Drush command. + + b) Go to ```/admin/reports/packages``` and download each library manually, then place them under address/lib as specified. + +3) Enable Address. -See https://www.drupal.org/node/2404989 for more information. +Note that when using Ludwig, updating the module will require re-downloading the libraries. +Composer is recommended whenever possible. diff --git a/docroot/modules/contrib/address/address.info.yml b/docroot/modules/contrib/address/address.info.yml index 1ae707cdc..0aab8acff 100644 --- a/docroot/modules/contrib/address/address.info.yml +++ b/docroot/modules/contrib/address/address.info.yml @@ -7,8 +7,8 @@ config: entity.address_format.collection dependencies: - drupal:field -# Information added by Drupal.org packaging script on 2017-09-20 -version: '8.x-1.2' +# Information added by Drupal.org packaging script on 2017-11-22 +version: '8.x-1.3' core: '8.x' project: 'address' -datestamp: 1505896147 +datestamp: 1511382787 diff --git a/docroot/modules/contrib/address/src/Plugin/migrate/cckfield/AddressField.php b/docroot/modules/contrib/address/src/Plugin/migrate/cckfield/AddressField.php index 269b161f6..15f119bb3 100644 --- a/docroot/modules/contrib/address/src/Plugin/migrate/cckfield/AddressField.php +++ b/docroot/modules/contrib/address/src/Plugin/migrate/cckfield/AddressField.php @@ -2,8 +2,11 @@ namespace Drupal\address\Plugin\migrate\cckfield; +@trigger_error('AddressField is deprecated in Address 1.3 and will be be removed before Address 2.x. Use \Drupal\address\Plugin\migrate\field\AddressField instead.', E_USER_DEPRECATED); + use Drupal\migrate\Plugin\MigrationInterface; -use Drupal\migrate_drupal\Plugin\migrate\cckfield\CckFieldPluginBase; +use Drupal\address\Plugin\migrate\field\AddressField as BaseAddressField; +use Drupal\migrate_drupal\Plugin\MigrateCckFieldInterface; /** * @MigrateCckField( @@ -11,38 +14,21 @@ * core = {7}, * type_map = { * "addressfield" = "address" - * } + * }, + * source_module = "addressfield", + * destination_module = "address" * ) + * + * @deprecated in 1.3, to be removed before 2.x. Use + * \Drupal\address\Plugin\migrate\field\AddressField instead. */ -class AddressField extends CckFieldPluginBase { - - /** - * {@inheritdoc} - */ - public function getFieldFormatterMap() { - return [ - 'addressfield_default' => 'address_default', - ]; - } - - /** - * {@inheritdoc} - */ - public function getFieldWidgetMap() { - return [ - 'addressfield_standard' => 'address_default', - ]; - } +class AddressField extends BaseAddressField implements MigrateCckFieldInterface { /** * {@inheritdoc} */ public function processCckFieldValues(MigrationInterface $migration, $field_name, $data) { - $process = [ - 'plugin' => 'addressfield', - 'source' => $field_name, - ]; - $migration->mergeProcessOfProperty($field_name, $process); + return $this->processFieldValues($migration, $field_name, $data); } } diff --git a/docroot/modules/contrib/address/src/Plugin/migrate/field/AddressField.php b/docroot/modules/contrib/address/src/Plugin/migrate/field/AddressField.php new file mode 100644 index 000000000..10fd03fd1 --- /dev/null +++ b/docroot/modules/contrib/address/src/Plugin/migrate/field/AddressField.php @@ -0,0 +1,50 @@ + 'address_default', + ]; + } + + /** + * {@inheritdoc} + */ + public function getFieldWidgetMap() { + return [ + 'addressfield_standard' => 'address_default', + ]; + } + + /** + * {@inheritdoc} + */ + public function processFieldValues(MigrationInterface $migration, $field_name, $data) { + $process = [ + 'plugin' => 'addressfield', + 'source' => $field_name, + ]; + $migration->mergeProcessOfProperty($field_name, $process); + } + +} diff --git a/docroot/modules/contrib/address/tests/modules/address_test/address_test.info.yml b/docroot/modules/contrib/address/tests/modules/address_test/address_test.info.yml index bcca0b8a3..ef1083dfb 100644 --- a/docroot/modules/contrib/address/tests/modules/address_test/address_test.info.yml +++ b/docroot/modules/contrib/address/tests/modules/address_test/address_test.info.yml @@ -7,8 +7,8 @@ dependencies: - address - views -# Information added by Drupal.org packaging script on 2017-09-20 -version: '8.x-1.2' +# Information added by Drupal.org packaging script on 2017-11-22 +version: '8.x-1.3' core: '8.x' project: 'address' -datestamp: 1505896147 +datestamp: 1511382787 diff --git a/docroot/modules/contrib/address/tests/src/Kernel/Plugin/migrate/field/AddressFieldTest.php b/docroot/modules/contrib/address/tests/src/Kernel/Plugin/migrate/field/AddressFieldTest.php new file mode 100644 index 000000000..507b63df4 --- /dev/null +++ b/docroot/modules/contrib/address/tests/src/Kernel/Plugin/migrate/field/AddressFieldTest.php @@ -0,0 +1,52 @@ +prophesize(MigrationInterface::class)->reveal(); + $field_plugin_manager = $this->container->get('plugin.manager.migrate.field'); + $definition = $field_plugin_manager->getDefinition('addressfield'); + $this->assertNotEmpty($definition); + $field_plugin = $field_plugin_manager->createInstance('addressfield', [], $migration); + $this->assertInstanceOf(AddressField::class, $field_plugin); + } + + /** + * Tests discovery of the cck field plugin. + */ + public function testLegacyPlugin() { + $migration = $this->prophesize(MigrationInterface::class)->reveal(); + $cck_plugin_manager = $this->container->get('plugin.manager.migrate.cckfield'); + $definition = $cck_plugin_manager->getDefinition('addressfield'); + $this->assertNotEmpty($definition); + $cck_plugin = $cck_plugin_manager->createInstance('addressfield', [], $migration); + $this->assertInstanceOf(CckAddressField::class, $cck_plugin); + } + +} diff --git a/docroot/modules/contrib/commerce_license/README.txt b/docroot/modules/contrib/commerce_license/README.txt new file mode 100644 index 000000000..195cbf87d --- /dev/null +++ b/docroot/modules/contrib/commerce_license/README.txt @@ -0,0 +1,23 @@ +INTRODUCTION +------------ + +The Commerce License allows the creation of products that sell access to some +aspect of the site. This could be a role, publication of a node, and so on. + +This access is controlled by a License entity, which is created for the user +when the product is purchased. + +The nature of what a License entity grants is handled by License type plugins. +Each License entity will have one License type plugin associated with it. + +A product variation that sells a License will have a configured License type +plugin field value. This acts as template to create the License when a user +purchases that product variation. + +REQUIREMENTS +------------ + +This module requires the following modules: + + * Commerce (https://drupal.org/project/commerce) + * Recurring Period (https://drupal.org/project/recurring_period) diff --git a/docroot/modules/contrib/commerce_license/commerce_license.api.php b/docroot/modules/contrib/commerce_license/commerce_license.api.php new file mode 100644 index 000000000..6e0a51485 --- /dev/null +++ b/docroot/modules/contrib/commerce_license/commerce_license.api.php @@ -0,0 +1,39 @@ +' . t('About') . ''; + $output .= '

' . t('License entities and product behavior') . '

'; + return $output; + + default: + } +} + +/** + * Implements hook_form_alter(). + */ +function commerce_license_form_alter(&$form, FormStateInterface $form_state, $form_id) { + $form_object = $form_state->getFormObject(); + + if ($form_object instanceof EntityFormInterface) { + $form_alter = new GrantedEntityFormAlter($form_state->getFormObject()->getEntity()); + $form_alter->formAlter($form, $form_state, $form_id); + } +} + +/** + * Implements hook_form_BASE_FORM_ID_alter(): commerce_product_variation_type_form + * + * @see commerce_license_field_widget_form_alter() + */ +function commerce_license_form_commerce_product_variation_type_form_alter(&$form, FormStateInterface $form_state, $form_id) { + $variation_type = $form_state->getFormObject()->getEntity(); + + // Add checkboxes to the product variation type form to select the license + // types that product variations of this type may use. + $options = array_column(\Drupal::service('plugin.manager.commerce_license_type')->getDefinitions(), 'label', 'id'); + $our_form['license_types'] = [ + '#type' => 'checkboxes', + '#title' => t("Available license types"), + '#description' => t("Limit the license types that can be used on product variations of this type. All types will be allowed if none are selected."), + '#options' => $options, + '#default_value' => $variation_type->getThirdPartySetting('commerce_license', 'license_types') ?: [], + // Only show this if the license trait is set on the product variation type. + '#states' => [ + 'visible' => [ + ':input[name="traits[commerce_license]"]' => ['checked' => TRUE], + ], + ], + ]; + // TODO: consider whether to lock this once the product variation type is + // created or has product variation entities, or at least lock the enabled + // license types. + + // Insert our form elements into the form after the 'traits' element. + // The form elements don't have their weight set, so we can't use that. + $traits_element_form_array_index = array_search('traits', array_keys($form)); + + $form = array_merge( + array_slice($form, 0, $traits_element_form_array_index + 1), + $our_form, + array_slice($form, $traits_element_form_array_index + 1) + ); + + $form['#validate'][] = 'commerce_license_form_commerce_product_variation_type_form_validate'; + + $form['actions']['submit']['#submit'][] = 'commerce_license_form_commerce_product_variation_type_form_submit'; +} + +/** + * Form validation handler to ensure everything joins up for license traits. + * + * @see commerce_license_form_commerce_product_variation_type_form_alter() + */ +function commerce_license_form_commerce_product_variation_type_form_validate($form, FormStateInterface $form_state) { + $traits = $form_state->getValue('traits'); + $original_traits = $form_state->getValue('original_traits'); + + // Only validate if our trait is in use. Need to check both new traits and + // existing traits values, as the 'traits' form value won't have a value for + // a checkbox that's disabled because an existing trait can't be removed. + if (empty($traits['commerce_license']) && !in_array('commerce_license', $original_traits)) { + return; + } + + // The order item type must have the license trait. + $order_item_type_id = $form_state->getValue('orderItemType'); + $order_item_type = \Drupal::entityTypeManager()->getStorage('commerce_order_item_type')->load($order_item_type_id); + + if (!in_array('commerce_license_order_item_type', $order_item_type->getTraits())) { + $form_state->setError($form['orderItemType'], t( + 'The License trait requires an order item type with the order item license trait. ' . + 'This product variation is set to use the @order-item-type-label order item type. You must either change this, or edit the order item type to add the license trait.', + [ + '@order-item-type-label' => $order_item_type->label(), + '@url-edit-order-item-type' => $order_item_type->toUrl('edit-form')->toString(), + ] + )); + } + + // The order must use a workflow with a fulfilment state. + $order_type_id = $order_item_type->getOrderTypeId(); + $order_type = \Drupal::entityTypeManager()->getStorage('commerce_order_type')->load($order_type_id); + $workflow_id = $order_type->getWorkflowId(); + $workflow = \Drupal::service('plugin.manager.workflow')->createInstance($workflow_id); + $states = $workflow->getStates(); + if (!isset($states['fulfillment'])) { + $form_state->setError($form['orderItemType'], t( + "The License trait requires an order workflow with the 'fulfillment' state. " . + 'This product variation is set to use the @order-item-type-label order item type, ' . + 'which is set to use the @order-type-label order type, ' . + 'which is set to use the @workflow-label workflow. ' . + 'You must either change this, or edit the order type to change the workflow.', + [ + '@order-item-type-label' => $order_item_type->label(), + '@order-type-label' => $order_type->label(), + '@workflow-label' => $workflow->getLabel(), + '@url-edit-order-type' => $order_type->toUrl('edit-form')->toString(), + ] + )); + } + + // The checkout flow may not allow anonymous checkout. + $checkout_flow_id = $order_type->getThirdPartySetting('commerce_checkout', 'checkout_flow'); + if ($checkout_flow_id) { + $checkout_flow = \Drupal::entityTypeManager()->getStorage('commerce_checkout_flow')->load($checkout_flow_id); + $login_pane_configuration = $checkout_flow->get('configuration')['panes']['login']; + if ($login_pane_configuration['step'] != '_disabled') { + if ($login_pane_configuration['allow_guest_checkout']) { + $form_state->setError($form['orderItemType'], t( + "The License trait requires a checkout flow that does not allow guest checkout. " . + 'This product variation is set to use the @order-item-type-label order item type, ' . + 'which is set to use the @order-type-label order type, ' . + 'which is set to use the @flow-label checkout flow. ' . + 'You must either change this, or edit the checkout flow.', + [ + '@order-item-type-label' => $order_item_type->label(), + '@order-type-label' => $order_type->label(), + '@flow-label' => $checkout_flow->label(), + '@url-checkout-flow' => $checkout_flow->toUrl('edit-form')->toString(), + ] + )); + } + } + } +} + +/** + * Form submit handler for our alterations to the product variation type form. + * + * @see commerce_license_form_commerce_product_variation_type_form_alter() + */ +function commerce_license_form_commerce_product_variation_type_form_submit($form, FormStateInterface $form_state) { + $value = $form_state->getValue('license_types'); + $license_types = array_filter($value); + + $variation_type = $form_state->getFormObject()->getEntity(); + $variation_type->setThirdPartySetting('commerce_license', 'license_types', $license_types); + // This is saving it a second time... but Commerce does the same in its form + // alterations. + $variation_type->save(); +} + +/** + * Implements hook_field_widget_form_alter(). + * + * @see commerce_license_form_commerce_product_variation_type_form_alter() + */ +function commerce_license_field_widget_form_alter(&$element, FormStateInterface $form_state, $context) { + /** @var \Drupal\Core\Field\FieldDefinitionInterface $field_definition */ + $field_definition = $context['items']->getFieldDefinition(); + $field_name = $field_definition->getName(); + $entity_type_id = $field_definition->getTargetEntityTypeId(); + + if ($field_name == 'license_type' && $entity_type_id == 'commerce_product_variation') { + // If the plugin ID form element doesn't have an '#options' key, then we + // can't remove the plugins that should not be allowed here, either because + // the widget for this field has been changed by an admin, or because the + // code for the commerce_plugin_item field type widgets has been changed. + // In which case, just crash rather than allowing access to a license type + // that shouldn't be allowed. License types can escalate a user's privileges + // on the site, and so granting license that shouldn't be allowed is a + // security risk. + if (!isset($element['target_plugin_id']['#options'])) { + throw new \Exception("Unable to change the plugin type options on the license_type field on the commerce_product_variation. Check the field widget type."); + } + + // Get the allowed license types for this product variation type. + $bundle_name = $field_definition->getTargetBundle(); + $product_variation_type = \Drupal::entityTypeManager()->getStorage('commerce_product_variation_type')->load($bundle_name); + $license_types = $product_variation_type->getThirdPartySetting('commerce_license', 'license_types') ?: []; + + // If there's no license type restrictions defined, we allow all types. + if ($license_types) { + // Remove plugin IDs from the options array. + $element['target_plugin_id']['#options'] = array_intersect_key( + $element['target_plugin_id']['#options'], + $license_types + ); + } + } +} + +/** + * Implements hook_theme(). + */ +function commerce_license_theme() { + $theme = []; + $theme['commerce_license'] = array( + 'render element' => 'elements', + 'file' => 'commerce_license.page.inc', + 'template' => 'commerce_license', + ); + $theme['commerce_license_content_add_list'] = [ + 'render element' => 'content', + 'variables' => ['content' => NULL], + 'file' => 'commerce_license.page.inc', + ]; + return $theme; +} + +/** +* Implements hook_theme_suggestions_HOOK(). +*/ +function commerce_license_theme_suggestions_commerce_license(array $variables) { + $suggestions = array(); + $entity = $variables['elements']['#commerce_license']; + $sanitized_view_mode = strtr($variables['elements']['#view_mode'], '.', '_'); + + $suggestions[] = 'commerce_license__' . $sanitized_view_mode; + $suggestions[] = 'commerce_license__' . $entity->bundle(); + $suggestions[] = 'commerce_license__' . $entity->bundle() . '__' . $sanitized_view_mode; + $suggestions[] = 'commerce_license__' . $entity->id(); + $suggestions[] = 'commerce_license__' . $entity->id() . '__' . $sanitized_view_mode; + return $suggestions; +} + +/** + * Gets the timezone for the given user. + * + * Helper function equivalent to drupal_get_user_timezone() but accepting + * an arbitrary user object as a parameter, rather than assuming the + * current user. + * + * @param \Drupal\Core\Session\AccountInterface $user + * A user account. + * + * @return string + * The timezone name. + */ +function commerce_licence_get_user_timezone(AccountInterface $user) { + $config = \Drupal::config('system.date'); + if ($user && $config->get('timezone.user.configurable') && $user->isAuthenticated() && $user->getTimezone()) { + return $user->getTimezone(); + } + else { + // Ignore PHP strict notice if time zone has not yet been set in the php.ini + // configuration. + $config_data_default_timezone = $config->get('timezone.default'); + return !empty($config_data_default_timezone) ? $config_data_default_timezone : @date_default_timezone_get(); + } +} diff --git a/docroot/modules/contrib/commerce_license/commerce_license.page.inc b/docroot/modules/contrib/commerce_license/commerce_license.page.inc new file mode 100644 index 000000000..9e65b3c5d --- /dev/null +++ b/docroot/modules/contrib/commerce_license/commerce_license.page.inc @@ -0,0 +1,30 @@ +getTypePlugin()->buildLabel($this); + } + + /** + * {@inheritdoc} + */ + public function preSave(EntityStorageInterface $storage) { + parent::preSave($storage); + + // If the state is being changed to 'active', set the granted and expiration + // timestamps. + // We don't notify the license type plugin here in case the save is + // cancelled by something else. + // (Note that $this->original is not set on new entities.) + if ((isset($this->original) && $this->state->value != $this->original->state->value) || !isset($this->original)) { + if ($this->state->value == 'active') { + $granted_time = \Drupal::service('datetime.time')->getRequestTime(); + + $this->set('granted', $granted_time); + $this->setExpiresTime($this->calculateExpirationTime($granted_time)); + } + } + } + + /** + * {@inheritdoc} + */ + public function postSave(EntityStorageInterface $storage, $update = TRUE) { + parent::postSave($storage, $update); + + // If the state was changed, notify our license type plugin. + // (Note that $this->original is not set on new entities.) + if ((isset($this->original) && $this->state->value != $this->original->state->value) || !isset($this->original)) { + if ($this->state->value == 'active') { + // The state is moved to 'active', or the license was created active: + // the license activates. + $this->getTypePlugin()->grantLicense($this); + } + + if (isset($this->original) && $this->original->state->value == 'active') { + // The state is moved away from 'active': the license is revoked. + $this->getTypePlugin()->revokeLicense($this); + } + } + } + + /** + * {@inheritdoc} + */ + public function delete() { + // Revoke the license if it is active. + if ($this->state->value == 'active') { + $this->getTypePlugin()->revokeLicense($this); + } + + parent::delete(); + } + + /** + * {@inheritdoc} + */ + public function getTypePlugin() { + /** @var \Drupal\commerce_license\LicenseTypeManager $license_type_manager */ + $license_type_manager = \Drupal::service('plugin.manager.commerce_license_type'); + return $license_type_manager->createInstance($this->bundle()); + } + + /** + * {@inheritdoc} + */ + public function setValuesFromPlugin(LicenseTypeInterface $license_plugin) { + $license_plugin->setConfigurationValuesOnLicense($this); + } + + /** + * {@inheritdoc} + */ + public function getExpiresTime() { + return $this->get('expires')->value; + } + + /** + * {@inheritdoc} + */ + public function setExpiresTime($timestamp) { + $this->set('expires', $timestamp); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getCreatedTime() { + return $this->get('created')->value; + } + + /** + * {@inheritdoc} + */ + public function setCreatedTime($timestamp) { + $this->set('created', $timestamp); + return $this; + } + + /** + * Calculate the expiration time for this license from a start time. + * + * @param int $start + * The timestamp to calculate the duration from. + * + * @return int + * The expiry timestamp, or the value + * \Drupal\recurring_period\Plugin\RecurringPeriod\RecurringPeriodInterface::UNLIMITED + * if the license does not expire. + */ + protected function calculateExpirationTime($start) { + /** @var \Drupal\recurring_period\Plugin\RecurringPeriod\RecurringPeriodInterface $expiration_type_plugin */ + $expiration_type_plugin = $this->get('expiration_type')->first()->getTargetInstance(); + + // The recurring period plugin needs DateTimeImmutable objects in order + // to handle timezones properly. So we convert the timestamp to a datetime + // using an appropriate timezone for the user, and then convert the + // expiration back into a UTC timestamp. + $start_date = (new \DateTimeImmutable('@' . $start)) + ->setTimezone(new \DateTimeZone(commerce_licence_get_user_timezone($this->getOwner()))); + $expiration_date = $expiration_type_plugin->calculateDate($start_date); + + // The returned date is either \DateTimeImmutable or + // \Drupal\recurring_period\Plugin\RecurringPeriod\RecurringPeriodInterface::UNLIMITED. + if (is_object($expiration_date)) { + return $expiration_date->format('U'); + } + else { + return $expiration_date; + } + } + + /** + * {@inheritdoc} + */ + public function getOwner() { + return $this->get('uid')->entity; + } + + /** + * {@inheritdoc} + */ + public function getOwnerId() { + return $this->get('uid')->target_id; + } + + /** + * {@inheritdoc} + */ + public function setOwnerId($uid) { + $this->set('uid', $uid); + return $this; + } + + + /** + * {@inheritdoc} + */ + public function setOwner(UserInterface $account) { + $this->set('uid', $account->id()); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getState() { + return $this->get('state')->first(); + } + + /** + * {@inheritdoc} + */ + public static function getWorkflowId(LicenseInterface $license) { + return $license->getTypePlugin()->getWorkflowId(); + } + + /** + * {@inheritdoc} + */ + public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { + $fields = parent::baseFieldDefinitions($entity_type); + + $fields['uid'] = BaseFieldDefinition::create('entity_reference') + ->setLabel(t('Owner')) + ->setDescription(t('The user ID of the license owner.')) + ->setSetting('target_type', 'user') + ->setSetting('handler', 'default') + ->setDefaultValueCallback('Drupal\commerce_license\Entity\License::getCurrentUserId') + ->setDisplayOptions('view', array( + 'label' => 'hidden', + 'type' => 'author', + 'weight' => 0, + )) + ->setDisplayConfigurable('form', TRUE) + ->setDisplayConfigurable('view', TRUE); + + $fields['state'] = BaseFieldDefinition::create('state') + ->setLabel(t('State')) + ->setDescription(t('The license state.')) + ->setRequired(TRUE) + ->setSetting('max_length', 255) + ->setDisplayOptions('view', [ + 'label' => 'hidden', + 'type' => 'state_transition_form', + 'weight' => 10, + ]) + ->setDisplayConfigurable('form', TRUE) + ->setDisplayConfigurable('view', TRUE) + ->setSetting('workflow_callback', ['\Drupal\commerce_license\Entity\License', 'getWorkflowId']); + + $fields['queues'] = BaseFieldDefinition::create('string') + ->setLabel(t('Queues')) + ->setDescription(t('The queues in which this license is currently placed.')) + ->setCardinality(BaseFieldDefinition::CARDINALITY_UNLIMITED) + ->setDisplayConfigurable('form', FALSE) + ->setDisplayConfigurable('view', FALSE); + + $fields['product_variation'] = BaseFieldDefinition::create('entity_reference') + ->setLabel(t('Licensed product variation')) + ->setDescription(t('The licensed product variation.')) + ->setRequired(TRUE) + ->setSetting('target_type', 'commerce_product_variation') + ->setDisplayOptions('form', [ + 'type' => 'entity_reference_autocomplete', + 'weight' => -1, + 'settings' => [ + 'match_operator' => 'CONTAINS', + 'size' => '60', + 'placeholder' => '', + ], + ]) + ->setDisplayConfigurable('form', TRUE) + ->setDisplayConfigurable('view', TRUE); + + $fields['expiration_type'] = BaseFieldDefinition::create('commerce_plugin_item:recurring_period') + ->setLabel(t('Expiration type')) + ->setDescription(t("The configuration for calculating the license's expiry.")) + ->setCardinality(1) + ->setRequired(FALSE) + ->setDisplayOptions('form', [ + 'type' => 'commerce_plugin_select', + 'weight' => 21, + ]) + ->setDisplayConfigurable('view', TRUE); + + $fields['created'] = BaseFieldDefinition::create('created') + ->setLabel(t('Created')) + ->setDescription(t('The time that the license was created.')); + + $fields['granted'] = BaseFieldDefinition::create('timestamp') + ->setLabel(t('Granted')) + ->setDescription(t('The time that the license was granted or activated.')) + ->setDisplayOptions('view', [ + 'label' => 'hidden', + 'type' => 'timestamp', + 'weight' => 1, + 'settings' => [ + 'date_format' => 'custom', + 'custom_date_format' => 'n/Y', + ], + ]) + ->setDisplayConfigurable('view', TRUE) + ->setDefaultValue(0); + + $fields['changed'] = BaseFieldDefinition::create('changed') + ->setLabel(t('Changed')) + ->setDescription(t('The time that the license was last modified.')); + + $fields['expires'] = BaseFieldDefinition::create('timestamp') + ->setLabel(t('Expires')) + ->setDescription(t('The time that the license will expire, if any.')) + ->setDisplayOptions('view', [ + 'label' => 'hidden', + 'type' => 'timestamp', + 'weight' => 1, + 'settings' => [ + 'date_format' => 'custom', + 'custom_date_format' => 'n/Y', + ], + ]) + ->setDisplayConfigurable('view', TRUE) + ->setDefaultValue(0); + + return $fields; + } + + /** + * Default value callback for 'uid' base field definition. + * + * @see ::baseFieldDefinitions() + * + * @return array + * An array of default values. + */ + public static function getCurrentUserId() { + return [\Drupal::currentUser()->id()]; + } + +} diff --git a/docroot/modules/contrib/commerce_license/src/Entity/LicenseInterface.php b/docroot/modules/contrib/commerce_license/src/Entity/LicenseInterface.php new file mode 100644 index 000000000..aedd57b11 --- /dev/null +++ b/docroot/modules/contrib/commerce_license/src/Entity/LicenseInterface.php @@ -0,0 +1,94 @@ +licenseStorage = $entity_type_manager->getStorage('commerce_license'); + $this->entityTypeManager = $entity_type_manager; + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + $events = [ + // Events defined by state_machine, derived from the workflow defined in + // commerce_order.workflows.yml. + // See Drupal\state_machine\Plugin\Field\FieldType\StateItem::dispatchTransitionEvent() + + // TODO: Revisit these transitions and check they are correct. + + // Events for reaching the 'fulfillment' state. This depends on whether + // the workflow in use has validation or not. + 'commerce_order.place.post_transition' => ['onCartOrderFulfillment', -100], + 'commerce_order.validate.post_transition' => ['onCartOrderFulfillment', -100], + // Event for reaching the 'canceled' state. + 'commerce_order.cancel.post_transition' => ['onCartOrderCancel', -100], + ]; + return $events; + } + + /** + * Reacts to an order reaching fulfillment state. + * + * @param \Drupal\state_machine\Event\WorkflowTransitionEvent $event + * The event we subscribed to. + */ + public function onCartOrderFulfillment(WorkflowTransitionEvent $event) { + // Only act if we are reaching the 'fulfillment' state. Different workflows + // use the same transition names to reach different states. + if ($event->getToState()->getId() != 'fulfillment') { + return; + } + + /** @var \Drupal\commerce_order\Entity\OrderInterface $order */ + $order = $event->getEntity(); + + $license_order_items = $this->getOrderItemsWithLicensedProducts($order); + + foreach ($license_order_items as $order_item) { + // Only create a new license if the order item doesn't already have one: + // this allows for orders to be created programmatically with a configured + // license. + if (empty($order_item->license->entity)) { + // Create a new license. + $license = $this->licenseStorage->createFromOrderItem($order_item); + + $license->save(); + + // Set the license field on the order item so we have a reference + // and can get hold of it in later events. + $order_item->license = $license->id(); + $order_item->save(); + } + else { + // Get the existing license the order item refers to. + $license = $order_item->license->entity; + } + + // Attempt to activate and confirm the license. + // TODO: This needs to be expanded for synchronizable licenses. + // TODO: how does a license type plugin indicate that it's not able to + // activate? And how do we notify the order at this point? + $transition = $license->getState()->getWorkflow()->getTransition('activate'); + $license->getState()->applyTransition($transition); + $license->save(); + + $transition = $license->getState()->getWorkflow()->getTransition('confirm'); + $license->getState()->applyTransition($transition); + $license->save(); + } + } + + /** + * Reacts to an order being cancelled. + * + * @param \Drupal\state_machine\Event\WorkflowTransitionEvent $event + * The event we subscribed to. + */ + public function onCartOrderCancel(WorkflowTransitionEvent $event) { + /** @var \Drupal\commerce_order\Entity\OrderInterface $order */ + $order = $event->getEntity(); + + $license_order_items = $this->getOrderItemsWithLicensedProducts($order); + + foreach ($license_order_items as $order_item) { + // Get the license from the order item. + $license = $order_item->license->entity; + + // Cancel the license. + $transition = $license->getState()->getWorkflow()->getTransition('cancel'); + $license->getState()->applyTransition($transition); + $license->save(); + } + } + + /** + * Returns the order items from an order which are for licensed products. + * + * @param \Drupal\commerce_order\Entity\OrderInterface $order + * The order entity. + * + * @return \Drupal\commerce_order\Entity\OrderItemInterface[] + * An array of the order items whose purchased products are for licenses. + */ + protected function getOrderItemsWithLicensedProducts(OrderInterface $order) { + $return_items = []; + + foreach ($order->getItems() as $order_item) { + // Skip order items that do not have a license reference field. + // We check order items rather than the purchased entity to allow products + // with licenses to be purchased without the checkout flow triggering + // our synchronization. This is for cases such as recurring orders, where + // the license entity should not be put through the normal workflow. + // Checking the order item's bundle for our entity trait is expensive, as + // it requires loading the bundle entity to call hasTrait() on it. + // For now, just check whether the order item has our trait's field on it. + // @see https://www.drupal.org/node/2894805 + if (!$order_item->hasField('license')) { + continue; + } + + $return_items[] = $order_item; + } + + return $return_items; + } + +} diff --git a/docroot/modules/contrib/commerce_license/src/EventSubscriber/ReferenceablePluginTypesSubscriber.php b/docroot/modules/contrib/commerce_license/src/EventSubscriber/ReferenceablePluginTypesSubscriber.php new file mode 100644 index 000000000..8a9375294 --- /dev/null +++ b/docroot/modules/contrib/commerce_license/src/EventSubscriber/ReferenceablePluginTypesSubscriber.php @@ -0,0 +1,40 @@ + 'onPluginTypes', + ]; + } + + /** + * Registers our plugin types as referenceable. + * + * @param \Drupal\commerce\Event\ReferenceablePluginTypesEvent $event + * The event. + */ + public function onPluginTypes(ReferenceablePluginTypesEvent $event) { + $plugin_types = $event->getPluginTypes(); + $plugin_types['commerce_license_type'] = $this->t('License type'); + $event->setPluginTypes($plugin_types); + } + +} diff --git a/docroot/modules/contrib/commerce_license/src/ExistingRights/ExistingRightsResult.php b/docroot/modules/contrib/commerce_license/src/ExistingRights/ExistingRightsResult.php new file mode 100644 index 000000000..afa6f7588 --- /dev/null +++ b/docroot/modules/contrib/commerce_license/src/ExistingRights/ExistingRightsResult.php @@ -0,0 +1,142 @@ +status = $status; + $this->messageOwner = $message_owner; + $this->messageOther = $message_other; + } + + /** + * Creates an ExistingRightsResult from a condition. + * + * @param bool $condition + * The condition to evaluate. + * @param \Drupal\Core\StringTranslation\TranslatableMarkup $message_owner + * @param \Drupal\Core\StringTranslation\TranslatableMarkup $message_other + * + * @return \Drupal\commerce_license\ExistingRights\ExistingRightsResult + * The result object. + */ + public static function rightsExistIf($condition, TranslatableMarkup $message_owner, TranslatableMarkup $message_other) { + if ($condition) { + return static::rightsExist( + $message_owner, + $message_other + ); + } + else { + return static::rightsDoNotExist(); + } + } + + /** + * Creates an ExistingRightsResult stating that rights exist. + * + * @param \Drupal\Core\StringTranslation\TranslatableMarkup $message_owner + * @param \Drupal\Core\StringTranslation\TranslatableMarkup $message_other + * + * @return \Drupal\commerce_license\ExistingRights\ExistingRightsResult + * The result object. + */ + public static function rightsExist(TranslatableMarkup $message_owner, TranslatableMarkup $message_other) { + return new static( + TRUE, + $message_owner, + $message_other + ); + } + + /** + * Creates an ExistingRightsResult stating that rights do not exist. + * + * @param \Drupal\Core\StringTranslation\TranslatableMarkup $message_owner + * @param \Drupal\Core\StringTranslation\TranslatableMarkup $message_other + * + * @return \Drupal\commerce_license\ExistingRights\ExistingRightsResult + * The result object. + */ + public static function rightsDoNotExist() { + return new static(FALSE); + } + + /** + * Gets the status of the result. + * + * @return bool + * Boolean indicating whether the checked user has existing rights. + */ + public function hasExistingRights() { + return $this->status; + } + + /** + * Gets the message intended for the user that was checked. + * + * @return \Drupal\Core\StringTranslation\TranslatableMarkup + * The translated message. + */ + public function getOwnerUserMessage() { + return $this->messageOwner; + } + + /** + * Gets the message intended for a user other than the one that was checked. + * + * @return \Drupal\Core\StringTranslation\TranslatableMarkup + * The translated message. + */ + public function getOtherUserMessage() { + return $this->messageOther; + } + +} diff --git a/docroot/modules/contrib/commerce_license/src/Form/LicenseCheckoutForm.php b/docroot/modules/contrib/commerce_license/src/Form/LicenseCheckoutForm.php new file mode 100644 index 000000000..5f36f8c4e --- /dev/null +++ b/docroot/modules/contrib/commerce_license/src/Form/LicenseCheckoutForm.php @@ -0,0 +1,73 @@ +entity->isNew()) { + $form['new_revision'] = array( + '#type' => 'checkbox', + '#title' => $this->t('Create new revision'), + '#default_value' => FALSE, + '#weight' => 10, + ); + } + + $this->entity->getPlugin()->buildCheckoutForm($form, $form_state); + + $entity = $this->entity; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function save(array $form, FormStateInterface $form_state) { + $entity = &$this->entity; + + // Save as a new revision if requested to do so. + if (!$form_state->isValueEmpty('new_revision') && $form_state->getValue('new_revision') != FALSE) { + $entity->setNewRevision(); + + // If a new revision is created, save the current user as revision author. + $entity->setRevisionCreationTime(REQUEST_TIME); + $entity->setRevisionUserId(\Drupal::currentUser()->id()); + } + else { + $entity->setNewRevision(FALSE); + } + + $status = parent::save($form, $form_state); + + switch ($status) { + case SAVED_NEW: + drupal_set_message($this->t('Created the %label License.', [ + '%label' => $entity->label(), + ])); + break; + + default: + drupal_set_message($this->t('Saved the %label License.', [ + '%label' => $entity->label(), + ])); + } + $form_state->setRedirect('entity.commerce_license.canonical', ['commerce_license' => $entity->id()]); + } + +} diff --git a/docroot/modules/contrib/commerce_license/src/Form/LicenseCreateForm.php b/docroot/modules/contrib/commerce_license/src/Form/LicenseCreateForm.php new file mode 100755 index 000000000..31331ca6f --- /dev/null +++ b/docroot/modules/contrib/commerce_license/src/Form/LicenseCreateForm.php @@ -0,0 +1,372 @@ +pluginManager = $plugin_manager; + $this->moduleHandler = $module_handler; + $this->entityTypeManager = $entity_type_manager; + $this->entityTypeBundleInfo = $entity_type_bundle_info; + $this->time = $time; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('plugin.manager.commerce_license_type'), + $container->get('module_handler'), + $container->get('entity_type.manager'), + $container->get('entity_type.bundle.info'), + $container->get('datetime.time') + ); + } + + /** + * Initialize the form state and the entity before the first form build. + */ + protected function init(FormStateInterface $form_state) { + // Flag that this form has been initialized. + $form_state->set('entity_form_initialized', TRUE); + // Prepare the entity to be presented in the entity form. + $this->prepareEntity(); + // Invoke the prepare form hooks. + $this->prepareInvokeAll('entity_prepare_form', $form_state); + $this->prepareInvokeAll($this->entity->getEntityTypeId() . '_prepare_form', $form_state); + } + + + /** + * Form element #after_build callback: Updates the entity with submitted data. + * + * Updates the internal $this->entity object with submitted values when the + * form is being rebuilt (e.g. submitted via AJAX), so that subsequent + * processing (e.g. AJAX callbacks) can rely on it. + */ + public function afterBuild(array $element, FormStateInterface $form_state) { + // Rebuild the entity if #after_build is being called as part of a form + // rebuild, i.e. if we are processing input. + if ($form_state->isProcessingInput()) { + $this->entity = $this->buildEntity($element, $form_state); + } + return $element; + } + + /** + * {@inheritdoc} + */ + public function getBaseFormId() { + $base_form_id = 'commerce_license_form'; + return $base_form_id; + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'commerce_license_create_form'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $step = $form_state->get('step'); + if (!$step) { + $step = 'license_type'; + // Skip the license type selection if there's only 1 type. + $plugins = array_column($this->pluginManager->getDefinitions(), 'label', 'id'); + if (count($plugins) === 1) { + /** @var \Drupal\commerce_license\Plugin\Commerce\LicenseType\LicenseTypeInterface $license_type */ + $license_type = reset($plugins); + error_log($license_type); + $form_state->set('license_type', $license_type); + $step = 'license'; + } + $form_state->set('step', $step); + } + + + if ($step == 'license_type') { + $form = $this->buildLicenseTypeForm($form, $form_state); + } + elseif ($step == 'license') { + // DEPRECATED??? + // Create our license now. We can't do it in RouteMatch because reasons. + $values = []; + // If the entity has bundles, fetch it from the route match. + $values['type'] = $form_state->get('license_type'); + + $entity = $this->entityTypeManager->getStorage('commerce_license') + ->create($values); + $this->setEntity($entity); + + $form = $this->buildLicenseForm($form, $form_state); + } + + // Add the submit button. + $actions['submit'] = [ + '#type' => 'submit', + '#value' => $this->t('Save'), + '#button_type' => 'primary', + '#submit' => ['::submitForm'], + ]; + $actions['#type'] = 'actions'; + if (!empty($actions)) { + $form['actions'] = $actions; + } + + return $form; + } + + public function buildLicenseTypeForm(array $form, FormStateInterface $form_state) { + $plugins = array_column($this->pluginManager->getDefinitions(), 'label', 'id'); + asort($plugins); + + $plugin_ids = array_keys($plugins); + $first_plugin = reset($plugin_ids); + + // The form state will have a plugin value if #ajax was used. + $plugin = $form_state->getValue('license_type', $first_plugin); + + $form['license_type'] = [ + '#type' => 'select', + '#title' => $this->t('License type'), + '#options' => $plugins, + '#default_value' => $plugin, + '#required' => TRUE, + ]; + + return $form; + } + + public function buildLicenseForm(array $form, FormStateInterface $form_state) { + // During the initial form build, add this form object to the form state and + // allow for initial preparation before form building and processing. + if (!$form_state->has('entity_form_initialized')) { + $this->init($form_state); + } + + // Ensure that edit forms have the correct cacheability metadata so they can + // be cached. + if (!$this->entity->isNew()) { + \Drupal::service('renderer') + ->addCacheableDependency($form, $this->entity); + } + + // Callbacks not added by using the entity methods. + $form['#process'][] = '::processForm'; + + $this->getFormDisplay($form_state)->buildForm($this->entity, $form, $form_state); + // Allow modules to act before and after form language is updated. + $form['#entity_builders']['update_form_langcode'] = '::updateFormLangcode'; + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + $step = $form_state->get('step'); + // No need to validate the plugin/type selection step more than the select + // list element will do for us. + if ($step == 'license') { + // @TODO: Entity form validation here. + parent::validateForm($form, $form_state); + } + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $step = $form_state->get('step'); + if ($step == 'license_type') { + // Set the license type and rebuild the form. + $form_state->set('license_type', $form_state->getValue('license_type')); + $form_state->set('step', 'license'); + $form_state->setRebuild(TRUE); + } + elseif ($step == 'license') { + // @TODO: Entity form submission here. + parent::submitForm($form, $form_state); + } + } + + /** + * {@inheritdoc} + */ + public function buildEntity(array $form, FormStateInterface $form_state) { + $entity = clone $this->entity; + $this->copyFormValuesToEntity($entity, $form, $form_state); + + // Invoke all specified builders for copying form values to entity + // properties. + if (isset($form['#entity_builders'])) { + foreach ($form['#entity_builders'] as $function) { + call_user_func_array($form_state->prepareCallback($function), [ + $entity->getEntityTypeId(), + $entity, + &$form, + &$form_state + ]); + } + } + + // Mark the entity as requiring validation. + $entity->setValidationRequired(!$form_state->getTemporaryValue('entity_validated')); + + return $entity; + } + + /** + * Copies top-level form values to entity properties + * + * This should not change existing entity properties that are not being edited + * by this form. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity the current form should operate upon. + * @param array $form + * A nested array of form elements comprising the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ + protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) { + $values = $form_state->getValues(); + + if ($this->entity instanceof EntityWithPluginCollectionInterface) { + // Do not manually update values represented by plugin collections. + $values = array_diff_key($values, $this->entity->getPluginCollections()); + } + + // @todo: This relies on a method that only exists for config and content + // entities, in a different way. Consider moving this logic to a config + // entity specific implementation. + foreach ($values as $key => $value) { + $entity->set($key, $value); + } + } + + /** + * {@inheritdoc} + */ + public function getEntity() { + return $this->entity; + } + + /** + * {@inheritdoc} + */ + public function setEntity(EntityInterface $entity) { + $this->entity = $entity; + return $this; + } + + /** + * Prepares the entity object before the form is built first. + */ + protected function prepareEntity() { + } + + /** + * Invokes the specified prepare hook variant. + * + * @param string $hook + * The hook variant name. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ + protected function prepareInvokeAll($hook, FormStateInterface $form_state) { + $implementations = $this->moduleHandler->getImplementations($hook); + foreach ($implementations as $module) { + $function = $module . '_' . $hook; + if (function_exists($function)) { + // Ensure we pass an updated translation object and form display at + // each invocation, since they depend on form state which is alterable. + $args = [$this->entity, $this->operation, &$form_state]; + call_user_func_array($function, $args); + } + } + } + + /** + * {@inheritdoc} + */ + public function getFormDisplay(FormStateInterface $form_state) { + return $form_state->get('form_display'); + } +} diff --git a/docroot/modules/contrib/commerce_license/src/Form/LicenseEditForm.php b/docroot/modules/contrib/commerce_license/src/Form/LicenseEditForm.php new file mode 100644 index 000000000..5f61d3394 --- /dev/null +++ b/docroot/modules/contrib/commerce_license/src/Form/LicenseEditForm.php @@ -0,0 +1,28 @@ +t('Saved the %label License.', [ + '%label' => $entity->label(), + ])); + + $form_state->setRedirect('entity.commerce_license.canonical', ['commerce_license' => $entity->id()]); + } + +} diff --git a/docroot/modules/contrib/commerce_license/src/Form/LicenseForm.php b/docroot/modules/contrib/commerce_license/src/Form/LicenseForm.php new file mode 100755 index 000000000..a84c2b5c3 --- /dev/null +++ b/docroot/modules/contrib/commerce_license/src/Form/LicenseForm.php @@ -0,0 +1,37 @@ +t('Created the %label License.', [ + '%label' => $entity->label(), + ])); + + break; + + default: + drupal_set_message($this->t('Saved the %label License.', [ + '%label' => $entity->label(), + ])); + } + $form_state->setRedirect('entity.commerce_license.canonical', ['commerce_license' => $entity->id()]); + } + +} diff --git a/docroot/modules/contrib/commerce_license/src/Form/LicenseSettingsForm.php b/docroot/modules/contrib/commerce_license/src/Form/LicenseSettingsForm.php new file mode 100644 index 000000000..f6ae6ae14 --- /dev/null +++ b/docroot/modules/contrib/commerce_license/src/Form/LicenseSettingsForm.php @@ -0,0 +1,55 @@ +entity = $entity; + } + + /** + * Alters the form. + * + * Helper for hook_form_alter(); same parameters. + */ + public function formAlter(&$form, FormStateInterface $form_state, $form_id) { + $form_object = $form_state->getFormObject(); + if (!($form_object instanceof EntityFormInterface)) { + // We're only interested in forms for an entity. + return; + } + + /** @var \Drupal\Core\Entity\EntityFormInterface $form_object */ + $entity = $form_object->getEntity(); + $entity_type_id = $entity->getEntityTypeId(); + if (!($entity instanceof EntityOwnerInterface) && $entity_type_id != 'user') { + // We only act on entities that have an owner, and user entities. + return; + } + + if ($entity_type_id == 'commerce_license') { + // Don't act on licenses themselves. + return; + } + + if ($entity->isNew()) { + // Don't act on a new entity, as it can't be the target of a license. + return; + } + + // Get the ID of owner of this entity, or of the user itself. + $user_id = ($entity_type_id == 'user') ? $entity->id() : $entity->getOwnerId(); + if (is_null($user_id)) { + // Bail if we didn't manage to get a user ID. Shouldn't get this far but + // some forms might misbehave. + return; + } + + // Get all active licenses owned by this user. + // Note: this assumes that users have relatively few licenses each. If + // scalability becomes an issue, consider instead first asking each license + // type plugin for which entity types it might be interested in, and then + // query only for those license types if there is a match with the form's + // entity. + $licenses = \Drupal::service('entity_type.manager')->getStorage('commerce_license')->loadByProperties([ + 'uid' => $user_id, + 'state' => 'active', + ]); + + // Let each suitable license's plugin alter the form for the license. + foreach ($licenses as $license) { + $license_type_plugin = $license->getTypePlugin(); + if ($license_type_plugin instanceof GrantedEntityLockingInterface) { + $license_type_plugin->alterEntityOwnerForm($form, $form_state, $form_id, $license, $entity); + } + } + + } + +} diff --git a/docroot/modules/contrib/commerce_license/src/LicenseAvailabilityCheckerExistingRights.php b/docroot/modules/contrib/commerce_license/src/LicenseAvailabilityCheckerExistingRights.php new file mode 100644 index 000000000..61d4b8c75 --- /dev/null +++ b/docroot/modules/contrib/commerce_license/src/LicenseAvailabilityCheckerExistingRights.php @@ -0,0 +1,93 @@ +currentUser = $current_user; + } + + /** + * {@inheritdoc} + */ + public function applies(PurchasableEntityInterface $entity) { + // This applies only to product variations which have our license trait on + // them. Check for the field the trait provides, as checking for the trait + // on the bundle is expensive -- see https://www.drupal.org/node/2894805. + if (!$entity->hasField('license_type')) { + return FALSE; + } + + // This applies only to license types that implement the interface. + $license_type_plugin = $entity->license_type->first()->getTargetInstance(); + if ($license_type_plugin instanceof ExistingRightsFromConfigurationCheckingInterface) { + return TRUE; + } + + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function check(PurchasableEntityInterface $entity, $quantity, Context $context) { + // Hand over to the license type plugin configured in the product variation, + // to let it determine whether the user already has what the license would + // grant. + $user = $context->getCustomer(); + $license_type_plugin = $entity->license_type->first()->getTargetInstance(); + + $existing_rights_result = $license_type_plugin->checkUserHasExistingRights($user); + + if ($existing_rights_result->hasExistingRights()) { + // Show a message that includes the reason from the rights check. + if ($user->id() == $this->currentUser->id()) { + $rights_check_message = $existing_rights_result->getOwnerUserMessage(); + } + else { + $rights_check_message = $existing_rights_result->getOtherUserMessage(); + } + $message = $rights_check_message . ' ' . t("You may not purchase the @product-label product.", [ + '@product-label' => $entity->label(), + ]); + drupal_set_message($message); + + return FALSE; + } + + // No opinion: return NULL. + } + +} diff --git a/docroot/modules/contrib/commerce_license/src/LicenseListBuilder.php b/docroot/modules/contrib/commerce_license/src/LicenseListBuilder.php new file mode 100644 index 000000000..a728c7c61 --- /dev/null +++ b/docroot/modules/contrib/commerce_license/src/LicenseListBuilder.php @@ -0,0 +1,70 @@ +licenseTypes = $license_type_manager->getDefinitions(); + } + + /** + * {@inheritdoc} + */ + public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { + return new static( + $entity_type, + $container->get('entity.manager')->getStorage($entity_type->id()), + $container->get('plugin.manager.commerce_license_type') + ); + } + + /** + * {@inheritdoc} + */ + public function buildHeader() { + $header['id'] = $this->t('License ID'); + $header['type'] = $this->t('Type'); + return $header + parent::buildHeader(); + } + + /** + * {@inheritdoc} + */ + public function buildRow(EntityInterface $entity) { + /* @var $entity \Drupal\commerce_license\Entity\License */ + $row['id'] = $entity->id(); + $row['type'] = $this->licenseTypes[$entity->bundle()]['label']; + return $row + parent::buildRow($entity); + } + +} diff --git a/docroot/modules/contrib/commerce_license/src/LicenseOrderProcessorMultiples.php b/docroot/modules/contrib/commerce_license/src/LicenseOrderProcessorMultiples.php new file mode 100644 index 000000000..3569e55ca --- /dev/null +++ b/docroot/modules/contrib/commerce_license/src/LicenseOrderProcessorMultiples.php @@ -0,0 +1,49 @@ +getItems() as $order_item) { + // Skip order items that do not have a license reference field. + if (!$order_item->hasField('license')) { + continue; + } + + // TODO: Allow license type plugins to respond here, as for types that + // collect user data in the checkout form, the same product variation can + // result in different licenses. + $quantity = $order_item->getQuantity(); + if ($quantity > 1) { + // Force the quantity back to 1. + $order_item->setQuantity(1); + + $purchased_entity = $order_item->getPurchasedEntity(); + // Note that this message shows both when attempting to increase the + // quantity of a license product already in the cart, and when + // attempting to put more than 1 of a license product into the cart. + // In the latter case, the message isn't as clear as it could be, but + // site builders should be hiding the quantity field from the add to + // cart form for license products, so this is moot. + drupal_set_message(t("You may only have one of @product-label in your cart.", [ + '@product-label' => $purchased_entity->label(), + ])); + } + } + + return; + } + +} diff --git a/docroot/modules/contrib/commerce_license/src/LicensePermissionProvider.php b/docroot/modules/contrib/commerce_license/src/LicensePermissionProvider.php new file mode 100644 index 000000000..4c6950924 --- /dev/null +++ b/docroot/modules/contrib/commerce_license/src/LicensePermissionProvider.php @@ -0,0 +1,34 @@ +id(); + + // Mark the 'overview' permission as restricted. + $permissions["access {$entity_type_id} overview"]['restrict access'] = TRUE; + + // Add a description to the 'create' to make it clear that it only covers + // admin creation, not creation via product purchase. + $permissions["create {$entity_type_id}"]['description'] = $this->t('Create licenses in administrative mode, bypassing the purchase of a product.'); + + return $permissions; + } + +} diff --git a/docroot/modules/contrib/commerce_license/src/LicenseStorage.php b/docroot/modules/contrib/commerce_license/src/LicenseStorage.php new file mode 100644 index 000000000..d9a6e69f2 --- /dev/null +++ b/docroot/modules/contrib/commerce_license/src/LicenseStorage.php @@ -0,0 +1,61 @@ +getPurchasedEntity(); + + // Take the license owner from the order, for the case when orders are + // created for another user. + $uid = $order_item->getOrder()->getCustomerId(); + + $license = $this->createFromProductVariation($purchased_entity, $uid); + + return $license; + } + + /** + * {@inheritdoc} + */ + public function createFromProductVariation(ProductVariationInterface $variation, int $uid) { + // TODO: throw an exception if the variation doesn't have this field. + $license_type_plugin = $variation->get('license_type')->first()->getTargetInstance(); + + $license = $this->create([ + 'type' => $license_type_plugin->getPluginId(), + 'state' => 'new', + 'product_variation' => $variation->id(), + 'uid' => $uid, + // Take the expiration type configuration from the product variation + // expiration field. + 'expiration_type' => $variation->license_expiration, + ]); + + // Set the license's plugin-specific configuration from the + // product variation's license_type field plugin instance. + $license->setValuesFromPlugin($license_type_plugin); + + return $license; + } + +} diff --git a/docroot/modules/contrib/commerce_license/src/LicenseStorageInterface.php b/docroot/modules/contrib/commerce_license/src/LicenseStorageInterface.php new file mode 100644 index 000000000..2da5a24b2 --- /dev/null +++ b/docroot/modules/contrib/commerce_license/src/LicenseStorageInterface.php @@ -0,0 +1,46 @@ +alterInfo('commerce_license_type_info'); + $this->setCacheBackend($cache_backend, 'commerce_license_type_plugins'); + } + + /** + * {@inheritdoc} + */ + public function processDefinition(&$definition, $plugin_id) { + parent::processDefinition($definition, $plugin_id); + + foreach (['id', 'label'] as $required_property) { + if (empty($definition[$required_property])) { + throw new PluginException(sprintf('The license type %s must define the %s property.', $plugin_id, $required_property)); + } + } + } + +} diff --git a/docroot/modules/contrib/commerce_license/src/LicenseViewsData.php b/docroot/modules/contrib/commerce_license/src/LicenseViewsData.php new file mode 100644 index 000000000..3cb500e3d --- /dev/null +++ b/docroot/modules/contrib/commerce_license/src/LicenseViewsData.php @@ -0,0 +1,36 @@ +entityType->getBaseTable() ?: $this->entityType->id(); + + $data[$base_table]['label'] = [ + 'title' => $this->t('Label'), + 'help' => $this->t('The label of the license.'), + 'real field' => 'license_id', + 'field' => [ + 'id' => 'commerce_license__entity_label', + ], + ]; + + // Workaround for core shortcoming. + // TODO: remove once https://www.drupal.org/node/2337515 is fixed. + $data[$base_table]['state']['filter']['id'] = 'state_machine_state'; + + return $data; + } + +} diff --git a/docroot/modules/contrib/commerce_license/src/Plugin/Commerce/EntityTrait/OrderItemLicensed.php b/docroot/modules/contrib/commerce_license/src/Plugin/Commerce/EntityTrait/OrderItemLicensed.php new file mode 100644 index 000000000..5516fbe5a --- /dev/null +++ b/docroot/modules/contrib/commerce_license/src/Plugin/Commerce/EntityTrait/OrderItemLicensed.php @@ -0,0 +1,53 @@ +setLabel(t('License')) + ->setDescription(t('The license purchased with this order item.')) + ->setSetting('target_type', 'commerce_license') + ->setSetting('handler', 'default') + ->setCardinality(1) + // Won't be set when the order item is initially created, so can't be + // required. + ->setRequired(FALSE) + ->setDisplayOptions('form', [ + 'type' => 'hidden', + 'weight' => 0, + ]) + ->setDisplayConfigurable('form', FALSE) + ->setDisplayConfigurable('view', FALSE); + return $fields; + } + +} diff --git a/docroot/modules/contrib/commerce_license/src/Plugin/Commerce/EntityTrait/ProductVariationLicensed.php b/docroot/modules/contrib/commerce_license/src/Plugin/Commerce/EntityTrait/ProductVariationLicensed.php new file mode 100644 index 000000000..e6ecf6c30 --- /dev/null +++ b/docroot/modules/contrib/commerce_license/src/Plugin/Commerce/EntityTrait/ProductVariationLicensed.php @@ -0,0 +1,49 @@ +setLabel(t('License Type')) + ->setCardinality(1) + ->setRequired(TRUE) + ->setDisplayOptions('form', [ + 'type' => 'commerce_plugin_select', + 'weight' => 20, + ]); + $fields['license_expiration'] = BundleFieldDefinition::create('commerce_plugin_item:recurring_period') + ->setLabel(t('License Expiration')) + ->setCardinality(1) + ->setRequired(TRUE) + ->setDisplayOptions('form', [ + 'type' => 'commerce_plugin_select', + 'weight' => 21, + ]); + return $fields; + } + +} diff --git a/docroot/modules/contrib/commerce_license/src/Plugin/Commerce/LicenseType/ExistingRightsFromConfigurationCheckingInterface.php b/docroot/modules/contrib/commerce_license/src/Plugin/Commerce/LicenseType/ExistingRightsFromConfigurationCheckingInterface.php new file mode 100644 index 000000000..6c0b9ed10 --- /dev/null +++ b/docroot/modules/contrib/commerce_license/src/Plugin/Commerce/LicenseType/ExistingRightsFromConfigurationCheckingInterface.php @@ -0,0 +1,37 @@ +setConfiguration($configuration); + } + + /** + * {@inheritdoc} + */ + public function getLabel() { + return $this->pluginDefinition['label']; + } + + /** + * {@inheritdoc} + */ + public function getWorkflowId() { + return 'license_default'; + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + return []; + } + + /** + * {@inheritdoc} + */ + public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { + } + + /** + * {@inheritdoc} + */ + public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { + // This assumes that for each property defined in defaultConfiguration(), + // there is a form element of the same name, and saves its value into + // that configuration property. + $values = $form_state->getValue($form['#parents']); + + $property_names = array_keys($this->defaultConfiguration()); + + foreach ($property_names as $property) { + $this->configuration[$property] = $values[$property]; + } + } + + /** + * {@inheritdoc} + */ + public function getConfiguration() { + return $this->configuration; + } + + /** + * {@inheritdoc} + */ + public function setConfiguration(array $configuration) { + $this->configuration = NestedArray::mergeDeep($this->defaultConfiguration(), $configuration); + } + + /** + * {@inheritdoc} + */ + public function setConfigurationValuesOnLicense(LicenseInterface $license) { + // By default, assume that a key in the configuration array corresponds to + // a field provided to the license type as an entity trait field in + // buildFieldDefinitions(). + foreach ($this->configuration as $key => $value) { + $license->{$key} = $value; + } + } + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + return []; + } + + /** + * {@inheritdoc} + */ + public function calculateDependencies() { + return []; + } + + /** + * {@inheritdoc} + */ + public function buildFieldDefinitions() { + return []; + } + +} diff --git a/docroot/modules/contrib/commerce_license/src/Plugin/Commerce/LicenseType/LicenseTypeInterface.php b/docroot/modules/contrib/commerce_license/src/Plugin/Commerce/LicenseType/LicenseTypeInterface.php new file mode 100755 index 000000000..52603fc55 --- /dev/null +++ b/docroot/modules/contrib/commerce_license/src/Plugin/Commerce/LicenseType/LicenseTypeInterface.php @@ -0,0 +1,100 @@ + $license->license_role->entity->label(), + ]; + return $this->t('@role role license', $args); + } + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + return [ + 'license_role' => '' + ] + parent::defaultConfiguration(); + } + + /** + * {@inheritdoc} + */ + public function grantLicense(LicenseInterface $license) { + // Get the role ID that this license grants. + $role_id = $license->license_role->target_id; + + // Get the owner of the license and grant them the role. + $owner = $license->getOwner(); + $owner->addRole($role_id); + + $owner->save(); + + // TODO: Log this, as it's something admins should see? + } + + /** + * {@inheritdoc} + */ + public function revokeLicense(LicenseInterface $license) { + // Get the role ID that this license grants. + $role_id = $license->license_role->first()->target_id; + + // Get the owner of the license and remove that role. + $owner = $license->getOwner(); + $owner->removeRole($role_id); + + $owner->save(); + + // TODO: Log this, as it's something admins should see? + } + + /** + * {@inheritdoc} + */ + public function checkUserHasExistingRights(UserInterface $user) { + $role_id = $this->configuration['license_role']; + $role = \Drupal::service('entity_type.manager')->getStorage('user_role')->load($role_id); + + return ExistingRightsResult::rightsExistIf( + $user->hasRole($role_id), + $this->t("You already have the @role-label role.", [ + '@role-label' => $role->label(), + ]), + $this->t("User @user already has the @role-label role.", [ + '@user' => $user->getDisplayName(), + '@role-label' => $role->label(), + ]) + ); + } + + /** + * {@inheritdoc} + */ + public function alterEntityOwnerForm(&$form, FormStateInterface $form_state, $form_id, LicenseInterface $license, EntityInterface $form_entity) { + if ($form_entity->getEntityTypeId() != 'user') { + // Only act on a user form. + return; + } + + $licensed_role_id = $license->license_role->target_id; + + $form['account']['roles'][$licensed_role_id]['#disabled'] = TRUE; + $form['account']['roles'][$licensed_role_id]['#description'] = t("This role is granted by a license. It cannot be removed manually."); + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + $roles = \Drupal::service('entity_type.manager')->getStorage('user_role')->loadMultiple(); + + // Skip the built-in roles. + unset($roles[RoleInterface::ANONYMOUS_ID]); + unset($roles[RoleInterface::AUTHENTICATED_ID]); + + // Remove the admin role if it exists. + // TODO: consider removing any role that has is_admin set. + unset($roles['administrator']); + + $options = []; + foreach ($roles as $rid => $role) { + $options[$rid] = $role->label(); + } + + $form['license_role'] = [ + '#type' => 'radios', + '#title' => $this->t('Licensed role'), + '#options' => $options, + '#default_value' => $this->configuration['license_role'], + '#required' => TRUE, + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { + } + + /** + * {@inheritdoc} + */ + public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { + $values = $form_state->getValue($form['#parents']); + + $this->configuration['license_role'] = $values['license_role']; + } + + /** + * {@inheritdoc} + */ + public function buildFieldDefinitions() { + $fields = parent::buildFieldDefinitions(); + + $fields['license_role'] = BundleFieldDefinition::create('entity_reference') + ->setLabel(t('Roles')) + ->setDescription(t('The roles this product grants access to.')) + ->setCardinality(1) + ->setRequired(TRUE) + ->setSetting('target_type', 'user_role'); + + return $fields; + } + +} diff --git a/docroot/modules/contrib/commerce_license/src/Plugin/Commerce/LicenseType/SynchronizableBase.php b/docroot/modules/contrib/commerce_license/src/Plugin/Commerce/LicenseType/SynchronizableBase.php new file mode 100644 index 000000000..a6f2231d1 --- /dev/null +++ b/docroot/modules/contrib/commerce_license/src/Plugin/Commerce/LicenseType/SynchronizableBase.php @@ -0,0 +1,27 @@ +pluginDefinition['label']; + } + + /** + * {@inheritdoc} + */ + public function buildFieldDefinitions() { + return []; + } + +} + diff --git a/docroot/modules/contrib/commerce_license/src/Plugin/views/field/EntityLabel.php b/docroot/modules/contrib/commerce_license/src/Plugin/views/field/EntityLabel.php new file mode 100644 index 000000000..432fc7775 --- /dev/null +++ b/docroot/modules/contrib/commerce_license/src/Plugin/views/field/EntityLabel.php @@ -0,0 +1,99 @@ +definition['entity type field'])) { + $this->additional_fields[$this->definition['entity type field']] = $this->definition['entity type field']; + } + } + + /** + * {@inheritdoc} + */ + public function preRender(&$values) { + $entity_ids_per_type = []; + foreach ($values as $value) { + $type = $this->getEntityTypeFromValues($value); + $entity_ids_per_type[$type][] = $this->getValue($value); + } + + foreach ($entity_ids_per_type as $type => $ids) { + $this->loadedReferencers[$type] = $this->entityManager->getStorage($type)->loadMultiple($ids); + } + } + + /** + * Returns the entity type to use for a given result row. + * + * @param \Drupal\views\ResultRow $row + * A result row of values retrieved from the database. + * + * @return string + */ + protected function getEntityTypeFromValues(ResultRow $row) { + // Support a dynamically defined entity type. + if (!empty($this->definition['entity type field'])) { + $entity_type_id = $this->getValue($row, $this->definition['entity type field']); + return $entity_type_id; + } + else { + // Fall back to the entity type of the table. + return parent::getEntityType(); + } + } + + /** + * {@inheritdoc} + */ + public function render(ResultRow $values) { + $entity_type_id = $this->getEntityTypeFromValues($values); + + $value = $this->getValue($values); + + if (empty($this->loadedReferencers[$entity_type_id][$value])) { + return; + } + + /** @var $entity \Drupal\Core\Entity\EntityInterface */ + $entity = $this->loadedReferencers[$entity_type_id][$value]; + + if (!empty($this->options['link_to_entity'])) { + try { + $this->options['alter']['url'] = $entity->toUrl(); + $this->options['alter']['make_link'] = TRUE; + } + catch (UndefinedLinkTemplateException $e) { + $this->options['alter']['make_link'] = FALSE; + } + catch (EntityMalformedException $e) { + $this->options['alter']['make_link'] = FALSE; + } + } + + return $this->sanitizeValue($entity->label()); + } + +} diff --git a/docroot/modules/contrib/commerce_license/templates/commerce_license.html.twig b/docroot/modules/contrib/commerce_license/templates/commerce_license.html.twig new file mode 100644 index 000000000..d5e2a590f --- /dev/null +++ b/docroot/modules/contrib/commerce_license/templates/commerce_license.html.twig @@ -0,0 +1,22 @@ +{# +/** + * @file commerce_license.html.twig + * Default theme implementation to present License data. + * + * This template is used when viewing License pages. + * + * + * Available variables: + * - content: A list of content items. Use 'content' to print all content, or + * - attributes: HTML attributes for the container element. + * + * @see template_preprocess_commerce_license() + * + * @ingroup themeable + */ +#} + + {% if content %} + {{- content -}} + {% endif %} + diff --git a/docroot/modules/contrib/commerce_license/tests/modules/commerce_license_set_expiry_test/commerce_license_set_expiry_test.info.yml b/docroot/modules/contrib/commerce_license/tests/modules/commerce_license_set_expiry_test/commerce_license_set_expiry_test.info.yml new file mode 100644 index 000000000..fb9cf4b94 --- /dev/null +++ b/docroot/modules/contrib/commerce_license/tests/modules/commerce_license_set_expiry_test/commerce_license_set_expiry_test.info.yml @@ -0,0 +1,5 @@ +name: 'Commerce License Set Expiry Test' +type: module +description: 'Test module for Commerce License' +package: Testing +core: 8.x diff --git a/docroot/modules/contrib/commerce_license/tests/modules/commerce_license_set_expiry_test/src/Plugin/RecurringPeriod/CommerceLicenseSetExpiryTest.php b/docroot/modules/contrib/commerce_license/tests/modules/commerce_license_set_expiry_test/src/Plugin/RecurringPeriod/CommerceLicenseSetExpiryTest.php new file mode 100644 index 000000000..7c1982b76 --- /dev/null +++ b/docroot/modules/contrib/commerce_license/tests/modules/commerce_license_set_expiry_test/src/Plugin/RecurringPeriod/CommerceLicenseSetExpiryTest.php @@ -0,0 +1,24 @@ +set('commerce_license_test.called.checkUserHasExistingRights', TRUE); + + // The state tells us whether to say rights exist or not. + return ExistingRightsResult::rightsExistIf( + \Drupal::state()->get('commerce_license_test.existing_rights_check_config'), + $this->t("You already have the rights."), + $this->t("The user already has the rights.") + ); + } + +} diff --git a/docroot/modules/contrib/commerce_license/tests/modules/commerce_license_test/src/Plugin/Commerce/LicenseType/LicenseStateChangeTest.php b/docroot/modules/contrib/commerce_license/tests/modules/commerce_license_test/src/Plugin/Commerce/LicenseType/LicenseStateChangeTest.php new file mode 100644 index 000000000..6aefdd681 --- /dev/null +++ b/docroot/modules/contrib/commerce_license/tests/modules/commerce_license_test/src/Plugin/Commerce/LicenseType/LicenseStateChangeTest.php @@ -0,0 +1,39 @@ +set('commerce_license_state_change_test', 'grantLicense'); + } + + /** + * {@inheritdoc} + */ + public function revokeLicense(LicenseInterface $license) { + $state = \Drupal::state(); + $state->set('commerce_license_state_change_test', 'revokeLicense'); + } + +} diff --git a/docroot/modules/contrib/commerce_license/tests/modules/commerce_license_test/src/Plugin/Commerce/LicenseType/TestLicenseBase.php b/docroot/modules/contrib/commerce_license/tests/modules/commerce_license_test/src/Plugin/Commerce/LicenseType/TestLicenseBase.php new file mode 100644 index 000000000..cf7f46404 --- /dev/null +++ b/docroot/modules/contrib/commerce_license/tests/modules/commerce_license_test/src/Plugin/Commerce/LicenseType/TestLicenseBase.php @@ -0,0 +1,35 @@ +owner = $this->createUser(); + + $this->roleStorage = $this->container->get('entity_type.manager')->getStorage('user_role'); + $role = $this->roleStorage->create([ + 'id' => 'licensed_role', + 'label' => 'Licensed role', + ]); + $role->save(); + + $this->licenseStorage = \Drupal::service('entity_type.manager')->getStorage('commerce_license'); + + // Create a license in the 'active' state. + $license = $this->licenseStorage->create([ + 'type' => 'role', + 'state' => 'active', + 'product_variation' => 1, + 'uid' => $this->owner->id(), + // Use the unlimited expiry plugin as it's simple. + 'expiration_type' => [ + 'target_plugin_id' => 'unlimited', + 'target_plugin_configuration' => [], + ], + 'license_role' => $role, + ]); + + // Confirm the license: this puts it into the 'active' state. + $transition = $license->getState()->getWorkflow()->getTransition('confirm'); + $license->getState()->applyTransition($transition); + $license->save(); + } + + /** + * Tests a role granted by a license is locked on a user's account form. + */ + public function testUserFormHasLock() { + // Create an admin user who can edit a user's roles. + $admin = $this->createUser([ + 'administer users', + 'administer permissions', + ]); + + $this->drupalLogin($admin); + + // Get the account for for the license owner user. + $this->drupalGet("user/" . $this->owner->id() . "/edit"); + $this->assertSession()->statusCodeEquals(200); + + $this->assertSession()->fieldDisabled("roles[licensed_role]"); + $this->assertSession()->pageTextContains("This role is granted by a license. It cannot be removed manually."); + } + +} diff --git a/docroot/modules/contrib/commerce_license/tests/src/Kernel/CommerceAvailabilityExistingRightsTest.php b/docroot/modules/contrib/commerce_license/tests/src/Kernel/CommerceAvailabilityExistingRightsTest.php new file mode 100644 index 000000000..bcacbb115 --- /dev/null +++ b/docroot/modules/contrib/commerce_license/tests/src/Kernel/CommerceAvailabilityExistingRightsTest.php @@ -0,0 +1,197 @@ +installEntitySchema('profile'); + $this->installEntitySchema('commerce_product'); + $this->installEntitySchema('commerce_product_variation'); + $this->installEntitySchema('commerce_order'); + $this->installEntitySchema('commerce_order_item'); + $this->installConfig('commerce_order'); + $this->installConfig('commerce_product'); + $this->createUser(); + + // Create an order type for licenses which uses the fulfillment workflow. + $order_type = $this->createEntity('commerce_order_type', [ + 'id' => 'license_order_type', + 'label' => $this->randomMachineName(), + 'workflow' => 'order_fulfillment', + ]); + commerce_order_add_order_items_field($order_type); + + // Create an order item type that uses that order type. + $order_item_type = $this->createEntity('commerce_order_item_type', [ + 'id' => 'license_order_item_type', + 'label' => $this->randomMachineName(), + 'purchasableEntityType' => 'commerce_product_variation', + 'orderType' => 'license_order_type', + 'traits' => ['commerce_license_order_item_type'], + ]); + $this->traitManager = \Drupal::service('plugin.manager.commerce_entity_trait'); + $trait = $this->traitManager->createInstance('commerce_license_order_item_type'); + $this->traitManager->installTrait($trait, 'commerce_order_item', $order_item_type->id()); + + // Create a product variation type with the license trait, using our order + // item type. + $product_variation_type = $this->createEntity('commerce_product_variation_type', [ + 'id' => 'license_pv_type', + 'label' => $this->randomMachineName(), + 'orderItemType' => 'license_order_item_type', + 'traits' => ['commerce_license'], + ]); + $trait = $this->traitManager->createInstance('commerce_license'); + $this->traitManager->installTrait($trait, 'commerce_product_variation', $product_variation_type->id()); + + // Create a product variation which grants a license. + $this->variation = $this->createEntity('commerce_product_variation', [ + 'type' => 'license_pv_type', + 'sku' => $this->randomMachineName(), + 'price' => [ + 'number' => 999, + 'currency_code' => 'USD', + ], + 'license_type' => [ + 'target_plugin_id' => 'existing_rights_check_config', + 'target_plugin_configuration' => [], + ], + // Use the unlimited expiry plugin as it's simple. + 'license_expiration' => [ + 'target_plugin_id' => 'unlimited', + 'target_plugin_configuration' => [], + ], + ]); + + // We need a product too otherwise tests complain about the missing + // backreference. + $this->createEntity('commerce_product', [ + 'type' => 'default', + 'title' => $this->randomMachineName(), + 'stores' => [$this->store], + 'variations' => [$this->variation], + ]); + $this->reloadEntity($this->variation); + $this->variation->save(); + + // Create a user to use for orders. + $this->user = $this->createUser(); + } + + /** + * Tests a product is not to the cart when the user has existing rights. + */ + public function testAddToCart() { + $this->installCommerceCart(); + + $this->store = $this->createStore(); + $customer = $this->createUser(); + $cart_order = $this->container->get('commerce_cart.cart_provider')->createCart('license_order_type', $this->store, $customer); + $this->cartManager = $this->container->get('commerce_cart.cart_manager'); + + // Tell our test license type plugin to report that the user has existing rights. + \Drupal::state()->set('commerce_license_test.existing_rights_check_config', TRUE); + + // Try to add the product to the cart. + $this->cartManager->addEntity($cart_order, $this->variation); + $order = $this->reloadEntity($cart_order); + + $this->assertTrue(\Drupal::state()->get('commerce_license_test.called.checkUserHasExistingRights'), "The checkUserHasExistingRights() method was called on the license type plugin."); + + $this->assertCount(0, $order->getItems(), "The product was not added to the cart."); + + // Tell our test license type plugin to report that the user does not have + // existing rights. + \Drupal::state()->set('commerce_license_test.existing_rights_check_config', FALSE); + + // Try to add the product to the cart. + $this->cartManager->addEntity($cart_order, $this->variation); + $order = $this->reloadEntity($cart_order); + + $this->assertCount(1, $order->getItems(), "The product was added to the cart."); + } + + /** + * Creates and saves a new entity. + * + * @param string $entity_type + * The entity type to be created. + * @param array $values + * An array of settings. + * Example: 'id' => 'foo'. + * + * @return \Drupal\Core\Entity\EntityInterface + * A new, saved entity. + */ + protected function createEntity($entity_type, array $values) { + /** @var \Drupal\Core\Entity\EntityStorageInterface $storage */ + $storage = \Drupal::service('entity_type.manager')->getStorage($entity_type); + $entity = $storage->create($values); + $status = $entity->save(); + $this->assertEquals(SAVED_NEW, $status, new FormattableMarkup('Created %label entity %type.', [ + '%label' => $entity->getEntityType()->getLabel(), + '%type' => $entity->id(), + ])); + // The newly saved entity isn't identical to a loaded one, and would fail + // comparisons. + $entity = $storage->load($entity->id()); + + return $entity; + } + +} diff --git a/docroot/modules/contrib/commerce_license/tests/src/Kernel/CommerceOrderSyncTest.php b/docroot/modules/contrib/commerce_license/tests/src/Kernel/CommerceOrderSyncTest.php new file mode 100644 index 000000000..b4d2d8eae --- /dev/null +++ b/docroot/modules/contrib/commerce_license/tests/src/Kernel/CommerceOrderSyncTest.php @@ -0,0 +1,200 @@ +installEntitySchema('profile'); + $this->installEntitySchema('commerce_product'); + $this->installEntitySchema('commerce_product_variation'); + $this->installEntitySchema('commerce_order'); + $this->installEntitySchema('commerce_order_item'); + $this->installConfig('commerce_order'); + $this->installConfig('commerce_product'); + $this->createUser(); + + // Create an order type for licenses which uses the fulfillment workflow. + $order_type = $this->createEntity('commerce_order_type', [ + 'id' => 'license_order_type', + 'label' => $this->randomMachineName(), + 'workflow' => 'order_fulfillment', + ]); + commerce_order_add_order_items_field($order_type); + + // Create an order item type that uses that order type. + $order_item_type = $this->createEntity('commerce_order_item_type', [ + 'id' => 'license_order_item_type', + 'label' => $this->randomMachineName(), + 'purchasableEntityType' => 'commerce_product_variation', + 'orderType' => 'license_order_type', + 'traits' => ['commerce_license_order_item_type'], + ]); + $this->traitManager = \Drupal::service('plugin.manager.commerce_entity_trait'); + $trait = $this->traitManager->createInstance('commerce_license_order_item_type'); + $this->traitManager->installTrait($trait, 'commerce_order_item', $order_item_type->id()); + + // Create a product variation type with the license trait, using our order + // item type. + $product_variation_type = $this->createEntity('commerce_product_variation_type', [ + 'id' => 'license_pv_type', + 'label' => $this->randomMachineName(), + 'orderItemType' => 'license_order_item_type', + 'traits' => ['commerce_license'], + ]); + $trait = $this->traitManager->createInstance('commerce_license'); + $this->traitManager->installTrait($trait, 'commerce_product_variation', $product_variation_type->id()); + + // Create a product variation which grants a license. + $this->variation = $this->createEntity('commerce_product_variation', [ + 'type' => 'license_pv_type', + 'sku' => $this->randomMachineName(), + 'price' => [ + 'number' => 999, + 'currency_code' => 'USD', + ], + 'license_type' => [ + 'target_plugin_id' => 'simple', + 'target_plugin_configuration' => [], + ], + // Use the unlimited expiry plugin as it's simple. + 'license_expiration' => [ + 'target_plugin_id' => 'unlimited', + 'target_plugin_configuration' => [], + ], + ]); + + // We need a product too otherwise tests complain about the missing + // backreference. + $this->createEntity('commerce_product', [ + 'type' => 'default', + 'title' => $this->randomMachineName(), + 'stores' => [$this->store], + 'variations' => [$this->variation], + ]); + $this->reloadEntity($this->variation); + $this->variation->save(); + + // Create a user to use for orders. + $this->user = $this->createUser(); + } + + /** + * Tests a license is created when an order reaches the fulfillment state. + */ + public function testOrderFulfillment() { + $this->installCommerceCart(); + + $this->store = $this->createStore(); + $customer = $this->createUser(); + $cart_order = $this->container->get('commerce_cart.cart_provider')->createCart('license_order_type', $this->store, $customer); + $this->cartManager = $this->container->get('commerce_cart.cart_manager'); + $this->cartManager->addEntity($cart_order, $this->variation); + + // Place, then fulfill the order. + $workflow = $cart_order->getState()->getWorkflow(); + $cart_order->getState()->applyTransition($workflow->getTransition('place')); + $cart_order->save(); + + $workflow = $cart_order->getState()->getWorkflow(); + $cart_order->getState()->applyTransition($workflow->getTransition('fulfill')); + $cart_order->save(); + + $order = $this->reloadEntity($cart_order); + + // Get the order item. There should be only one in the order. + $order_item = $order->getItems()[0]; + + // Check that the order item now refers to a new license which has been + // created for the user. + $license = $order_item->license->entity; + + $this->assertEquals('commerce_license', $license->getEntityTypeId(), 'The order item has a license entity set in its license field.'); + $this->assertEquals('simple', $license->bundle(), 'The license entity is of the expected type.'); + $this->assertEquals($customer->id(), $license->getOwnerId(), 'The license entity has the expected owner.'); + $this->assertEquals($this->variation->id(), $license->product_variation->target_id, 'The license entity references the product variation.'); + $this->assertEquals('active', $license->state->value, 'The license is active.'); + + // Note that we don't need to check that the license has activated its + // license type plugin, as that is covered by LicenseStateChangeTest. + } + + /** + * Creates and saves a new entity. + * + * @param string $entity_type + * The entity type to be created. + * @param array $values + * An array of settings. + * Example: 'id' => 'foo'. + * + * @return \Drupal\Core\Entity\EntityInterface + * A new, saved entity. + */ + protected function createEntity($entity_type, array $values) { + /** @var \Drupal\Core\Entity\EntityStorageInterface $storage */ + $storage = \Drupal::service('entity_type.manager')->getStorage($entity_type); + $entity = $storage->create($values); + $status = $entity->save(); + $this->assertEquals(SAVED_NEW, $status, new FormattableMarkup('Created %label entity %type.', [ + '%label' => $entity->getEntityType()->getLabel(), + '%type' => $entity->id(), + ])); + // The newly saved entity isn't identical to a loaded one, and would fail + // comparisons. + $entity = $storage->load($entity->id()); + + return $entity; + } + +} diff --git a/docroot/modules/contrib/commerce_license/tests/src/Kernel/LicenseDeletionTest.php b/docroot/modules/contrib/commerce_license/tests/src/Kernel/LicenseDeletionTest.php new file mode 100644 index 000000000..6ca252e23 --- /dev/null +++ b/docroot/modules/contrib/commerce_license/tests/src/Kernel/LicenseDeletionTest.php @@ -0,0 +1,79 @@ +installEntitySchema('user'); + $this->installEntitySchema('commerce_product_variation'); + $this->installEntitySchema('commerce_license'); + + $this->licenseStorage = \Drupal::service('entity_type.manager')->getStorage('commerce_license'); + } + + /** + * Tests that exceptions thrown by workers are handled properly. + */ + public function testLicenseDeletion() { + $owner = $this->createUser(); + + // Create a license in the 'active' state. + $license = $this->licenseStorage->create([ + 'type' => 'state_change_test', + 'state' => 'active', + 'product_variation' => 1, + 'uid' => $owner->id(), + // Use the unlimited expiry plugin as it's simple. + 'expiration_type' => [ + 'target_plugin_id' => 'unlimited', + 'target_plugin_configuration' => [], + ], + ]); + + $license->save(); + + // Ensure the test tracking state is clear. + \Drupal::state()->set('commerce_license_state_change_test', NULL); + + $license->delete(); + + // Deleting the license should cause the plugin to revoke the rights. + $this->assertEqual(\Drupal::state()->get('commerce_license_state_change_test'), 'revokeLicense', "The plugin's revokeLicense() method was called."); + } + +} diff --git a/docroot/modules/contrib/commerce_license/tests/src/Kernel/LicenseRoleTypeTest.php b/docroot/modules/contrib/commerce_license/tests/src/Kernel/LicenseRoleTypeTest.php new file mode 100644 index 000000000..6b710c758 --- /dev/null +++ b/docroot/modules/contrib/commerce_license/tests/src/Kernel/LicenseRoleTypeTest.php @@ -0,0 +1,152 @@ +installEntitySchema('commerce_product_variation'); + $this->installEntitySchema('commerce_license'); + $this->installConfig('user'); + + // Install the bundle plugins for the license entity type which this + // module provides. This takes care of creating the fields which the bundle + // plugins define. + $this->container->get('entity.bundle_plugin_installer')->installBundles( + $this->container->get('entity_type.manager')->getDefinition('commerce_license'), + ['commerce_license'] + ); + + $this->licenseTypeManager = $this->container->get('plugin.manager.commerce_license_type'); + $this->licenseStorage = $this->container->get('entity_type.manager')->getStorage('commerce_license'); + $this->roleStorage = $this->container->get('entity_type.manager')->getStorage('user_role'); + } + + /** + * Tests that a role license grants and revokes a role from its owner. + */ + public function testLicenseGrantRevoke() { + $role = $this->roleStorage->create(['id' => 'licensed_role']); + $role->save(); + + $license_owner = $this->createUser(); + + // Create a license in the 'new' state, owned by the user. + $license = $this->licenseStorage->create([ + 'type' => 'role', + 'state' => 'new', + 'product_variation' => 1, + 'uid' => $license_owner->id(), + // Use the unlimited expiry plugin as it's simple. + 'expiration_type' => [ + 'target_plugin_id' => 'unlimited', + 'target_plugin_configuration' => [], + ], + 'license_role' => $role, + ]); + + $license->save(); + + // Assert the user does not have the role. + $license_owner = $this->reloadEntity($license_owner); + $this->assertFalse($license_owner->hasRole('licensed_role'), "The user does not have the licensed role."); + + // Don't bother pushing the license through state changes, as that is + // by covered by LicenseStateChangeTest. Just call the plugin direct to + // grant the license. + $license->getTypePlugin()->grantLicense($license); + + // The license owner now has the role. + $license_owner = $this->reloadEntity($license_owner); + $this->assertTrue($license_owner->hasRole('licensed_role'), "The user has the licensed role."); + + // Revoke the license. + $license->getTypePlugin()->revokeLicense($license); + + // Assert the user does not have the role. + $license_owner = $this->reloadEntity($license_owner); + $this->assertFalse($license_owner->hasRole('licensed_role'), "The user does not have the licensed role."); + } + + /** + * Tests a license receives field values from a configured plugin. + */ + public function testLicenseCreationFromPlugin() { + $role = $this->roleStorage->create(['id' => 'licensed_role']); + $role->save(); + + $license_owner = $this->createUser(); + + // Create a license which doesn't have any type-specific field values set. + $license = $this->licenseStorage->create([ + 'type' => 'role', + 'state' => 'new', + 'product_variation' => 1, + 'uid' => $license_owner->id(), + 'expiration_type' => [ + 'target_plugin_id' => 'unlimited', + 'target_plugin_configuration' => [], + ], + ]); + $license->save(); + + // Create a configured role license plugin. + $plugin_configuration = [ + 'license_role' => $role->id(), + ]; + $license_type_plugin = $this->licenseTypeManager->createInstance('role', $plugin_configuration); + + // Set the license's type-specific fields from the configured plugin. + $license->setValuesFromPlugin($license_type_plugin); + + $license->save(); + $license = $this->reloadEntity($license); + + $this->assertEquals($role->id(), $license->license_role->target_id, "The role field was set on the license."); + } + +} diff --git a/docroot/modules/contrib/commerce_license/tests/src/Kernel/LicenseSetExpiryTest.php b/docroot/modules/contrib/commerce_license/tests/src/Kernel/LicenseSetExpiryTest.php new file mode 100644 index 000000000..65624ea9b --- /dev/null +++ b/docroot/modules/contrib/commerce_license/tests/src/Kernel/LicenseSetExpiryTest.php @@ -0,0 +1,88 @@ +installEntitySchema('user'); + $this->installEntitySchema('commerce_product_variation'); + $this->installEntitySchema('commerce_license'); + + $this->licenseStorage = \Drupal::service('entity_type.manager')->getStorage('commerce_license'); + } + + /** + * Tests a license has its expiry date set from the expiry plugin. + */ + public function testLicenseSetExpiry() { + $owner = $this->createUser(); + + // Create a license in the 'new' state, without an expiration timestamp. + $license = $this->licenseStorage->create([ + 'type' => 'simple', + 'state' => 'new', + 'product_variation' => 1, + 'uid' => $owner->id(), + // Use our test expiration plugin. + 'expiration_type' => [ + 'target_plugin_id' => 'commerce_license_set_expiry_test', + 'target_plugin_configuration' => [], + ], + ]); + + $license->save(); + + // Activate the license: this puts it into the 'pending' state. + $transition = $license->getState()->getWorkflow()->getTransition('activate'); + $license->getState()->applyTransition($transition); + $license->save(); + + // Check the expiration timestamp is not yet set. + $this->assertEqual($license->expires->value, 0); + + // Confirm the license: this puts it into the 'active' state. + $transition = $license->getState()->getWorkflow()->getTransition('confirm'); + $license->getState()->applyTransition($transition); + $license->save(); + + // Check the expiration timestamp is now set. + $this->assertEqual($license->expires->value, 12345); + } + +} diff --git a/docroot/modules/contrib/commerce_license/tests/src/Kernel/LicenseStateChangeTest.php b/docroot/modules/contrib/commerce_license/tests/src/Kernel/LicenseStateChangeTest.php new file mode 100644 index 000000000..c03c306f1 --- /dev/null +++ b/docroot/modules/contrib/commerce_license/tests/src/Kernel/LicenseStateChangeTest.php @@ -0,0 +1,139 @@ +installEntitySchema('user'); + $this->installEntitySchema('commerce_product_variation'); + $this->installEntitySchema('commerce_license'); + + $this->licenseStorage = \Drupal::service('entity_type.manager')->getStorage('commerce_license'); + } + + /** + * Tests that changes to a license's state causes the plugin to react. + */ + public function testLicenseStateChanges() { + $owner = $this->createUser(); + + // Create a license in the 'new' state. + $license = $this->licenseStorage->create([ + 'type' => 'state_change_test', + 'state' => 'new', + 'product_variation' => 1, + 'uid' => $owner->id(), + // Use the unlimited expiry plugin as it's simple. + 'expiration_type' => [ + 'target_plugin_id' => 'unlimited', + 'target_plugin_configuration' => [], + ], + ]); + + $license->save(); + + // The license is not active: the plugin should not react. + $this->assertEqual(\Drupal::state()->get('commerce_license_state_change_test'), NULL); + + // Activate the license: this puts it into the 'pending' state. + $transition = $license->getState()->getWorkflow()->getTransition('activate'); + $license->getState()->applyTransition($transition); + $license->save(); + + // The license is not active: the plugin should not react. + $this->assertEqual(\Drupal::state()->get('commerce_license_state_change_test'), NULL); + + // Confirm the license: this puts it into the 'active' state. + $transition = $license->getState()->getWorkflow()->getTransition('confirm'); + $license->getState()->applyTransition($transition); + $license->save(); + + // The license is now active: the plugin should be called. + $this->assertEqual(\Drupal::state()->get('commerce_license_state_change_test'), 'grantLicense'); + + // Reset the test tracking state. + \Drupal::state()->set('commerce_license_state_change_test', NULL); + + // Save the license again without changing its state. + $license->save(); + + // The license is unchanged: the plugin should not react. + $this->assertEqual(\Drupal::state()->get('commerce_license_state_change_test'), NULL); + + // Suspend the license. + $transition = $license->getState()->getWorkflow()->getTransition('suspend'); + $license->getState()->applyTransition($transition); + $license->save(); + + // The license is now inactive: the plugin should be called. + $this->assertEqual(\Drupal::state()->get('commerce_license_state_change_test'), 'revokeLicense'); + + // Reset the test tracking state. + \Drupal::state()->set('commerce_license_state_change_test', NULL); + + // Revoke the license. + $transition = $license->getState()->getWorkflow()->getTransition('revoke'); + $license->getState()->applyTransition($transition); + $license->save(); + + // Although the license state changed, it has gone from one inactive state + // to another: the plugin should not react. + $this->assertEqual(\Drupal::state()->get('commerce_license_state_change_test'), NULL); + + // Reset the test tracking state. + \Drupal::state()->set('commerce_license_state_change_test', NULL); + + // Test creating a license initially in the active state. + $license = $this->licenseStorage->create([ + 'type' => 'state_change_test', + 'state' => 'active', + 'product_variation' => 1, + 'uid' => 1, + 'expiration_type' => [ + 'target_plugin_id' => 'unlimited', + 'target_plugin_configuration' => [], + ], + ]); + + $license->save(); + + // The license is created active: the plugin should be called. + $this->assertEqual(\Drupal::state()->get('commerce_license_state_change_test'), 'grantLicense'); + } + +} diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index 5ee867c8d..95f6afca6 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -5567,71 +5567,6 @@ ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock." }, - { - "name": "phpspec/prophecy", - "version": "v1.7.2", - "version_normalized": "1.7.2.0", - "source": { - "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6", - "reference": "c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.0.2", - "php": "^5.3|^7.0", - "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", - "sebastian/comparator": "^1.1|^2.0", - "sebastian/recursion-context": "^1.0|^2.0|^3.0" - }, - "require-dev": { - "phpspec/phpspec": "^2.5|^3.2", - "phpunit/phpunit": "^4.8 || ^5.6.5" - }, - "time": "2017-09-04T11:05:03+00:00", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.7.x-dev" - } - }, - "installation-source": "dist", - "autoload": { - "psr-0": { - "Prophecy\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - }, - { - "name": "Marcello Duarte", - "email": "marcello.duarte@gmail.com" - } - ], - "description": "Highly opinionated mocking framework for PHP 5.3+", - "homepage": "https://github.com/phpspec/prophecy", - "keywords": [ - "Double", - "Dummy", - "fake", - "mock", - "spy", - "stub" - ] - }, { "name": "phpunit/phpunit", "version": "4.8.36", @@ -5985,65 +5920,6 @@ ], "description": "Internationalization library powered by CLDR data." }, - { - "name": "drupal/address", - "version": "1.2.0", - "version_normalized": "1.2.0.0", - "source": { - "type": "git", - "url": "https://git.drupal.org/project/address", - "reference": "8.x-1.2" - }, - "dist": { - "type": "zip", - "url": "https://ftp.drupal.org/files/projects/address-8.x-1.2.zip", - "reference": "8.x-1.2", - "shasum": "041445ac14087be943c0c1c562b9bf800d87f7e8" - }, - "require": { - "commerceguys/addressing": "~1.0", - "commerceguys/intl": "~0.7", - "drupal/core": "*" - }, - "type": "drupal-module", - "extra": { - "branch-alias": { - "dev-1.x": "1.x-dev" - }, - "drupal": { - "version": "8.x-1.2", - "datestamp": "1505896144", - "security-coverage": { - "status": "covered", - "message": "Covered by Drupal's security advisory policy" - } - } - }, - "installation-source": "dist", - "notification-url": "https://packages.drupal.org/8/downloads", - "license": [ - "GPL-2.0+" - ], - "authors": [ - { - "name": "bojanz", - "homepage": "https://www.drupal.org/user/86106" - }, - { - "name": "googletorp", - "homepage": "https://www.drupal.org/user/386230" - }, - { - "name": "rszrama", - "homepage": "https://www.drupal.org/user/49344" - } - ], - "description": "Provides functionality for storing, validating and displaying international postal addresses.", - "homepage": "http://drupal.org/project/address", - "support": { - "source": "http://cgit.drupalcode.org/address" - } - }, { "name": "drupal/age_field_formatter", "version": "1.0.0", @@ -8561,7 +8437,7 @@ "source": { "type": "git", "url": "https://git.drupal.org/project/commerce_license", - "reference": "6504af9fb47979a78a1f619b582a08b68506bbe6" + "reference": "4afdcf9970abc8d648d5e9ec9d79ec0a56280822" }, "require": { "drupal/commerce": "*", @@ -8579,7 +8455,7 @@ }, "drupal": { "version": "8.x-2.x-dev", - "datestamp": "1511174884", + "datestamp": "1511209685", "security-coverage": { "status": "not-covered", "message": "Dev releases are not covered by Drupal security advisories." @@ -8741,5 +8617,129 @@ "support": { "source": "http://cgit.drupalcode.org/commerce_paypal" } + }, + { + "name": "drupal/address", + "version": "1.3.0", + "version_normalized": "1.3.0.0", + "source": { + "type": "git", + "url": "https://git.drupal.org/project/address", + "reference": "8.x-1.3" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/address-8.x-1.3.zip", + "reference": "8.x-1.3", + "shasum": "170551d6ecf4a08bac178f31b9869a626279d9eb" + }, + "require": { + "commerceguys/addressing": "~1.0", + "commerceguys/intl": "~0.7", + "drupal/core": "*" + }, + "type": "drupal-module", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + }, + "drupal": { + "version": "8.x-1.3", + "datestamp": "1511382784", + "security-coverage": { + "status": "covered", + "message": "Covered by Drupal's security advisory policy" + } + } + }, + "installation-source": "dist", + "notification-url": "https://packages.drupal.org/8/downloads", + "license": [ + "GPL-2.0+" + ], + "authors": [ + { + "name": "bojanz", + "homepage": "https://www.drupal.org/user/86106" + }, + { + "name": "googletorp", + "homepage": "https://www.drupal.org/user/386230" + }, + { + "name": "rszrama", + "homepage": "https://www.drupal.org/user/49344" + } + ], + "description": "Provides functionality for storing, validating and displaying international postal addresses.", + "homepage": "http://drupal.org/project/address", + "support": { + "source": "http://cgit.drupalcode.org/address" + } + }, + { + "name": "phpspec/prophecy", + "version": "1.7.3", + "version_normalized": "1.7.3.0", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf", + "reference": "e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", + "sebastian/comparator": "^1.1|^2.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5|^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7" + }, + "time": "2017-11-24T13:59:53+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ] } ] diff --git a/vendor/phpspec/prophecy/composer.json b/vendor/phpspec/prophecy/composer.json index fc9ba782c..5bdbdb3e4 100644 --- a/vendor/phpspec/prophecy/composer.json +++ b/vendor/phpspec/prophecy/composer.json @@ -27,7 +27,7 @@ "require-dev": { "phpspec/phpspec": "^2.5|^3.2", - "phpunit/phpunit": "^4.8 || ^5.6.5" + "phpunit/phpunit": "^4.8.35 || ^5.7" }, "autoload": { diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/SplFileInfoPatch.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/SplFileInfoPatch.php index 3d8e44f3d..ceee94a2e 100644 --- a/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/SplFileInfoPatch.php +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/SplFileInfoPatch.php @@ -34,7 +34,6 @@ public function supports(ClassNode $node) if (null === $node->getParentClass()) { return false; } - return 'SplFileInfo' === $node->getParentClass() || is_subclass_of($node->getParentClass(), 'SplFileInfo') ; @@ -67,6 +66,13 @@ public function apply(ClassNode $node) return; } + if ($this->nodeIsSymfonySplFileInfo($node)) { + $filePath = str_replace('\\','\\\\',__FILE__); + $constructor->setCode('return parent::__construct("' . $filePath .'", "", "");'); + + return; + } + $constructor->useParentCode(); } @@ -103,4 +109,15 @@ private function nodeIsSplFileObject(ClassNode $node) return 'SplFileObject' === $parent || is_subclass_of($parent, 'SplFileObject'); } + + /** + * @param ClassNode $node + * @return boolean + */ + private function nodeIsSymfonySplFileInfo(ClassNode $node) + { + $parent = $node->getParentClass(); + + return 'Symfony\\Component\\Finder\\SplFileInfo' === $parent; + } }