Boston
Account created on 4 June 2009, about 15 years ago
#

Merge Requests

Recent comments

🇺🇸United States amaisano Boston

Having the same problem with D10 Core Moderation module "Moderated Content" view, which is a Views revisions table. Same error.

🇺🇸United States amaisano Boston

Besides the Array to String conversion, this patch also is unable to accept custom drush migrate:import parameters:

AWS:[wwwdev@mkt-qal-drupal01 src]$ drush mim --tag=node_translation --continue-on-failure --language=es,it --batch-size=1000
[warning] Array to string conversion Process.php:341
> [error] Migration failed with source plugin exception: The "language" option does not exist. in /data/3ds/swd10/releases/4.19.12-RC/src/vendor/symfony/console/Input/Input.php line 140

I assume this will also affect the --migrate-debug flag.

These are both added using official drush extension setups:

services:
  sw_migrate.commands:
    class: \Drupal\sw_migrate\Commands\SwMigrateCommands
    tags:
      - { name: drush.command }
<?php

namespace Drupal\sw_migrate\Commands;

use Consolidation\AnnotatedCommand\CommandData;
use Drush\Commands\DrushCommands;

/**
 * Extends migrate-import with a language-filtering option.
 */
class SwMigrateCommands extends DrushCommands {

  /**
   * Register new options for the migrate:import command.
   *
   * @hook command migrate:import
   * @option language Enable language filtering
   * @usage drush migrate-import --language=fr,it
   *   Run translation-based migration(s) in the specified languages only.
   */
  public function additionalOptionsMigrateImport(CommandData $commandData, $options = ['language' => NULL]) {
    // No action required here. The new options will be examined during the
    // source plugin if required.
  }

}

... so I am surprised that adding the --batch-size parameter is suddenly ignoring them?

🇺🇸United States amaisano Boston

What's disappointing? emixaam's comment seems ideal, no?

I found that the `--force` command does not mix well with `--batch-size` either... it fails all migrations if their dependencies are not fulfilled. If I run a migration without --batch-size using --force, it works as expected, ignoring the dependencies.

🇺🇸United States amaisano Boston

FYI - this patch does NOT fix the From Library paragraph type label. It continues to be in the content's language (FR), even though we've set our preferences to use EN for all UI.

Add [FR word] is on the dropdown, and the [FR word] is shown to the left of the summary, instead of "From Library" on /fr/node/2/edit with ALL other UI elements being in EN.

🇺🇸United States amaisano Boston

Not sure if this is 100% the same problem, but I solved this by completely rejecting migration mappings from entering the database if arbitrary criteria was not met. I did this by extending the skip_on_empty plugin to what I call exclude_on_empty.

class ExcludeOnEmpty extends SkipOnEmpty {

  /**
   * Excludes the current row when value is not set.
   *
   * {@inheritDoc}
   */
  public function row($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
    try {
      return parent::row($value, $migrate_executable, $row, $destination_property);
    }
    catch (MigrateSkipRowException $e) {
      throw new MigrateSkipRowException($e->getMessage(), FALSE);
    }
  }

}
🇺🇸United States amaisano Boston

Secondary thought - I just had a use case of running everything EXCEPT. So some kind of negation would be a cool bonus feature:

drush mim --all --tag=!translation #import everything except migrations tagged "translation"

🇺🇸United States amaisano Boston

Here is the parser code plugin we made that handles custom/dynamic/multiple source urls:

https://www.drupal.org/project/migration_jsonapi

🇺🇸United States amaisano Boston

Found a quick, custom plugin based solution thanks to author's help:


namespace Drupal\sw_migrate\Plugin\migrate_conditions\condition;

use Drupal\migrate\MigrateException;
use Drupal\migrate_conditions\Plugin\SimpleComparisonBase;

/**
 * Alternate to migrate_conditions matches condition.
 *
 * Allows dynamic regex to be passed into regex() operator
 * via source and/or _pseudo_field properties.
 *
 * @MigrateConditionsConditionPlugin(
 *   id = "regex",
 *   parens = "property"
 * )
 */
class Regex extends SimpleComparisonBase {

  /**
   * {@inheritdoc}
   */
  protected function compare($source, $value) {
    if (is_string($source) && !is_null($value)) {
      $regex = '/' . $value . '/';
      return (bool) preg_match($regex, $source);
    }
    else {
      throw new MigrateException('When using the regex condition, the source must be a string.');
    }
  }

}

