diff --git a/core/modules/config/src/Tests/ConfigSchemaTest.php b/core/modules/config/src/Tests/ConfigSchemaTest.php index 2f0c6a9..a94e26a 100644 --- a/core/modules/config/src/Tests/ConfigSchemaTest.php +++ b/core/modules/config/src/Tests/ConfigSchemaTest.php @@ -176,8 +176,6 @@ function testSchemaMapping() { $expected['mapping']['effects']['sequence']['mapping']['data']['type'] = 'image.effect.[%parent.id]'; $expected['mapping']['effects']['sequence']['mapping']['weight']['type'] = 'integer'; $expected['mapping']['effects']['sequence']['mapping']['uuid']['type'] = 'string'; - $expected['mapping']['replacementID']['type'] = 'string'; - $expected['mapping']['replacementID']['label'] = 'Replacement style ID'; $expected['mapping']['third_party_settings']['type'] = 'sequence'; $expected['mapping']['third_party_settings']['label'] = 'Third party settings'; $expected['mapping']['third_party_settings']['sequence']['type'] = '[%parent.%parent.%type].third_party.[%key]'; diff --git a/core/modules/image/config/install/image.style.large.yml b/core/modules/image/config/install/image.style.large.yml index 006652b..c793584 100644 --- a/core/modules/image/config/install/image.style.large.yml +++ b/core/modules/image/config/install/image.style.large.yml @@ -12,4 +12,3 @@ effects: width: 480 height: 480 upscale: false -replacementID: null diff --git a/core/modules/image/config/install/image.style.medium.yml b/core/modules/image/config/install/image.style.medium.yml index 5687a09..775a511 100644 --- a/core/modules/image/config/install/image.style.medium.yml +++ b/core/modules/image/config/install/image.style.medium.yml @@ -12,4 +12,3 @@ effects: width: 220 height: 220 upscale: false -replacementID: null diff --git a/core/modules/image/config/install/image.style.thumbnail.yml b/core/modules/image/config/install/image.style.thumbnail.yml index 0714c37..73dc9b8 100644 --- a/core/modules/image/config/install/image.style.thumbnail.yml +++ b/core/modules/image/config/install/image.style.thumbnail.yml @@ -12,4 +12,3 @@ effects: width: 100 height: 100 upscale: false -replacementID: null diff --git a/core/modules/image/config/schema/image.schema.yml b/core/modules/image/config/schema/image.schema.yml index 788a7cc..323220b 100644 --- a/core/modules/image/config/schema/image.schema.yml +++ b/core/modules/image/config/schema/image.schema.yml @@ -22,9 +22,6 @@ image.style.*: type: integer uuid: type: string - replacementID: - type: string - label: Replacement style ID image.effect.*: type: mapping diff --git a/core/modules/image/image.install b/core/modules/image/image.install index fd14d03..711dd14 100644 --- a/core/modules/image/image.install +++ b/core/modules/image/image.install @@ -61,3 +61,15 @@ function image_requirements($phase) { return $requirements; } + +/** + * Stores the image style dependencies into form and view display entities. + */ +function image_update_8001() { + $config_factory = \Drupal::configFactory(); + foreach (['form', 'view'] as $context) { + foreach ($config_factory->listAll("core.entity_{$context}_display.") as $name) { + $config_factory->getEditable($name)->save(); + } + } +} diff --git a/core/modules/image/image.services.yml b/core/modules/image/image.services.yml index 2f17bb5..b1f4eac 100644 --- a/core/modules/image/image.services.yml +++ b/core/modules/image/image.services.yml @@ -13,3 +13,8 @@ services: public: false tags: - { name: page_cache_response_policy } + image.keyvalue.memory: + class: Drupal\Core\KeyValueStore\KeyValueMemoryFactory + image.state: + parent: state + arguments: ['@image.keyvalue.memory'] diff --git a/core/modules/image/src/Entity/ImageStyle.php b/core/modules/image/src/Entity/ImageStyle.php index 10eba46..434e570 100644 --- a/core/modules/image/src/Entity/ImageStyle.php +++ b/core/modules/image/src/Entity/ImageStyle.php @@ -53,20 +53,12 @@ * "name", * "label", * "effects", - * "replacementID" * } * ) */ class ImageStyle extends ConfigEntityBase implements ImageStyleInterface, EntityWithPluginCollectionInterface { /** - * The name of the image style to use as replacement upon delete. - * - * @var string - */ - protected $replacementID; - - /** * The name of the image style. * * @var string @@ -129,9 +121,11 @@ public function postSave(EntityStorageInterface $storage, $update = TRUE) { public static function postDelete(EntityStorageInterface $storage, array $entities) { parent::postDelete($storage, $entities); + $state = \Drupal::service('image.state'); foreach ($entities as $style) { // Flush cached media for the deleted style. $style->flush(); + $state->delete("image.style.replacement.{$style->id()}"); } } @@ -373,7 +367,7 @@ public function addImageEffect(array $configuration) { * {@inheritdoc} */ public function getReplacementID() { - return $this->get('replacementID'); + return NULL; } /** diff --git a/core/modules/image/src/Form/ImageStyleDeleteForm.php b/core/modules/image/src/Form/ImageStyleDeleteForm.php index 58f186d..c0092ff 100644 --- a/core/modules/image/src/Form/ImageStyleDeleteForm.php +++ b/core/modules/image/src/Form/ImageStyleDeleteForm.php @@ -11,8 +11,6 @@ use Drupal\Core\Entity\Entity\EntityViewDisplay; use Drupal\Core\Entity\EntityDeleteForm; use Drupal\Core\Form\FormStateInterface; -use Drupal\field\Entity\FieldConfig; -use Drupal\image\Entity\ImageStyle; /** * Creates a form to delete an image style. @@ -20,65 +18,44 @@ class ImageStyleDeleteForm extends EntityDeleteForm { /** - * A list of components using this image style. + * Tell if there are formatters or widget using the image style being deleted. * - * @var array[] + * @var bool */ protected $affectedComponents; /** * {@inheritdoc} - * - * @todo Convert the response to a nice rendered array once - * https://www.drupal.org/node/2575375 gets committed. */ public function getDescription() { - $description = ''; - // There are components relying on the style being deleted. - if ($this->getAffectedComponents()) { - $styles = array_keys(ImageStyle::loadMultiple()); - - // If there are image styles defined, other than the style being deleted, - // show the replacement description. - if (count($styles) > 1) { - $description .= '

' . $this->t("Next components are relying on %style image style. You may select another style to replace it in all components settings. If you don't provide a replacement the components will be disabled. In this case you'll need to revisit the form and view displays an reconfigure the widgets and formatters.", ['%style' => $this->entity->label()]) . '

'; - } - // No possible replacements, announce that the formatters and widgets need - // updating. - else { - $description .= '

' . $this->t("Next components are relying on %style image style and will be disabled. You'll need to revisit the form and view displays an reconfigure the widgets and formatters.", ['%style' => $this->entity->label()]) . '

'; - } - - foreach ($this->getAffectedComponents() as $context => $info) { - if ($info['components']) { - $items = ['#theme' => 'item_list', '#items' => $info['components'], '#title' => $this->t("@label:", ['@label' => $info['label']])]; - $description .= \Drupal::service('renderer')->render($items); - } - } - } - - $description .= '

' . $this->t("All images that have been generated for this style will be permanently deleted.") . '

'; - - return $description; + return $this->t("All images that have been generated for this style will be permanently deleted."); } /** * {@inheritdoc} */ public function form(array $form, FormStateInterface $form_state) { - // Show replacement element only if there are components relying on this - // image style. - if ($this->getAffectedComponents()) { + // If there are components relying on this image style show a warning + // message and, if case, the image style replacement select. + if ($this->hasAffectedComponents()) { $replacement_styles = array_diff_key(image_style_options(), [$this->entity->id() => '']); // If there are non-empty options in the list, allow the user to // optionally pickup a replacement. if (count($replacement_styles) > 1) { + $form['warning'] = [ + '#markup' => $this->t("There are components relying on %style image style. You may select another style to replace it in all components settings. If you don't provide a replacement, those components will be disabled. In this case you'll need to revisit the form and view displays an reconfigure the widgets and formatters.", ['%style' => $this->entity->label()]), + ]; $form['replacement'] = [ '#title' => $this->t('Replacement style'), '#type' => 'select', '#options' => $replacement_styles, - '#empty_option' => $this->t('No replacement, disable widgets and formatters using it'), + '#empty_option' => $this->t('- No replacement (disable widgets/formatters) -'), + ]; + } + else { + $form['warning'] = [ + '#markup' => $this->t("There are components relying on %style image style and will be disabled. You'll need to revisit the form and view displays an reconfigure the widgets and formatters.", ['%style' => $this->entity->label()]), ]; } } @@ -90,61 +67,46 @@ public function form(array $form, FormStateInterface $form_state) { * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { - // If a replacement has been selected, save it in the entity to be used - // later, when resolving dependencies. + // If a replacement has been selected, save it as state to be used later, in + // the same request, when resolving dependencies. if (!empty($replacement = $form_state->getValue('replacement'))) { - ImageStyle::load($this->entity->id()) - ->set('replacementID', $replacement) - ->save(); + \Drupal::service('image.state') + ->set("image.style.replacement.{$this->entity->id()}", $replacement); } parent::submitForm($form, $form_state); } /** - * Returns a list of components that are using the image style being deleted. + * Checks if there are components using the image style being deleted. * - * @return array[] - * An array with the keys 'view' and 'form' each one being an array of - * components that are using this image style in their settings. + * @return bool + * TRUE if at least one component is using the image style, FALSE otherwise. */ - protected function getAffectedComponents() { - if (!isset($this->affectedComponents)) { - $this->affectedComponents = []; - // Merge view and form displays together. Use array_values() to avoid key - // collisions. - $displays = array_merge(array_values(EntityViewDisplay::loadMultiple()), array_values(EntityFormDisplay::loadMultiple())); - /** @var \Drupal\Core\Entity\Display\EntityDisplayInterface $display */ - foreach ($displays as $display) { + protected function hasAffectedComponents() { + if (isset($this->affectedComponents)) { + return $this->affectedComponents; + } + // Merge view and form displays together. Use array_values() to avoid key + // collisions. + $displays = array_merge(array_values(EntityViewDisplay::loadMultiple()), array_values(EntityFormDisplay::loadMultiple())); + /** @var \Drupal\Core\Entity\Display\EntityDisplayInterface $display */ + foreach ($displays as $display) { + $dependencies = $display->getDependencies() + ['config' => []]; + // If this image style is not a dependency of the whole display, do not + // descend into components because component dependencies are part of + // display dependencies. + if (in_array($this->entity->getConfigDependencyName(), $dependencies['config'])) { foreach ($display->getComponents() as $name => $options) { - $context = $display->get('displayContext'); - $component_type = $context == 'view' ? 'image' : 'image_image'; - $setting = $context == 'view' ? 'image_style' : 'preview_image_style'; - if (isset($options['type']) && $options['type'] == $component_type && $options['settings'][$setting] == $this->entity->id()) { - if (!isset($this->affectedComponents[$context])) { - $this->affectedComponents[$context] = [ - 'label' => (string) ($context == 'view' ? $this->t('Formatters') : $this->t('Widgets')), - 'components' => [], - ]; - } - /** @var \Drupal\field\FieldConfigInterface $field_config */ - $field_config = FieldConfig::load("{$display->getTargetEntityTypeId()}.{$display->getTargetBundle()}.$name"); - $entity_type = $this->entityManager->getDefinition($field_config->getTargetEntityTypeId()); - $entity_type_label = $entity_type->getLabel(); - // This entity type provides a bundle entity type. - if ($bundle_entity_type_id = $entity_type->getBundleEntityType()) { - $bundle_label = $this->entityManager->getStorage($bundle_entity_type_id) - ->load($field_config->getTargetBundle())->label(); - } - // A simple bundle with no bundle entity type. - else { - $bundle_label = $field_config->getTargetBundle(); + if ($renderer = $display->getRenderer($name)) { + $plugin_dependencies = $renderer->calculateDependencies() + ['config' => []]; + if (in_array($this->entity->getConfigDependencyName(), $plugin_dependencies['config'])) { + return $this->affectedComponents = TRUE; } - $this->affectedComponents[$context]['components'][$name] = (string) $this->t("@entity: @bundle: @field", ['@entity' => $entity_type_label, '@bundle' => $bundle_label, '@field' => $field_config->getLabel()]); } } } } - return $this->affectedComponents; + return $this->affectedComponents = FALSE; } } diff --git a/core/modules/image/src/ImageStyleInterface.php b/core/modules/image/src/ImageStyleInterface.php index 9507291..fcdd5bd 100644 --- a/core/modules/image/src/ImageStyleInterface.php +++ b/core/modules/image/src/ImageStyleInterface.php @@ -17,8 +17,9 @@ /** * Returns the replacement ID. * - * @return string - * The name of the image style to use as replacement upon delete. + * @return null + * + * @deprecated in 8.0.x, will be removed in 8.1.x. */ public function getReplacementID(); diff --git a/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php index 879bb7f..b7857bb 100644 --- a/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php +++ b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php @@ -245,13 +245,13 @@ public function calculateDependencies() { */ public function onDependencyRemoval(array $dependencies) { $changed = parent::onDependencyRemoval($dependencies); + $state = \Drupal::service('image.state'); /** @var \Drupal\image\ImageStyleInterface $style */ $name = $this->getSetting('image_style'); if ($name && $style = ImageStyle::load($name)) { /** @var \Drupal\image\ImageStyleInterface $removed_style */ if (!empty($dependencies[$style->getConfigDependencyKey()][$style->getConfigDependencyName()])) { - $removed_style = $dependencies[$style->getConfigDependencyKey()][$style->getConfigDependencyName()]; - if ($replacement_id = $removed_style->getReplacementID()) { + if ($replacement_id = $state->get("image.style.replacement.$name")) { /** @var \Drupal\image\ImageStyleInterface $replacement */ if ($replacement = ImageStyle::load($replacement_id)) { $this->setSetting('image_style', $replacement_id); diff --git a/core/modules/image/src/Plugin/Field/FieldWidget/ImageWidget.php b/core/modules/image/src/Plugin/Field/FieldWidget/ImageWidget.php index eb2acbb..9df060c 100644 --- a/core/modules/image/src/Plugin/Field/FieldWidget/ImageWidget.php +++ b/core/modules/image/src/Plugin/Field/FieldWidget/ImageWidget.php @@ -292,14 +292,14 @@ public function calculateDependencies() { */ public function onDependencyRemoval(array $dependencies) { $changed = parent::onDependencyRemoval($dependencies); + $state = \Drupal::service('image.state'); /** @var \Drupal\image\ImageStyleInterface $style */ $name = $this->getSetting('preview_image_style'); if ($name && $style = ImageStyle::load($name)) { /** @var \Drupal\image\ImageStyleInterface $removed_style */ if (!empty($dependencies[$style->getConfigDependencyKey()][$style->getConfigDependencyName()])) { - $removed_style = $dependencies[$style->getConfigDependencyKey()][$style->getConfigDependencyName()]; - if ($replacement_id = $removed_style->getReplacementID()) { - /** @var \Drupal\image\ImageStyleInterface $replacement */ + if ($replacement_id = $state->get("image.style.replacement.$name")) { + /** @var \Drupal\image\ImageStyleInterface $replacement */ if ($replacement = ImageStyle::load($replacement_id)) { $this->setSetting('preview_image_style', $replacement_id); $changed = TRUE; diff --git a/core/modules/image/src/Tests/ImageAdminStylesTest.php b/core/modules/image/src/Tests/ImageAdminStylesTest.php index 8f159a3..ed3854f 100644 --- a/core/modules/image/src/Tests/ImageAdminStylesTest.php +++ b/core/modules/image/src/Tests/ImageAdminStylesTest.php @@ -8,7 +8,6 @@ namespace Drupal\image\Tests; use Drupal\Component\Utility\SafeMarkup; -use Drupal\Core\Entity\Entity\EntityViewDisplay; use Drupal\image\Entity\ImageStyle; use Drupal\image\ImageStyleInterface; use Drupal\node\Entity\Node; @@ -447,11 +446,6 @@ function testConfigImport() { // Copy config to sync, and delete the image style. $sync = $this->container->get('config.storage.sync'); $active = $this->container->get('config.storage'); - // Remove first the entity view display dependency to image style to avoid - // import dependencies validation error. - EntityViewDisplay::load('node.article.default') - ->removeComponent($field_name) - ->save(); $this->copyConfig($active, $sync); $sync->delete('image.style.' . $style_name); $this->configImporter()->import(); diff --git a/core/modules/image/src/Tests/ImageStyleDeleteTest.php b/core/modules/image/src/Tests/ImageStyleDeleteTest.php index a5212d4..e8d835c 100644 --- a/core/modules/image/src/Tests/ImageStyleDeleteTest.php +++ b/core/modules/image/src/Tests/ImageStyleDeleteTest.php @@ -49,13 +49,7 @@ public function testDeletionMessages() { // Replacement select found. $this->assertFieldByName('replacement'); // Message telling about components relying on this image style found. - $this->assertRaw((string) t("Next components are relying on %style image style. You may select another style to replace it in all components settings. If you don't provide a replacement the components will be disabled. In this case you'll need to revisit the form and view displays an reconfigure the widgets and formatters.", ['%style' => 'Style 1'])); - // Check listed components. - $this->assertText("Formatters"); - $this->assertText("Widgets"); - $this->assertText("Content: Basic page: image1"); - $this->assertNoText("Content: Article: image1"); - $this->assertNoText("Content: Article: image2"); + $this->assertRaw((string) t("There are components relying on %style image style. You may select another style to replace it in all components settings. If you don't provide a replacement, those components will be disabled. In this case you'll need to revisit the form and view displays an reconfigure the widgets and formatters.", ['%style' => 'Style 1'])); // The style cache flush text is there. $this->assertText((string) t('All images that have been generated for this style will be permanently deleted.')); @@ -63,13 +57,7 @@ public function testDeletionMessages() { // Replacement select found. $this->assertFieldByName('replacement'); // Message telling about components relying on this image style found. - $this->assertRaw((string) t("Next components are relying on %style image style. You may select another style to replace it in all components settings. If you don't provide a replacement the components will be disabled. In this case you'll need to revisit the form and view displays an reconfigure the widgets and formatters.", ['%style' => 'Style 2'])); - // Check listed components. - $this->assertText("Widgets"); - $this->assertNoText("Formatters"); - $this->assertText("Content: Article: image2"); - $this->assertNoText("Content: Basic page: image1"); - $this->assertNoText("Content: Article: image1"); + $this->assertRaw((string) t("There are components relying on %style image style. You may select another style to replace it in all components settings. If you don't provide a replacement, those components will be disabled. In this case you'll need to revisit the form and view displays an reconfigure the widgets and formatters.", ['%style' => 'Style 2'])); // The style cache flush text is there. $this->assertText((string) t('All images that have been generated for this style will be permanently deleted.')); @@ -77,11 +65,8 @@ public function testDeletionMessages() { // Replacement select not found. $this->assertNoFieldByName('replacement'); // Messages telling about components relying on this image style not found. - $this->assertNoRaw((string) t("Next components are relying on %style image style. You may select another style to replace it in all components settings. If you don't provide a replacement the components will be disabled. In this case you'll need to revisit the form and view displays an reconfigure the widgets and formatters.", ['%style' => 'Style 3'])); - $this->assertNoRaw((string) t("Next components are relying on %style image style and will be disabled. You'll need to revisit the form and view displays an reconfigure the widgets and formatters.", ['%style' => 'Style 3'])); - // There are no listed components. - $this->assertNoText("Formatters"); - $this->assertNoText("Widgets"); + $this->assertNoRaw((string) t("There are components relying on %style image style. You may select another style to replace it in all components settings. If you don't provide a replacement, those components will be disabled. In this case you'll need to revisit the form and view displays an reconfigure the widgets and formatters.", ['%style' => 'Style 3'])); + $this->assertNoRaw((string) t("There are components relying on %style image style and will be disabled. You'll need to revisit the form and view displays an reconfigure the widgets and formatters.", ['%style' => 'Style 3'])); // The style cache flush text is there. $this->assertText((string) t('All images that have been generated for this style will be permanently deleted.')); @@ -96,13 +81,7 @@ public function testDeletionMessages() { // Replacement select not found. $this->assertNoFieldByName('replacement'); // Message telling about components relying on this image style found. - $this->assertRaw((string) t("Next components are relying on %style image style and will be disabled. You'll need to revisit the form and view displays an reconfigure the widgets and formatters.", ['%style' => 'Style 1'])); - // Check listed components. - $this->assertText("Formatters"); - $this->assertText("Widgets"); - $this->assertText("Content: Basic page: image1"); - $this->assertNoText("Content: Article: image1"); - $this->assertNoText("Content: Article: image2"); + $this->assertRaw((string) t("There are components relying on %style image style and will be disabled. You'll need to revisit the form and view displays an reconfigure the widgets and formatters.", ['%style' => 'Style 1'])); // The style cache flush text is there. $this->assertText((string) t('All images that have been generated for this style will be permanently deleted.')); } diff --git a/core/modules/image/src/Tests/Update/ImageUpdateTest.php b/core/modules/image/src/Tests/Update/ImageUpdateTest.php new file mode 100644 index 0000000..5a4427f --- /dev/null +++ b/core/modules/image/src/Tests/Update/ImageUpdateTest.php @@ -0,0 +1,55 @@ +databaseDumpFiles = [ + __DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz', + ]; + } + + /** + * Tests image_update_8001(). + * + * @see image_update_8001() + */ + public function testImageUpdate8001() { + $view = 'core.entity_view_display.node.article.default'; + $form = 'core.entity_form_display.node.article.default'; + + // View display 'node.article.default' doesn't depend on style 'large'. + $dependencies = $this->config($view)->get('dependencies.config'); + $this->assertFalse(in_array('image.style.large', $dependencies)); + // Form display 'node.article.default' doesn't depend on style 'thumbnail'. + $dependencies = $this->config($form)->get('dependencies.config'); + $this->assertFalse(in_array('image.style.thumbnail', $dependencies)); + + // Run updates. + $this->runUpdates(); + + // View display 'node.article.default' depend on style 'large'. + $dependencies = $this->config($view)->get('dependencies.config'); + $this->assertTrue(in_array('image.style.large', $dependencies)); + // Form display 'node.article.default' depend on style 'thumbnail'. + $dependencies = $this->config($view)->get('dependencies.config'); + $this->assertTrue(in_array('image.style.large', $dependencies)); + } + +} diff --git a/core/modules/image/tests/src/Kernel/ImageStyleIntegrationTest.php b/core/modules/image/tests/src/Kernel/ImageStyleIntegrationTest.php index d977e98..c6d9280 100644 --- a/core/modules/image/tests/src/Kernel/ImageStyleIntegrationTest.php +++ b/core/modules/image/tests/src/Kernel/ImageStyleIntegrationTest.php @@ -77,22 +77,24 @@ public function testEntityDisplayDependency() { ])->setComponent('sticker', ['settings' => ['preview_image_style' => 'main_style']]); $form_display->save(); - // The entity view and form displays exists before dependency removal. + // Check that the entity displays exists before dependency removal. $this->assertNotNull(EntityViewDisplay::load($view_display->id())); $this->assertNotNull(EntityFormDisplay::load($form_display->id())); // Delete the 'main_style' image style. Before that, emulate the UI process - // of selecting a replacement style by setting the property and saving. - $style->set('replacementID', 'replacement_style')->save(); + // of selecting a replacement style by setting the replacement image style + // ID in the state. + $state = $this->container->get('image.state'); + $state->set('image.style.replacement.main_style', 'replacement_style'); $style->delete(); - // The entity view and form displays exists after dependency removal. + // Check that the entity displays exists after dependency removal. $this->assertNotNull($view_display = EntityViewDisplay::load($view_display->id())); $this->assertNotNull($form_display = EntityFormDisplay::load($form_display->id())); - // The 'sticker' formatter component exists in both displays. + // Check that the 'sticker' formatter component exists in both displays. $this->assertNotNull($formatter = $view_display->getComponent('sticker')); $this->assertNotNull($widget = $form_display->getComponent('sticker')); - // Both displays are using now 'replacement_style' to show images. + // Check that both displays are using now 'replacement_style' for images. $this->assertSame($formatter['settings']['image_style'], 'replacement_style'); $this->assertSame($widget['settings']['preview_image_style'], 'replacement_style');