diff --git a/CRM/Mosaico/BAO/MosaicoTemplate.php b/CRM/Mosaico/BAO/MosaicoTemplate.php
index 9881387af..77f407185 100644
--- a/CRM/Mosaico/BAO/MosaicoTemplate.php
+++ b/CRM/Mosaico/BAO/MosaicoTemplate.php
@@ -26,24 +26,70 @@ public static function create($params) {
* Helps updating the URLs in templates so they can be reused
* after restoring a dump database in a new server.
*
+ * URL's can be include either scheme/host or path or both
+ *
* @param string $fromUrl URL of the server where the
* templates were created
* @param string $toUrl URL of the current server
*/
public static function replaceUrls($fromUrl, $toUrl) {
- $replaceQuery = "UPDATE civicrm_mosaico_template
- SET metadata = json_replace(metadata, '$.template',
- replace(
- json_unquote(
- json_extract(metadata, '$.template')
- ),
+ $from = parse_url($fromUrl);
+ $to = parse_url($toUrl);
+
+ // Update 'template': only uses path without leading slash
+ if ($from['path']) {
+ $replaceQuery = "UPDATE civicrm_mosaico_template
+ SET metadata = JSON_REPLACE(metadata, '$.template',
+ REPLACE(
+ JSON_UNQUOTE(
+ JSON_EXTRACT(metadata, '$.template')
+ ),
%1, %2)
- );";
+ );";
+
+ CRM_Core_DAO::executeQuery($replaceQuery, [
+ 1 => [ltrim($from['path'], '/'), 'String'],
+ 2 => [ltrim($to['path'] ?? '', '/'), 'String'],
+ ]);
+ }
+ // Update 'html' and 'content'
+ $replaceQuery = "UPDATE civicrm_mosaico_template
+ SET html = REPLACE(html, %1, %2),
+ content = REPLACE(content, %1, %2)
+ ;";
+
+ // ... with unencoded strings
CRM_Core_DAO::executeQuery($replaceQuery, [
1 => [$fromUrl, 'String'],
2 => [$toUrl, 'String'],
]);
+
+ // ... with encoded strings
+ CRM_Core_DAO::executeQuery($replaceQuery, [
+ 1 => [urlencode($fromUrl), 'String'],
+ 2 => [urlencode($toUrl), 'String'],
+ ]);
+
+ // Images load from https://example.com/civicrm/mosaico/img, so update the host part as well
+ $hostFrom = str_replace($from['path'], '', $fromUrl);
+ $hostTo = str_replace($to['path'], '', $toUrl);
+ if ($hostFrom && $hostTo) {
+ CRM_Core_DAO::executeQuery($replaceQuery, [
+ 1 => [$hostFrom . "/civicrm/mosaico", 'String'],
+ 2 => [$hostTo . "/civicrm/mosaico", 'String'],
+ ]);
+ }
+
+ // However, 'content' is a json string, and the encoded representation depends on the json encoding options
+ // The above works where the JSON_UNESCAPED_SLASHES flag has been used so that the encoded representation is like '/my/path'
+ // But without that option, the encoded representation is '\/my\/path'
+ // Comparing the decoded values would be better, but this should work for our purposes...
+ // executeQuery doesn't like backslahes in 'String', so do this directly.
+
+ $slashedFrom = str_replace('/', '\\\/', $fromUrl);
+ $slashedTo = str_replace('/', '\\\/', $toUrl);
+ CRM_Core_DAO::executeQuery("UPDATE civicrm_mosaico_template SET content = REPLACE(content, '$slashedFrom', '$slashedTo');");
}
/**
@@ -65,7 +111,7 @@ public static function findBaseTemplates($ignoreCache = FALSE, $dispatchHooks =
$templatesLocation[] = ['dir' => $templatesDir, 'url' => $templatesUrl];
$customTemplatesDir = \Civi::paths()->getPath(\Civi::settings()->get('mosaico_custom_templates_dir'));
- $customTemplatesUrl = \Civi::paths()->getUrl(\Civi::settings()->get('mosaico_custom_templates_url'),'absolute');
+ $customTemplatesUrl = \Civi::paths()->getUrl(\Civi::settings()->get('mosaico_custom_templates_url'), 'absolute');
if (!is_null($customTemplatesDir) && !is_null($customTemplatesUrl)) {
if (is_dir($customTemplatesDir)) {
$templatesLocation[] = ['dir' => $customTemplatesDir, 'url' => $customTemplatesUrl];
diff --git a/docs/api.md b/docs/api.md
index 7fc395e9d..26e16568b 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -11,10 +11,14 @@ This extension defines a few new APIs:
old v1.x templates.
* `MosaicoTemplate.*`: This API provides access to the user-configurable templates. It supports all standard CRUD
actions (`get`, `create`, `delete`etc). Its data-structure closely adheres to Mosaico's canonical storage format.
-* `MosaicoTemplate.replaceurls`: When a database is restored in a server with a different URL, templates will need to be updated. The `replaceurls` method facilitates that migration task:
+* `MosaicoTemplate.replaceurls`: When a database is restored in a server with a different URL or if paths change then templates will need to be updated. The `replaceurls` method facilitates that migration task:
```
cv api MosaicoTemplate.replaceurls from_url="http://old.server.org" to_url="https://new.server.org"
+```
+ Migrating from Drupal 7 to WordPress:
+```
+cv api MosaicoTemplate.replaceurls from_url="/sites/default/files/civicrm/" to_url="/wp-content/uploads/civicrm/"
```
* `MosaicoBaseTemplate.get`: This API provides access to the *base templates*. A base template (such as `versafix-1`)
@@ -116,18 +120,18 @@ Example usage:
function example_civicrm_mosaicoPlugin(&$plugins) {
$plugins[] = _example_alert_plugin();
}
-
+
/**
* Example plugin to display alert on init and dispose.
- */
+ */
function _example_alert_plugin(){
$plugin = <<< JS
function(vm) {
alert("test-plugin");
return {
- init: function(vm){alert("init");},
+ init: function(vm){alert("init");},
dispose: function(vm){alert("dispose");}
- }
+ }
}
JS;
diff --git a/tests/phpunit/CRM/Mosaico/MosaicoTemplateTest.php b/tests/phpunit/CRM/Mosaico/MosaicoTemplateTest.php
index f8d07089c..1d57eca1f 100644
--- a/tests/phpunit/CRM/Mosaico/MosaicoTemplateTest.php
+++ b/tests/phpunit/CRM/Mosaico/MosaicoTemplateTest.php
@@ -85,4 +85,92 @@ public function testClone(): void {
$this->assertEquals(json_encode(['abc' => 'def']), $clone['content']);
}
+ /**
+ * Test replaceUrls with full URL
+ */
+ public function testReplaceUrlsFull(): void {
+ $createResult = $this->callAPISuccess('MosaicoTemplate', 'create', [
+ 'title' => 'MosaicoTemplateTest baz',
+ 'base' => 'versafix-1',
+ // As extracted from a real template
+ 'html' => '
',
+ 'metadata' => json_encode(['template' => '/sites/default/files/civicrm/mosaico_tpl/CiviVersafix/template-CiviVersafix.html', 'templateversion' => '1.0.5', 'editorversion' => '0.15.0'], JSON_UNESCAPED_SLASHES),
+ 'content' => json_encode(['src' => 'https://old-site.org/sites/default/files/civicrm/persist/contribute/images/uploads/logo_bced05c6746c32f9adb3fea8a7060dac.png']),
+ ]);
+
+ $this->callAPISuccess('MosaicoTemplate', 'replaceurls', [
+ 'from_url' => 'https://old-site.org/sites/default/files/civicrm/',
+ 'to_url' => 'https://new-site.org/wp-content/uploads/civicrm/',
+ ]);
+
+ $getResult = $this->callAPISuccess('MosaicoTemplate', 'getsingle', ['id' => $createResult['id']]);
+ foreach (['html', 'content', 'metadata'] as $element) {
+ $this->assertStringNotContainsString('old-site.org', $getResult[$element]);
+ $this->assertStringNotContainsString('sites', $getResult[$element]);
+ $this->assertStringContainsString('wp-content', $getResult[$element]);
+ if ($element != 'metadata') {
+ $this->assertStringContainsString('new-site.org', $getResult[$element]);
+ $this->assertStringContainsString('logo_bced', $getResult[$element]);
+ }
+ }
+ }
+
+ /**
+ * Test replaceUrls with host only
+ */
+ public function testReplaceUrlsHost(): void {
+ $createResult = $this->callAPISuccess('MosaicoTemplate', 'create', [
+ 'title' => 'MosaicoTemplateTest baz',
+ 'base' => 'versafix-1',
+ // As extracted from a real template
+ 'html' => '
',
+ 'metadata' => json_encode(['template' => '/sites/default/files/civicrm/mosaico_tpl/CiviVersafix/template-CiviVersafix.html', 'templateversion' => '1.0.5', 'editorversion' => '0.15.0'], JSON_UNESCAPED_SLASHES),
+ 'content' => json_encode(['src' => 'https://old-site.org/sites/default/files/civicrm/persist/contribute/images/uploads/logo_bced05c6746c32f9adb3fea8a7060dac.png']),
+ ]);
+
+ $this->callAPISuccess('MosaicoTemplate', 'replaceurls', [
+ 'from_url' => 'https://old-site.org/',
+ 'to_url' => 'https://new-site.org/',
+ ]);
+
+ $getResult = $this->callAPISuccess('MosaicoTemplate', 'getsingle', ['id' => $createResult['id']]);
+ foreach (['html', 'content', 'metadata'] as $element) {
+ $this->assertStringNotContainsString('old-site.org', $getResult[$element]);
+ $this->assertStringContainsString('sites', $getResult[$element]);
+ if ($element != 'metadata') {
+ $this->assertStringContainsString('new-site.org', $getResult[$element]);
+ $this->assertStringContainsString('logo_bced', $getResult[$element]);
+ }
+ }
+ }
+
+ /**
+ * Test replaceUrls with path only
+ */
+ public function testReplaceUrlsPath(): void {
+ $createResult = $this->callAPISuccess('MosaicoTemplate', 'create', [
+ 'title' => 'MosaicoTemplateTest baz',
+ 'base' => 'versafix-1',
+ // As extracted from a real template
+ 'html' => '
',
+ 'metadata' => json_encode(['template' => '/sites/default/files/civicrm/mosaico_tpl/CiviVersafix/template-CiviVersafix.html', 'templateversion' => '1.0.5', 'editorversion' => '0.15.0'], JSON_UNESCAPED_SLASHES),
+ 'content' => json_encode(['src' => 'https://old-site.org/sites/default/files/civicrm/persist/contribute/images/uploads/logo_bced05c6746c32f9adb3fea8a7060dac.png']),
+ ]);
+
+ $this->callAPISuccess('MosaicoTemplate', 'replaceurls', [
+ 'from_url' => '/sites/default/files/civicrm/',
+ 'to_url' => '/wp-content/uploads/civicrm/',
+ ]);
+
+ $getResult = $this->callAPISuccess('MosaicoTemplate', 'getsingle', ['id' => $createResult['id']]);
+ foreach (['html', 'content', 'metadata'] as $element) {
+ $this->assertStringNotContainsString('sites', $getResult[$element]);
+ $this->assertStringContainsString('wp-content', $getResult[$element]);
+ if ($element != 'metadata') {
+ $this->assertStringContainsString('old-site.org', $getResult[$element]);
+ $this->assertStringContainsString('logo_bced', $getResult[$element]);
+ }
+ }
+ }
+
}