We need to port the D7 feeds_tamper Absolute URL plugin to D8 tamper.

Issue fork tamper-2976171

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

jamesdixon created an issue. See original summary.

jamesdixon’s picture

jamesdixon’s picture

Status: Active » Needs work
StatusFileSize
new6.49 KB

Here's a good start for this plugin. I need to continue by making the Drupal getUri call use a container as was done in another plugin here so the tests will work.

jamesdixon’s picture

StatusFileSize
new7.38 KB

The trouble I'm running into with this plugin, is the old plugin grabbed the Feeds URL from $result->link.

I don't believe feeds has a link like this anymore to grab the URL pieces from?

megachriz’s picture

Status: Needs work » Postponed

$result_link

Yes, I believe that $result->link is gone. In D7, only some parsers set this value, like the syndication parser for example:

class FeedsSyndicationParser extends FeedsParser {
  public function parse(FeedsSource $source, FeedsFetcherResult $fetcher_result) {
    // (...)
    $result->link = $feed['link'];

In the D7 of Feeds extensible parsers it was taken from the fetcher's config:

abstract class FeedsExBase extends FeedsParser {
  public function parse(FeedsSource $source, FeedsFetcherResult $fetcher_result) {
    // (...)
    // Set link.
    $fetcher_config = $source->getConfigFor($source->importer->fetcher);
    $result->link = isset($fetcher_config['source']) && is_string($fetcher_config['source']) ? $fetcher_config['source'] : '';

The CSV parser for example did *not* set a link.

I think $result->link was removed in D8 because there was no guarantee that it was set. The change was made in the following commit: https://cgit.drupalcode.org/feeds/commit/?h=8.x-3.x&id=da3b00d0b1817b846...
In that commit you see the following change:

-    $result->title = $channel->getTitle();
-    $result->description = $channel->getDescription();
-    $result->link = $channel->getLink();
+    $result->set('feed_title', $channel->getTitle())
+           ->set('feed_description', $channel->getDescription())
+           ->set('feed_url', $channel->getLink());

Plan of action

I think it would be best if this plugin would allow the user to select a source where the source's base url is coming from. For the syndication parser, it would be logical that it would come from 'feed_url', but for other parsers the base url may be stored on a different source column. This means that just like #2976172: Copy Plugin and #2976180: Rewrite Plugin this issue is postponed on #2971881: Plugins that depend on a tamperable item need to know the available properties.

No Request object

Using a Request object in this plugin makes no sense, I think. As I see it, the plugin is meant to make absolute urls from relative urls provided by the source. For that, the base url from the source needs to be taken. A Request object contains url information from the Drupal site you are importing into. That's the destination, not the source.

jamesdixon’s picture

Cool thanks for providing context about why that $result->link was removed, and a clear path forward. Glad I didn't put too much time into working with Request. We will sort out #2971881: Plugins that depend on a tamperable item need to know the available properties before tackling this.

jamesdixon’s picture

StatusFileSize
new8.4 KB
new1.51 KB

I started adding the code to accept a source for the base URL. Not sure if my description is correct or not, but we can clean that up in review.

The source column name which holds the base URL. If you had a CSV source with a column header of base_url, then you would include base_url here as an example.  The value in the source column should not include a trailing slash. For example: https://someurlofyourchoice.com

I still need to remove the request object and update the code to work with the new source option. Then clean up the test.

I also need to add form submission code, but I can rip this patch off outside of the config values that aren't needed here:

https://www.drupal.org/files/issues/2020-09-21/copy-plugin-tamper-297617...

jamesdixon’s picture

Status: Postponed » Needs review
StatusFileSize
new10.29 KB
new7.06 KB

More progress!

This patch requires the TamperItem I created in the Copy plugin, which I added to this patch also:

https://www.drupal.org/project/tamper/issues/2976172

Copy has not yet been committed. This means whichever plugin that is committed first between Copy and AbsoluteUrl needs to include the TamperItem. The second plugin that is committed should have the TamperItem code removed from the patch, as it will already be in the code base from the first plugin that was committed.

I'm not sure if this is going to pass. I couldn't test locally as I'm having a hard time figuring out how to install the php-xml package on lando. It might be called something else now. Here are reference links for me to debug later:

https://docs.lando.dev/guides/installing-php-extensions-on-lando.html#_1...

https://stackoverflow.com/questions/14395239/class-domdocument-not-found

It might actually be called php-dom on my lando build.

Status: Needs review » Needs work

The last submitted patch, 8: absoluteurlplugin-2976171-8.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

jamesdixon’s picture

StatusFileSize
new68.37 KB

Shoot getting the same issue where DomDocument isn't available from Drupal test bot.

I need to look into how to get this dependency included in the Tamper plugin.

Also for my notes the php-xml package isn't available in Lando, so it must be something else. It said so in the rebuild step.

jamesdixon’s picture

Hmm if the Drupal test bot is failing on DomDocument I am wondering if we can't rely on that dependency using the Drupal test bot.

https://stackoverflow.com/questions/46179814/class-domdocument-not-found...

This suggests the fix is simply to install the php-xml or php-dom package on the server that needs it.

DomDocument was included in the Drupal 7 feeds_tamper module and I'm assuming the tests included for AbsoluteUrl passed at one point for that module.

megachriz’s picture

@jamesdixon
DOMDocument is a global class. And with the Tamper plugin code, you are in a namespace.

To use global classes in namespaced classes, prepend them with a backslash:

$dom = new \DOMDocument();
megachriz’s picture

I’ve no experience with Lando, but I’m used to that php-xml is installed by default on PHP builds that I install. Php-xml is definitely available for the testbot, else Feeds Extensible Parsers tests wouldn’t pass on the testbot. Feeds Extensible Parsers has a Xpath parser which makes use of DOMDocument. And Feeds tests probably wouldn’t pass either, see feeds/src/Component/XmlParserTrait.php.

On some machines I installed PHP by using Acquia Dev Desktop.
On others I installed PHP using homebrew: brew install php@7.4

I understand from using Docker/Lando it’s easier to switch PHP versions. And that it is easier to switch other components as well, like MySQL.

jamesdixon’s picture

Thank you! I believe it's the namespace issue, thanks for catching that.

jamesdixon’s picture

Status: Needs work » Needs review
StatusFileSize
new11.01 KB
new5.9 KB

Thanks for the assist @megachriz.

Also as suggested I broke this down into using a data provider for the big list of tests that used the same plugin configuration.

Tests are passing locally for me now.

jamesdixon’s picture

StatusFileSize
new11.03 KB
new1.38 KB
new38.34 KB

I manually tested and found:

1) I had to update the code for the options in the form, as it wasn't saving properly. Copy plugin will likely suffer from the same issue
2) We weren't able to get the source for the URL as "source" is an already defined tamper variable.
3) getSourceProperty() doesn't exist in FeedsItems so I had to use getSource() instead.
4) cosmetic form description issues were fixed