🇺🇸United States amaisano Boston

Also, sub-folders under migrate_shared_configuration/ would be nice:

- migrate_shared_configuration/node/bundle_1.yml
- migrate_shared_configuration/node/bundle_2.yml
- migrate_shared_configuration/term/vocab_1.yml
🇺🇸United States amaisano Boston

It would be nice if you could still group multiple inside a single YML file, especially when you have multiple override "snippets" that are only a few lines -- creating a separate file for all of them is a bit overwhelming.

defaults.yml
- include1:
-   source:
-     constants:
-       a: 1
- include2:
-   source:
-     constants:
-       b: 2
- include3:
-   source:
-     constants:
-       c: 3

big_complex_field.yml
- process:
-   field_paragraphs:
-      plugin: ...   
🇺🇸United States amaisano Boston

Here's my take on this, in a custom plugin:

<?php

declare(strict_types = 1);

namespace Drupal\sw_migrate\Plugin\migrate\process;

use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Row;
use Drupal\migrate_plus\Plugin\migrate\process\EntityLookup;

/**
 * This plugin updates existing entities within the process plugin.
 *
 * All the configuration from the generate plugin applies here.
 *
 * Available configuration keys:
 * - langcode: (optional) If set, update translations instead of base entity.
 *
 * Example:
 * @code
 * destination:
 *   plugin: 'entity:node'
 * process:
 *   field_tags:
 *     plugin: entity_update
 *     source: '@_parent_id'
 *     entity_type: paragraphs_library_item
 *     langcode: '@_translation'
 *     value_key: id
 *     values:
 *       label: title
 * @endcode
 *
 * @see \Drupal\migrate_plus\Plugin\migrate\process\EntityLookup
 *
 * @MigrateProcessPlugin(
 *   id = "entity_update"
 * )
 */
class EntityUpdate extends EntityLookup {

  /**
   * The current row.
   *
   * @var \Drupal\migrate\Row|null
   */
  protected ?Row $row = NULL;

  /**
   * {@inheritdoc}
   */
  public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
    $this->row = $row;

    $result = parent::transform($value, $migrate_executable, $row, $destination_property);

    return $this->updateEntity($result, $value);
  }

  /**
   * Update an entity for a given target.
   *
   * @param string $target_id
   *   The id of the entity.
   * @param mixed $value
   *   The entity values.
   *
   * @return int|string
   *   The entity id of the generated entity.
   */
  protected function updateEntity($target_id, $value) {
    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
    $entity = $this->entityTypeManager
      ->getStorage($this->lookupEntityType)
      ->load($target_id);

    $entity_values = $this->configuration['values'];

    // Support for plugin-level translation config:
    $translation = FALSE;
    $langcode = $this->configuration['langcode'] ?? NULL;

    if ($langcode) {
      // If langcode is present in plugin config, expand it:
      $langcode = $this->row->get($langcode);

      // Unless value is false, get the language translation:
      if ($langcode !== FALSE) {
        $translation = TRUE;
      }
    }

    if ($translation) {
      foreach ($entity_values as $key => $value) {
        $value = $this->row->get($value);
        $entity->getTranslation($langcode)->set($key, $value);
      }
    }
    else {
      foreach ($entity_values as $key => $value) {
        $value = $this->row->get($value);
        $entity->set($key, $value);
      }
    }

    $entity->save();

    return $entity->id();
  }

}

🇺🇸United States amaisano Boston

So cross-module use of the shared_configuration YAML data is possible w/o this patch? Like so:

/my-migrations
--/modules
--/--/node_migration
--/--/--/node_migration.migrate_shared_configuration.yml (with a "defaults" include definition)
--/--/term_migration
--/--/--/term_migration.migrate_shared_configuration.yml (with a "taxonomy" include definition)
--/--/misc_migration
--/--/--/migrations
--/--/--/--/misc_migration.yaml (with contents below)

# Misc Migration
id: misc_migration
includes:
  - defaults
  - taxonomy
source: ...

?

🇺🇸United States amaisano Boston

Does this only break it up within the current module, or can it be made so that we can reference different module's shared migration file(s)? Would be nice to have cross-module migration shared yml files (like a family of sub modules where the parent module has a common set of migrations, and the sub modules that each deal with different parts of the migration have more specific shared migration).

