Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate CiviMail "extern" scripts to conventional routes #17312

Merged
merged 13 commits into from
May 21, 2020

Conversation

totten
Copy link
Member

@totten totten commented May 13, 2020

Overview

Update CiviMail's default behavior to replace extern/open.php and extern/url.php with civicrm/mailing/open and civicrm/mailing/url.

This is an update/replacement for #17084. It contributes towards multiple issues - e.g. https://lab.civicrm.org/dev/drupal/-/issues/9, https://lab.civicrm.org/dev/drupal/-/issues/77, https://lab.civicrm.org/dev/core/-/issues/1708, and possibly https://lab.civicrm.org/dev/mail/-/issues/17

Before

  • CiviMail accepts callbacks at these URLs:
    • http://example.com/...codepath.../civicrm/extern/url.php?u=NNN&qid=NNN
    • http://example.com/...codepath.../civicrm/extern/open.php?q=NNN
  • When composing CiviMail messages, the links always point to those two locations.

After

  • CiviMail accepts callbacks at these URLs:
    • http://example.com/...codepath.../civicrm/extern/url.php?u=NNN&qid=NNN (same)
    • http://example.com/...codepath.../civicrm/extern/open.php?q=NNN (same)
    • http://example.com/civicrm/mailing/url?u=NNN&qid=NNN (new)
    • http://example.com/civicrm/mailing/open?qid=NNN (new; note: 'qbecomesqid`)
  • When composing CiviMail messages, the links obey a setting (defaultExternUrl=router or defaultExternUrl=standalone). The default is router.
  • The setting is available in "System Settings => Resource URLs".
    Screen Shot 2020-05-13 at 12 28 01 AM

Technical Details

This is an update/replacement for #17084. Some distinguishing characteristics are:

  • It addresses extern/open in addition to extern/url.
  • When preparing callback URLs, it still goes through the same API as before (CRM_Utils_System::externUrl()). This should mean that wp-civi-rest and flexmailer can get decent forward/backward compatibility by using that API.
  • It allows for a gentler transition - if the new routes have some problematic edge-case, then you can switch back.

Why allow the option? It's a hedge against some of risks/edges invovled.

  • Some sites may use settings_location.php and/or customized boot logic (for multisite/URL dynamism). While these ought to work better out-of-the-box, it's an open-space and hard to verify from my POV.
  • Moving to the CMS/Civi router has two potential impacts on performance.
    • When going through a framework, one is more prone to unexpected session-initialization - which is undesirable for webmail clicks.
    • There will be a full bootstrap, which can add latency to handling clickthroughs.
    • (One would expect these to be acceptable/manageable, but that's not an empirical or objective judgment and may require some real-world usage to fully shake-out.)

Of course, there are several upsides:

  • The bootstrap is more typical (which is notably useful on WP - and should reduce the need for setting_location.php-style customizations).
  • These PHP files don't need to be exposed to the web (which is notably useful on D8).
  • The extern/url.php replacement supports hook_civicrm_alterRedirect. Compared to hook_civicrm_alterMailParams (which was reportedly used circa v3.3 to customize click-through tracker URLs), this is a simpler and safer way to programmatically supplement click-through URLs.

Going forward, when we tackle migrations for some other extern scripts, the setting defaultExternUrl and the function migrateExternUrl() may come in handy again.

totten and others added 9 commits May 12, 2020 23:07
Ex: In a request for `http://dmaster.bknix:8001/civicrm/mailing/url?u=3&qid=2`, the redirect URL
has the value `q=civicrm/mailing/url` incorrectly appended.
For example, suppose your goal is to recognize any CiviMail links going to `woogle.com` and
append code with the mailing ID (`&src=civimail_123`). Do this:

```php
  if ($context['for'] === 'civicrm/mailing/url' && preg_match('/woogle\.com$/', $url->getHost())) {
    $mailing_id = CRM_Core_DAO::singleValueQuery('
      SELECT mj.mailing_id FROM civicrm_mailing_event_queue meq
      INNER JOIN civicrm_mailing_job mj ON mj.id = meq.job_id
      WHERE meq.id = %1
    ', [
      1 => [$context['queue_id'], 'Int']
    ]);
    $url = $url->withQuery($url->getQuery() . '&src=civimail_' . $mailing_id);
  }
```
This toggle allows a sysadmin to choose between the legacy `extern/foo.php`
and newer built-in routes.
@civibot
Copy link

civibot bot commented May 13, 2020

(Standard links)

@civibot civibot bot added the master label May 13, 2020
// Ugh
unset($query_param['page']);
unset($query_param['noheader']);
}
Copy link
Member Author