Let's see if this one passes.

This is also tricky now because the source for the base URL isn't defined unless you set it on the mapping screen. I just used log message, but that's kinda hacky.

w00t

This is where that dummy target could come in handy. I'm not sure how this was set up in Drupal 7.

Status: Needs review » Needs work

The last submitted patch, 16: absoluteurlplugin-2976171-16.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

jamesdixon’s picture

Status: Needs work » Needs review
StatusFileSize
new11.03 KB
new279 bytes

Forgot to update schema. Lets see if this fixes the issue with the tests.

jamesdixon’s picture

StatusFileSize
new111.8 KB

@megachriz helped me find what the issue with getSourceProperty() is.

Now this task is blocked by

#3182240: TamperableFeedItemAdapter getSourceProperty() doesn't return anything

jamesdixon’s picture

StatusFileSize
new11.01 KB
new444 bytes

Tests pass but this patch I'm posting isn't actually working properly yet because of #3182240: TamperableFeedItemAdapter getSourceProperty() doesn't return anything.

Working on fixing that first. Afterwards manually testing this patch should work.

The only change is using getSourceProperty() method instead of getSource() directly on the Item.

jamesdixon’s picture

jamesdixon’s picture

#3182240: TamperableFeedItemAdapter getSourceProperty() doesn't return anything now has a patch which should be committed to feeds_tamper soon. Until it's committed the Absolute Url Tamper plugin depends on that patch.

Now we have the issue where there's not a proper dummy target to assign the absolute Url base Url setting.

I think before the Url itself would be pulled from the Feed but we don't have that option anymore because Tamper is decoupled from Feeds.

In order to choose a source for the base Url that source needs to be mapped for something or else the source won't appear in the list of options for the plugin configuration.

What I did to get around this was map to a log but this isn't really a clean solution.

I'm wondering if using a dummy target like we had in Feeds D7 would work, or if we need to update Tamper to check for more sources that may exist in the source data that aren't mapped.