🇺🇸United States amaisano Boston

We need this.

The use case is simple and surprisingly necessary:

We ONLY want "reusable" paragraphs to be used in the ERR field. There should be no "direct" ERR (non From Library type) paragraphs directly attached to the node.

It's impossible to do this without allowing ALL paragraph types, which completely confuses our contributors who have added things directly and should not be able to.

I believe this is a regression from how this worked in D8, but I could be wrong.

🇺🇸United States amaisano Boston

FYI I've made adjustments to this patch as a custom plugin in my repo, and made it support entity translation updates, too. Thanks.

🇺🇸United States amaisano Boston

Agree - the latest version of this at time of writing still does not allow adding/removing/reordering of paragraphs fields on any translation until you both 1) enable this asymm module AND 2) mark all your paragraphs fields as translatable (ignoring warnings). The description of this on the project page STILL does not say you need to do this - it just says:

> To enable the functionality for the stable widget for paragraphs simple install this module.

Only under the "if you want to use with classic/legacy, do these things" is this information supposedly required. But in reality, you need to follow steps 1-3 for both widget types.

Unless there is something we are all missing?

🇺🇸United States amaisano Boston

We wrote a similar plugin extension that provides a list of URLs back to the main source plugin. There hasn't been any conflict with # of items found vs. imported vs. any other status - it just works like normal.

We've extended the the migrate_plug\data_parse\JSON plugin, and altered the nextSource() method to build this list.

We are crawling a JSON:API in our custom plugin until we find no more "next-page" attributes in the JSON response, and then compile the full list of URLs as basically a bunch of offset page requests.

I can share the code if anyone is interested.

🇺🇸United States amaisano Boston

Patch #122 is _not_ enough on its own to restore the ability to add/remove/drag paragraphs field items around per translation. In fact I'm not sure what it means to accomplish. Using just D10 + Paragraphs 1.6 + Content Translation (and marking all fields as translatable) results in no visible difference in user experience.

Is it meant to be used in combination with other patches/modules/versions?

Patch #128 works essentially the same as the paragraphs_asymmetric_translation_widgets module. It restores all edit functions to translated node paragraph fields. Both 128 and the spin-off module work on their own.

Also, none of these solutions work with TMGMT jobs. Because ¶s are cloned, they never get the accepted translations from a job. They just get the base values copied from the base translation.

🇺🇸United States amaisano Boston

I am using D10, Paragraphs 1.16, TMGMT, and cannot get the accepted job translations for paragraph fields into the node translation when it gets created. Or even on updates. Basically, the same as the screenshot in this issue.

I've enabled translation on the node's "field_paragraphs" field, AND all the fields for my paragraph types.

I'm tried the non-patched paragraphs_asymmetric_translation_widgets module, and also with both patches you mentioned in #7 (2).

I've also tried patch 122 and 128 on the original Paragraphs Experimental issue for asymmetry, same results.

Is there another step I could be missing? Does this not work with D10? All of the patches applied cleanly.

There are recent comments in this issue that claim it all works perfectly, but I might not have all the details: https://www.drupal.org/project/tmgmt/issues/3134922#comment-15169735 Integration with paragraphs asymmetric translation RTBC

🇺🇸United States amaisano Boston

I am using D10, Paragraphs 1.16, TMGMT, and cannot get the accepted job translations for paragraph fields into the node translation when it gets created. Or even on updates.

I've enabled translation on the node's "field_paragraphs" field, AND all the fields for my paragraph types.

I'm using non-patched paragraphs_asymmetric_translation_widgets module, and this patch.

I've also tried patch 122 and 128 on the original Paragraphs Experimental issue for asymmetry, same results.

Is there another step I could be missing? Does this not work with D10? All of the patches applied cleanly.

🇺🇸United States amaisano Boston

Fork-based patch.

🇺🇸United States amaisano Boston

amaisano made their first commit to this issue’s fork.

🇺🇸United States amaisano Boston

I am experiencing this issue but with a slightly different context.

None of the patches here worked.

Similar to https://www.drupal.org/project/drupal/issues/3026055 (no solution), I have a taxonomy term in English that has a File (Image) field that has a value for it. In my content translation settings, I have the term's Image field marked as translatable because we need to alter the ALT text for it per language, but the "File" attribute is UNchecked, because we don't need a different image per language.