@totten totten May 13, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I first tested on a wpmaster site, extractPassthroughParameters() was still producing some noisy GET params (e.g. a link to https://www.google.com winds up as https://www.google.com?noheader=1). This extra conditional resolved that.

But it's also ugly. TBH I'd be happier to rip out extractPassthroughParameters() and make a small BC-break:

  • The notion in https://issues.civicrm.org/jira/browse/CRM-7103 / extractPassthroughParameters() is not well-defined across different routing systems.
  • This PR makes a better API available for the same use-case (hook_civicrm_alterRedirect).
  • I skimmed hook_civicrm_alterMailParams consumers in universe and couldn't spot any doing URL manipulation on CiviMail content. (The fuzion transactional ext was close, but it skips CiviMail.)
  • If one is actually relying on hook_civicrm_alterMailParams for URL manipulation, then you can buy some time for a transition -- set defaultExternUrl=standalone and you get the old behavior.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@totten I'm pretty sure that this issue on Lab will have an impact here - because bleeding edge WordPress has changed the way in which the page query var is used and it now assumes that this is the the desired path and redirects accordingly. We're going to have to audit CiviCRM's usage of this query var in a WordPress context and strip out any usage of it on the front-end. This should lead to unset($query_param['page']); being unnecessary.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, well, ?page= won't be missed. :)

Since coordinating the timelines on these changes may be tricky, I guess the implication here is... if #17312 does keep extractPassthroughParameters(), then eventually we can remove the unset($query_param['page'). For the moment, from a WP-integration perspective, there's no harm in keeping the unset().

@totten
Copy link
Member Author

totten commented May 13, 2020

I've done some testing on dmaster and wpmaster with fake SMTP (mailcatcher) in both modes (defaultExternUrl=standalone|router). These seem good on the current revision:

  • Click-throughs and opens worked on test mailing ("New Mailing => Send test"; read the email, skim the source, click the links)
  • Click-throughs and opens worked on an actual mailing (e.g. read the email, skim the source, click the links, and check the report/statistics for the mailing).

(Note: When testing the links, make sure to control for permissions -- e.g. open the links in a separate browser or "private" browser where the user is unauthenticated.)

I have not yet tested flexmailer or mosaico, but I'm pretty sure they'll merit a patch so that they use CRM_Utils_System::externUrl() (and thereby abide defaultExternUrl policy whenever that gets deployed). That's probably the next TODO.

I also haven't tested with wp-civi-rest or with any of the more creative WP deployment styles.

@kcristiano
Copy link
Member

kcristiano commented May 13, 2020

@totten I've done a quick run through on WP with the PR both with and without WP REST routing. Testing looks good. Still need to test with Mosaico/flexmailer.

I do want to review what will need to change due to:
https://lab.civicrm.org/dev/wordpress/-/issues/49 as that is a critical issue for WP/CV right now (in WP Trac Terms OMGWTFBBQ) as @christianwach mentions.

I'll also add this to the RC testing and look at Joomla as well.

Update:

Testing with Mosaico and Flexmailer on WP:

Mailing Links with PR - http://wpcvmaster.test/wp-admin/admin.php?page=CiviCRM&q=civicrm/mailing/url&u=13&qid=16 works

Mailing links with PR and WP REST API routing: http://wpcvmaster.test/wp-json/civicrm/v3/url?page=CiviCRM&q=civicrm/mailing/url&u=11&qid=12 Fails

Mailing Links on Master (No PR) and Mosaico/Flexmailer: http://wpcvmaster.test/wp-json/civicrm/v3/url?u=15&qid=21 works