megachriz’s picture

Good work! Here is my review.

  1.   +++ b/src/Plugin/Tamper/AbsoluteUrl.php
      @@ -0,0 +1,226 @@
      +  // :TODO: fix this up, we maybe should be using getBaseUrl instead
      +  // https://api.drupal.org/api/drupal/vendor%21symfony%21http-foundation%21Request.php/function/Request%3A%3AgetBaseUrl/8.2.x
      +  /**
      +   * Holds the Request object we use to grab the url info.
      +   *
      +   * @var \Drupal\Core\Lib\Request
      +   */
      +  protected $request;
      +
      

    I guess that this property can be removed. It doesn't seem to be used anymore.

  2.   +++ b/src/Plugin/Tamper/AbsoluteUrl.php
      @@ -0,0 +1,226 @@
      +    $config[self::SETTING_SOURCE] = 'source';
      

    Why does this default to 'source'? I think the default should be NULL.

    I also got the "An illegal choice has been detected." when trying to add a Tamper plugin without selecting a source.

  3.   +++ b/src/Plugin/Tamper/AbsoluteUrl.php
      @@ -0,0 +1,226 @@
      +    $form[self::SETTING_SOURCE] = [
      +      '#type' => 'radios',
      +      '#title' => $this->t('Source'),
      +      '#options' => $replace,
      +      '#default_value' => $this->getSetting(self::SETTING_SOURCE),
      +      '#description' => $this->t('The source column name which holds the base URL. If you had a CSV source with a column header of base_url, then you would include base_url here as an example.  The value in the source column should not include a trailing slash. For example: https://someurlofyourchoice.com'),
      +    ];
      

    I think that this setting should be required.

  4.   +++ b/src/Plugin/Tamper/AbsoluteUrl.php
      @@ -0,0 +1,226 @@
      +    static $dom;
      ...
      +    if (!isset($dom)) {
      +      $dom = new \DOMDocument();
      +    }
      

    Not sure if this is wrong, but what's the reason of keeping the DOMDocument in memory? The new HTML is loaded sometime later anyway:
    $dom->loadHTML($data);

  5.   +++ b/src/Plugin/Tamper/AbsoluteUrl.php
      @@ -0,0 +1,226 @@
      +    // Supress warnings for invalid HTML.
      

    Minor: 'Suppress' is with double 'P'. (I saw the spelling error marked in my editor.)

  6.   +++ b/src/Plugin/Tamper/AbsoluteUrl.php
      @@ -0,0 +1,226 @@
      +  /**
      +   * Convert URL to absolute.
      +   */
      +  protected function convertAbsoluteUrl($r_url, &$urls, $b) {
      

    Describe the parameters in the function docblock.

  7.   +++ b/src/Plugin/Tamper/AbsoluteUrl.php
      @@ -0,0 +1,226 @@
      +  /**
      +   * Join parts of the URL together.
      +   */
      +  protected function joinUrl($parts) {
      

    Describe the parameter in the function docblock. Also, I assume $parts is always an array, so a typehint "array" could be added to the parameter.

  8. If I use 'drupal.org' as base url source, I'll get the following errors:

    Notice: Undefined index: scheme in Drupal\tamper\Plugin\Tamper\AbsoluteUrl->convertAbsoluteUrl() (line 142 of /Users/youri/Sites/devdesktop/drupal8/modules/wip/feeds8/tamper/src/Plugin/Tamper/AbsoluteUrl.php).

    Notice: Undefined index: host in Drupal\tamper\Plugin\Tamper\AbsoluteUrl->convertAbsoluteUrl() (line 149 of /Users/youri/Sites/devdesktop/drupal8/modules/wip/feeds8/tamper/src/Plugin/Tamper/AbsoluteUrl.php).

    Maybe the plugin should automatically add 'http' if it is missing. Good to add test coverage for this case.

  9. I get the same errors when leaving base_url empty. It would be good to add a test case for if base_url is empty.
  10. If $site_url is an array, for example $site_url = ['https://drupal.org'];, I get the following error:

    Warning: parse_url() expects parameter 1 to be string, array given in Drupal\tamper\Plugin\Tamper\AbsoluteUrl->tamper() (line 83 of /Users/youri/Sites/devdesktop/drupal8/modules/wip/feeds8/tamper/src/Plugin/Tamper/AbsoluteUrl.php).

    If it's an array, I think it would be a good idea to pick the first item of it. And the code should check if the site url is a string. If not, it should abort the operation, I think.

    Needs tests.

  11. I think it would be good to add a test case that the plugin is not touching links that are already absolute:
    'urls that are already absolute' => [
      '<a href="https://www.example.com/foo">bar</a>',
      '<a href="https://www.example.com/foo">bar</a>',
    ],
    
  12. Suggestion: the test method testAllTheThingsAbsolute() could have a third parameter in which you pass the base url. It might be easier this way to add more cases where the base url differs.
  13. There are a few coding standards issues. See https://www.drupal.org/pift-ci-job/1880908
jamesdixon’s picture

Thanks for the thorough review @megachriz!

This will be an improvement on the Drupal 7 test coverage and documentation that existed.

I plan on working on this in the week.

megachriz’s picture

Status: Needs review » Needs work
+++ b/src/Plugin/Tamper/AbsoluteUrl.php
@@ -0,0 +1,226 @@
+    foreach ($this->sourceDefinition->getList() as $source) {
+      $replace[$source] = $source;
+    }

Just as for the "Copy" plugin, this should display the source's labels, but store the keys:

foreach ($this->sourceDefinition->getList() as $key => $label) {
  $sources[$key] = $label;
}

Change from #3185025: Pass source labels to a Tamper source definition is required to see the difference in the Feeds Tamper UI.

jamesdixon’s picture

Thanks for the detailed feedback @megachriz.

I have attempted to create a forked branch and push my changes there. I am still learning the fork method, and am not sure how to see if tests are passing, code standards are observed etc using this method. I will have to read up on that soon.

There is progress but I still must:

Add tests for points 8,9,10, and 11 which were recommended in comment #23: https://www.drupal.org/project/tamper/issues/2976171#comment-13902887

I need to address items 12, and 13 from comment #23.

jamesdixon’s picture

Status: Needs work » Needs review

I refactored the testAllThingsAbsolute() function to include a 3rd parameter for base url, fixed coding issues and style issues.

Let's run the merge request and see if it passes. All of @megachriz's feedback has been addressed.

The last submitted patch, 3: absoluteurlplugin-2976171-3.patch, failed testing. View results

The last submitted patch, 4: absoluteurlplugin-2976171-4.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

The last submitted patch, 7: absoluteurlplugin-2976171-6.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

megachriz’s picture

Status: Needs review » Needs work

@jamesdixon
Good work! I added a new review. :) I only reviewed the code, I did not try the new code yet.

There are also still code style issues to be fixed as can be seen on https://www.drupal.org/pift-ci-job/1958675.

jamesdixon’s picture

Status: Needs work » Needs review

Thanks MegaChriz. I worked through your feedback. Looking better!

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

ericgsmith’s picture

Status: Needs review » Needs work

Gave this a rebase and review - still a bit more work needed

megachriz’s picture

Status: Needs work » Needs review

I hope I've addressed all the issues. Will look again later.

megachriz’s picture

I've checked again. I added code to support base urls that do not include the scheme. So previously, "example.com" was marked as incorrect, because parse_url() does not see that as a url that contains a domain name. With the regular expression /^([^\/]+\.[a-z]+)\//i, the code can accept "example.com" as a base url. Tests cases are added for this and it is checked that "www.example.com", "example.com/cat" and "example.com/foo.bar" are correctly parsed as well.

There is one remaining unresolved thread and that is that there is a inconsistency with other Tamper plugins in that it tries to convert the incoming data to a string, where other plugins would throw a TamperException. I'm not sure if that is a problem or if it is worth it to let this issue hang because of that. In my opinion, at least in the context of Feeds, I think it is a good thing if the Tamper plugin tries to be "friendly" and just see if it can use the data. Perhaps the other Tamper plugins need to be changed to become friendlier to or maybe there should exist a strict mode if it is important that the source data is of the correct data type. Changing other Tamper plugins are out of scope for this issue, however.

I think that this is ready to go, although it is possible that a small change is needed after #3332785: Improve handling of empty data is resolved.

ericgsmith’s picture

Status: Needs review » Reviewed & tested by the community

Thanks @megachriz

Changes and comments all make sense to me - looks good!

  • megachriz committed aa5bc58d on 8.x-1.x authored by jamesdixon
    Issue #2976171 by jamesdixon, megachriz, ericgsmith: Ported Absolute URL...
megachriz’s picture

Status: Reviewed & tested by the community » Fixed

Thanks for reviewing @ericgsmith! I merged the code.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.