On the French translation of this term, I cannot save any changes due to the "Non-translatable field elements can only be changed when updating the original language" error.

If I go back to the English version and remove the image (and save the English version), then I can once again make changes to fields and save the French translation without problems.

🇺🇸United States amaisano Boston

@danflanagan8 that seems to be working! What a nice workaround. Thank you.

Snippet YML:

-
  plugin: sub_process
  source: bricks
  process:
    _stub:
      plugin: switch_on_condition
      source: type
      .....

--migrate-debug output:

...
    13 => []
    14 => array:3 [
      "_stub" => array:2 [
        0 => "227"
        1 => "274"
      ]
      "target_id" => "227"
      "target_revision_id" => "274"
    ]

It's interesting how the output values contain the pseudo_field, which usually isn't present in the output for Destinations. It has no effect on the import thankfully.

🇺🇸United States amaisano Boston

This doesn't work with sub_process plugins. The snippet is sending the 1st item in the array rather than the whole array to the separate file.

🇺🇸United States amaisano Boston

Sorry for being so vague. I've used Groups - may not be everyone's experience - to achieve a full asymmetric translation ecosystem in Drupal.

We created a different group for each of the "markets" or languages. One for EN-US, one for EN-UK, one for PT-BR, etc.

When you enable groups, and then allow group_content to do its magic, you can basically assign a non-group node to one, or many groups.

For instance, you create an article node. You can do this outside of groups, but with Groups you'd go "into" your market language group, and create the article. That article automatically gets created as a normal Drupal node, but a group_content wrapper entity is also created for it, which is unique to that group.

So Node 1 is now assigned to EN-UK. If you want, you can _also_ assign Node 1 to EN-US. This is essentially saying you want to use the same "translation" for both languages.

What's neat however is that you can field the group_content entity, and _any values present in that group_content entity_ are unique per group aka language. That is where you get your asymmetry from. You can have fields that are always the same per group at the node level, and fields that are unique to each language at the group_content level.

If DE-DE wants to translate a US article, as long as the copy you want to translate is in the group_content layer for that node article bundle wrapper (each node bundle has its own group_content bundle) you simply assign the article node to DE-DE, and you can enter your own unique DE-DE translation values, AND paragraphs, and entities, and anything else. The "source" node is still article 1.

When you add language negotiation to this, you can start assigning different languages to different groups, which allows the path alias to route you to the correct language of an article.

Of course, this (and your module too), doesn't 'quite work with the typical TMGMT workflow or even content translation workflow that comes with Drupal. You are essentially creating a different piece of content per language, and the group router/controller is in charge of showing you the matching group_content for a node depending on your current language, which is what I see this module doing.

Now, if your module still allows for a TMGMT job to request and perform translations, then that would be a HUGE advantage over something more manual like groups, so please let me know if I'm missing something.

🇺🇸United States amaisano Boston

Question: Do custom plugins (like the parser plugin being described here), need to implement something specific to be 100% compatible with the shared configuration system? It could be something custom in that code could be throwing this off? I can share the code if desired.

🇺🇸United States amaisano Boston

Right. The command --migrate-debug command works fine for the migrate:import command. --migrate-debug-pre doesn't seem to be doing anything however.

I was hoping to see debug output BEFORE doing the import, which is why i hoped the README was right about the migrate:status be compatible with the debug command, so it can show me "all that it sees" prior to performing the import, which I think was the original idea.

🇺🇸United States amaisano Boston

I'm using the brand new release for D10 that came out today and am getting these errors as well. Using Drush 11.x.

🇺🇸United States amaisano Boston

FYI it seems I did not commit - and lost - my additional changes/fixes for my patch.

We're going to have trouble adding the field initially and make changes to a new field without those edits.

I'll try and re-create.

I had also expanded/fixed the listing of fields that can be overwritten.

🇺🇸United States amaisano Boston

Updated to restore weight

🇺🇸United States amaisano Boston

Patch generated from latest merge request commit attached.

🇺🇸United States amaisano Boston

I have some code to help for this. Updating base branch and forking.

🇺🇸United States amaisano Boston

amaisano made their first commit to this issue’s fork.

Production build 0.69.0 2024