@eileenmcnaughton
Copy link
Contributor

test this please

@eileenmcnaughton
Copy link
Contributor

I think transitional support fo the old extern urls is good - but I have some concern about the open endedness. I'd rather see something pointing out that the setting will be removed and that if a site needs to resort to setting it they should create a gitlab issue. I would make sure the name of the setting, not just the help, made that clear

],
'default' => 'router',
'add' => '5.27',
'title' => ts('Extern URL Style'),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Transitional Extern URL style ...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Title tweaked:

Screen Shot 2020-05-14 at 2 34 08 AM

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I just noticed your bit about gitlab. Here's another variation:

Screen Shot 2020-05-14 at 2 45 07 AM

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@totten @eileenmcnaughton I'm confused -- why would a GitLab issue be necessary?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@christianwach the theory is that people would only use this setting if they need to because something weird is happening. Once there are no more something weirds then the setting & support for it can go. Probably in a few months we would remove from the form & later on from the code base

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eileenmcnaughton Sounds reasonable to me 👍

'title' => ts('Extern URL Style'),
'is_domain' => 1,
'is_contact' => 0,
'help_text' => NULL,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also - the help text should ideally be in here - although I think that if the settings page is not using the generic tpl it may not work

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds cool :) - I didn't realize that dataflow was now working for some forms.

FWIW, when I grep settings/ for other examples of help_text, they all appear to be a couple sentences long. Since this is actually several paragraphs (which in turn requires markup), perhaps it's still OK in the .hlp file?

totten added a commit to totten/civicrm-wordpress that referenced this pull request May 13, 2020
Overview
--------

`hook_civicrm_alterExternUrl` allows for modifications to certain callback
URLs (e.g.  CiviMail open/click-through URLs). As a hook, there may be multiple
parties which weigh-in.

This fixes an interaction that arises in the following conditions:

* Use Civi+WordPress
* Set `CIVICRM_WP_REST_REPLACE_MAILING_TRACKING` to `TRUE`
* Use civicrm/civicrm-core#17312
* Send a mailing (or test email) with a tracked link

Before
------

The tracked link is converted to something like

```
http://wpmaster.bknix:8001/wp-json/civicrm/v3/url?page=CiviCRM&q=civicrm/mailing/url&u=5&qid=67
```

On the plus side, this includes query parameters `u=5&qid=67` - which are important inputs for any `extern/url`-style end-point.

On the downside, the URL is mixing up artifacts which identify different end-points, ie

* `wp-json/civicrm/v3/url` suggests the `wp-rest` end-point
* `?page=CiviCRM&q=civicrm/mailing/url` suggests the #17312 end-point

After
-----

The tracked link appears as:

```
http://wpmaster.bknix:8001/wp-json/civicrm/v3/url?u=5&qid=68
```

Notice that this unambiguously uses the `wp-rest` end-point.  It includes
the important `u=5&qid=67` but skips the irrelevant `q=civicrm/mailing/url`.

Technical Details
-----------------

The contract for `hook_civicrm_alterExternUrl` allows multiple parties to
weigh-in on the URL. In this case, we have 3 parties which can generate
the URL:

* One function suggests the original standalone scripts
* Another function suggests the #17312 routes
* Another function suggests the `wp-rest` routes

Notably, all of these parties have the aim of *choosing the end-point*, so they
should construct the full `$url`.
totten added a commit to totten/org.civicrm.flexmailer that referenced this pull request May 14, 2020
This aims to improve cooperation between `flexmailer`, `civicrm-wordpress:wp-rest/`, and
civicrm/civicrm-core#17312 -- so that flexmailer produces compliant URLs.

Before
------

The open-tracking URL is constructed as a reference to the standalone script `extern/open.php`
using certain `$config` properties.

After
-----

On Civi v5.23+, it uses `CRM_Utils_System::externUrl()` API to request the full URL of
`extern/open`.  Flexmailer is no longer responsible for figuring the URL, and other
agents (via `hook_civicrm_alterExternUrl`) can do so.

On Civi v5.22 and earlier, it continues using the same old formula. This provides
drop-in/bug-level-compatibility on older versions. This can be removed in a couple months.

Comments
--------

I suspect this also fixes [dev/mail#17](https://lab.civicrm.org/dev/mail/issues/17),
wherein the open tracker has flawed URLs in certain multilingual configurations.
However, I can't confirm for certain because I don't have that configuration.

But just to game this out...

* If the current patch fixes dev/mail#17, then huzza!
* If it doesn't, then the problem is in `civicrm-core`'s `CRM_Utils_System::externUrl()`
  (not `flexmailer:OpenTracker.php`). Which means:
    * The problem already affects other use-cases in `civirm-core` in v5.23+.
    * The fix should go in `civicrm-core`.

Either way, addressing dev/mail#17 shouldn't require any further change in `flexmailer`.
@totten
Copy link
Member Author

totten commented May 14, 2020

@kcristiano Great, nice testing. :) I've posted a PR to fix the oddball (/wp-json/civicrm/v3/url?page=CiviCRM&q=civicrm/mailing/url). See civicrm/civicrm-wordpress#192

For flexmailer, I've now done some testing (via D7):

@eileenmcnaughton
Copy link
Contributor

test this please

@kcristiano
Copy link
Member

I am waiting to do more testing on this until we merge #17352 and civicrm/civicrm-wordpress#194

@eileenmcnaughton
Copy link
Contributor

@kcristiano the wait is over :-)

@kcristiano
Copy link
Member

I've done testing on WP. All on master woith both WP 5.41 and WP 5.5.alpha

I tested:

  • Traditional Mailing
  • Traditional mailing with Flexmailer
  • Traditional Mailing with WP REST API
  • Traditional mailing with Flexmailer and WP REST API
  • Mosaico
  • Mosaico with WP REST API

All worked as expected.

I did not test on Joomla. But this does look ready to merge along with civicrm/civicrm-wordpress#192

@eileenmcnaughton
Copy link
Contributor

Thanks @kcristiano & @totten

@eileenmcnaughton eileenmcnaughton merged commit a5949c8 into civicrm:master May 21, 2020
@totten totten deleted the url3 branch May 21, 2020 06:01
bastienho pushed a commit to bastienho/civicrm-wordpress that referenced this pull request Sep 10, 2020
Overview
--------

`hook_civicrm_alterExternUrl` allows for modifications to certain callback
URLs (e.g.  CiviMail open/click-through URLs). As a hook, there may be multiple
parties which weigh-in.

This fixes an interaction that arises in the following conditions:

* Use Civi+WordPress
* Set `CIVICRM_WP_REST_REPLACE_MAILING_TRACKING` to `TRUE`
* Use civicrm/civicrm-core#17312
* Send a mailing (or test email) with a tracked link

Before
------

The tracked link is converted to something like

```
http://wpmaster.bknix:8001/wp-json/civicrm/v3/url?page=CiviCRM&q=civicrm/mailing/url&u=5&qid=67
```

On the plus side, this includes query parameters `u=5&qid=67` - which are important inputs for any `extern/url`-style end-point.

On the downside, the URL is mixing up artifacts which identify different end-points, ie

* `wp-json/civicrm/v3/url` suggests the `wp-rest` end-point
* `?page=CiviCRM&q=civicrm/mailing/url` suggests the #17312 end-point

After
-----

The tracked link appears as:

```
http://wpmaster.bknix:8001/wp-json/civicrm/v3/url?u=5&qid=68
```

Notice that this unambiguously uses the `wp-rest` end-point.  It includes
the important `u=5&qid=67` but skips the irrelevant `q=civicrm/mailing/url`.

Technical Details
-----------------

The contract for `hook_civicrm_alterExternUrl` allows multiple parties to
weigh-in on the URL. In this case, we have 3 parties which can generate
the URL:

* One function suggests the original standalone scripts
* Another function suggests the #17312 routes
* Another function suggests the `wp-rest` routes

Notably, all of these parties have the aim of *choosing the end-point*, so they
should construct the full `$url`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants