Skip to content

Commit 0dd1346

Browse files
Merge pull request #567 from Smartling/WP-943-acf-field-group-sync
add ACF field group synchronization (WP-943)
2 parents d5391c1 + 71671d8 commit 0dd1346

13 files changed

Lines changed: 255 additions & 8 deletions

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "smartling/wordpress-connector",
33
"license": "GPL-2.0-or-later",
4-
"version": "3.11.7",
4+
"version": "3.12.0",
55
"description": "",
66
"type": "wordpress-plugin",
77
"repositories": [

inc/Smartling/Extensions/Acf/AcfDynamicSupport.php

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,21 @@
1010
use Smartling\Helpers\ArrayHelper;
1111
use Smartling\Helpers\DiagnosticsHelper;
1212
use Smartling\Helpers\FieldsFilterHelper;
13+
use Smartling\Helpers\LogContextMixinHelper;
1314
use Smartling\Helpers\LoggerSafeTrait;
1415
use Smartling\Helpers\SiteHelper;
1516
use Smartling\Helpers\WordpressFunctionProxyHelper;
1617
use Smartling\Replacers\ReplacerFactory;
1718
use Smartling\Settings\ConfigurationProfileEntity;
1819
use Smartling\Settings\SettingsManager;
20+
use Smartling\Submissions\SubmissionEntity;
21+
use Smartling\Submissions\SubmissionManager;
1922

2023
class AcfDynamicSupport
2124
{
2225
use LoggerSafeTrait;
2326

27+
public const POST_TYPE_ACF_FIELD_GROUP = 'acf-field-group';
2428
public const REFERENCED_TYPE_NONE = 'none';
2529
public const REFERENCED_TYPE_MEDIA = 'media';
2630
public const REFERENCED_TYPE_POST = 'post';
@@ -60,6 +64,7 @@ public function __construct(
6064
private ArrayHelper $arrayHelper,
6165
private SettingsManager $settingsManager,
6266
private SiteHelper $siteHelper,
67+
private SubmissionManager $submissionManager,
6368
private WordpressFunctionProxyHelper $wpProxy,
6469
)
6570
{}
@@ -180,7 +185,7 @@ private function rawReadGroups(): array
180185
{
181186
$posts = (new \WP_Query(
182187
[
183-
'post_type' => 'acf-field-group',
188+
'post_type' => self::POST_TYPE_ACF_FIELD_GROUP,
184189
'suppress_filters' => true,
185190
'posts_per_page' => -1,
186191
'post_status' => 'publish',
@@ -405,6 +410,79 @@ function (array $definitions) {
405410
}
406411
}
407412

413+
public function syncFieldGroup(SubmissionEntity $submission): void
414+
{
415+
$context = [
416+
'submissionId' => $submission->getId(),
417+
'contentType' => $submission->getContentType(),
418+
'sourceId' => $submission->getSourceId(),
419+
];
420+
try {
421+
foreach ($context as $key => $value) {
422+
LogContextMixinHelper::addToContext($key, $value);
423+
}
424+
if ($submission->getContentType() !== self::POST_TYPE_ACF_FIELD_GROUP) {
425+
$this->getLogger()->error("Trying to sync ACF field group, expected content type: " . self::POST_TYPE_ACF_FIELD_GROUP);
426+
427+
return;
428+
}
429+
430+
$post = $this->wpProxy->get_post($submission->getSourceId(), ARRAY_A);
431+
if ($post === null) {
432+
$this->getLogger()->error("Trying to sync ACF field group, source post not found");
433+
434+
return;
435+
}
436+
437+
$array = $this->wpProxy->maybe_unserialize($post['post_content']);
438+
if (!is_array($array)) {
439+
$this->getLogger()->error("Trying to sync ACF field group, post content could not be unserialized, content=\"$post->post_content\"");
440+
441+
return;
442+
}
443+
444+
if (!array_key_exists('location', $array)) {
445+
$this->getLogger()->debug("Sync of ACF field group skipped: no location fields");
446+
447+
return;
448+
}
449+
450+
foreach ($array['location'] as &$rules) {
451+
foreach ($rules as &$rule) {
452+
if ($rule['param'] === 'page') {
453+
$targetSubmission = $this->submissionManager->findOne([
454+
SubmissionEntity::FIELD_SOURCE_BLOG_ID => $submission->getSourceBlogId(),
455+
SubmissionEntity::FIELD_SOURCE_ID => $rule['value'],
456+
SubmissionEntity::FIELD_TARGET_BLOG_ID => $submission->getTargetBlogId(),
457+
SubmissionEntity::FIELD_CONTENT_TYPE => $this->wpProxy->get_post_types(),
458+
]);
459+
if ($targetSubmission === null) {
460+
$this->getLogger()->debug("Skip change location page {$rule['operator']} {$rule['value']}: no target submission exists");
461+
continue;
462+
}
463+
$rule['value'] = (string)$targetSubmission->getTargetId();
464+
}
465+
}
466+
unset($rule);
467+
}
468+
unset($rules);
469+
470+
$this->siteHelper->withBlog($submission->getTargetBlogId(), function () use ($array, $submission): void {
471+
$result = $this->wpProxy->wp_update_post([
472+
'ID' => $submission->getTargetId(),
473+
'post_content' => serialize($array),
474+
], true);
475+
if ($result instanceof \WP_Error) {
476+
$this->getLogger()->error("Sync of ACF field group failed to update post: " . implode(', ', $result->get_error_messages()));
477+
}
478+
});
479+
} finally {
480+
foreach (array_keys($context) as $key) {
481+
LogContextMixinHelper::removeFromContext($key);
482+
}
483+
}
484+
}
485+
408486
private function tryRegisterACF(): void
409487
{
410488
$this->getLogger()->debug('Checking if ACF presents...');
@@ -617,7 +695,7 @@ private function checkAcfTypes(): bool
617695
{
618696
$postTypes = $this->getPostTypes();
619697

620-
return in_array('acf-field', $postTypes, true) && in_array('acf-field-group', $postTypes, true);
698+
return in_array('acf-field', $postTypes, true) && in_array(self::POST_TYPE_ACF_FIELD_GROUP, $postTypes, true);
621699
}
622700

623701
/**

inc/Smartling/Helpers/DetectChangesHelper.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Smartling\Helpers;
44

55
use Smartling\DbAl\UploadQueueManager;
6+
use Smartling\Extensions\Acf\AcfDynamicSupport;
67
use Smartling\Models\IntegerIterator;
78
use Smartling\Settings\ConfigurationProfileEntity;
89
use Smartling\Settings\SettingsManager;
@@ -15,6 +16,7 @@ class DetectChangesHelper
1516
use LoggerSafeTrait;
1617

1718
public function __construct(
19+
private AcfDynamicSupport $acfDynamicSupport,
1820
private ContentSerializationHelper $contentSerializationHelper,
1921
private UploadQueueManager $uploadQueueManager,
2022
private SettingsManager $settingsManager,
@@ -105,6 +107,12 @@ public function detectChanges(int $blogId, int $contentId, string $contentType):
105107
$submissions = $this->getSubmissions($blogId, $contentId, $contentType);
106108

107109
if (0 === count($submissions)) {
110+
$submissions = $this->getSubmissions($blogId, $contentId, AcfDynamicSupport::POST_TYPE_ACF_FIELD_GROUP);
111+
foreach ($submissions as $submission) {
112+
$this->getLogger()->debug("Sync field group submissionId={$submission->getId()}");
113+
$this->acfDynamicSupport->syncFieldGroup($submission);
114+
}
115+
108116
$this->getLogger()->debug(
109117
vsprintf('No submissions found for %s blog=%s, id=%s', [$contentType, $blogId, $contentId])
110118
);

inc/Smartling/Helpers/WordpressFunctionProxyHelper.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,11 @@ public function setObjectTerms(int $objectId, array $termIds, string $taxonomy):
159159
return wp_set_object_terms($objectId, $termIds, $taxonomy);
160160
}
161161

162+
public function wp_update_post(): int|\WP_Error
163+
{
164+
return wp_update_post(...func_get_args());
165+
}
166+
162167
public function url_to_postid(string $url): int
163168
{
164169
return url_to_postid($url);

inc/Smartling/WP/Controller/PostBasedWidgetControllerStd.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Smartling\Bootstrap;
99
use Smartling\DbAl\LocalizationPluginProxyInterface;
1010
use Smartling\Exception\SmartlingDbException;
11+
use Smartling\Extensions\Acf\AcfDynamicSupport;
1112
use Smartling\Helpers\ArrayHelper;
1213
use Smartling\Helpers\Cache;
1314
use Smartling\Helpers\CommonLogMessagesTrait;
@@ -16,7 +17,6 @@
1617
use Smartling\Helpers\PluginInfo;
1718
use Smartling\Helpers\SiteHelper;
1819
use Smartling\Helpers\SmartlingUserCapabilities;
19-
use Smartling\Jobs\JobEntity;
2020
use Smartling\Jobs\JobEntityWithBatchUid;
2121
use Smartling\Settings\SettingsManager;
2222
use Smartling\Submissions\SubmissionEntity;
@@ -568,8 +568,7 @@ public function save(mixed $post_id, mixed $post = null, mixed $update = null):
568568
return;
569569
}
570570

571-
if ($this->servedContentType === $_POST['post_type']) {
572-
571+
if ($this->canHandle($_POST['post_type'])) {
573572
$this->getLogger()->debug(
574573
vsprintf('Entering post save hook. post_id = \'%s\', blog_id = \'%s\'',
575574
[
@@ -608,4 +607,11 @@ public function save(mixed $post_id, mixed $post = null, mixed $update = null):
608607
add_action('save_post', [$this, 'save']);
609608
}
610609
}
610+
611+
private function canHandle($postType): bool
612+
{
613+
return $this->servedContentType === $postType
614+
// acf field groups are a private post type that needs to be handled
615+
|| $postType === AcfDynamicSupport::POST_TYPE_ACF_FIELD_GROUP;
616+
}
611617
}

inc/config/services.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ services:
268268
detect-changes.helper:
269269
class: Smartling\Helpers\DetectChangesHelper
270270
arguments:
271+
- "@acf.dynamic.support"
271272
- "@content-serialization.helper"
272273
- "@manager.upload.queue"
273274
- "@manager.settings"
@@ -528,6 +529,7 @@ services:
528529
- "@helper.array"
529530
- "@manager.settings"
530531
- "@site.helper"
532+
- "@manager.submission"
531533
- "@wp.proxy"
532534

533535
acf.type.detector:

readme.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ Additional information on the Smartling Connector for WordPress can be found [he
6262
3. Track translation status within WordPress from the Submissions Board. View overall progress of submitted translation requests as well as resend updated content.
6363

6464
== Changelog ==
65+
= 3.12.0 =
66+
* Added ACF field group synchronization between configured sites on save.
67+
6568
= 3.11.7 =
6669
* Added expert global setting to control cron lock ttl
6770
* Added support for Elementor conditionals

smartling-connector.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
* Plugin Name: Smartling Connector
1212
* Plugin URI: https://www.smartling.com/products/automate/integrations/wordpress/
1313
* Description: Integrate your WordPress site with Smartling to upload your content and download translations.
14-
* Version: 3.11.7
14+
* Version: 3.12.0
1515
* Author: Smartling
1616
* Author URI: https://www.smartling.com
1717
* License: GPL-2.0+

tests/DetectChangesTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use PHPUnit\Framework\TestCase;
66
use Smartling\DbAl\UploadQueueManager;
7+
use Smartling\Extensions\Acf\AcfDynamicSupport;
78
use Smartling\Helpers\ContentSerializationHelper;
89
use Smartling\Helpers\DetectChangesHelper;
910
use Smartling\Settings\SettingsManager;
@@ -27,6 +28,7 @@ class DetectChangesTest extends TestCase
2728
protected function setUp(): void
2829
{
2930
$this->detectChangesHelperMock = new DetectChangesHelper(
31+
$this->createMock(AcfDynamicSupport::class),
3032
$this->createMock(ContentSerializationHelper::class),
3133
$this->createMock(UploadQueueManager::class),
3234
$this->createMock(SettingsManager::class),

tests/Smartling/Extensions/Acf/AcfDynamicSupportTest.php

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,24 @@
88
use Smartling\Helpers\WordpressFunctionProxyHelper;
99
use Smartling\Replacers\ReplacerFactory;
1010
use Smartling\Settings\SettingsManager;
11+
use Smartling\Submissions\SubmissionEntity;
12+
use Smartling\Submissions\SubmissionManager;
1113

12-
class AcfDynamicSupportTest extends TestCase {
14+
class AcfDynamicSupportTest extends TestCase
15+
{
16+
protected function setUp(): void
17+
{
18+
defined('ARRAY_A') || define('ARRAY_A', 'ARRAY_A');
19+
parent::setUp();
20+
}
1321

1422
public function testGetReplacerIdForField()
1523
{
1624
$x = new class(
1725
new ArrayHelper(),
1826
$this->createMock(SettingsManager::class),
1927
$this->createMock(SiteHelper::class),
28+
$this->createMock(SubmissionManager::class),
2029
$this->createMock(WordpressFunctionProxyHelper::class),
2130
) extends AcfDynamicSupport {
2231
public
@@ -30,4 +39,102 @@ function run(): void
3039
'someAttribute',
3140
));
3241
}
42+
43+
public function testSyncFieldGroup()
44+
{
45+
$sourceBlogId = 1;
46+
$targetBlogId = 7;
47+
$fieldGroupSubmissionTargetId = 11;
48+
49+
$fieldGroupSubmission = $this->createMock(SubmissionEntity::class);
50+
$fieldGroupSubmission->method('getContentType')->willReturn(AcfDynamicSupport::POST_TYPE_ACF_FIELD_GROUP);
51+
$fieldGroupSubmission->method('getSourceBlogId')->willReturn($sourceBlogId);
52+
$fieldGroupSubmission->method('getSourceId')->willReturn(3);
53+
$fieldGroupSubmission->method('getTargetBlogId')->willReturn($targetBlogId);
54+
$fieldGroupSubmission->method('getTargetId')->willReturn($fieldGroupSubmissionTargetId);
55+
56+
$pageSourceId = 4052;
57+
$pageTargetId = 12;
58+
59+
$submission4052 = $this->createMock(SubmissionEntity::class);
60+
$submission4052->method('getContentType')->willReturn('post');
61+
$submission4052->method('getSourceBlogId')->willReturn($sourceBlogId);
62+
$submission4052->method('getSourceId')->willReturn($pageSourceId);
63+
$submission4052->method('getTargetBlogId')->willReturn($targetBlogId);
64+
$submission4052->method('getTargetId')->willReturn($pageTargetId);
65+
66+
$postSubmissions = [
67+
$pageSourceId => $submission4052,
68+
];
69+
70+
$submissionManager = $this->createMock(SubmissionManager::class);
71+
$submissionManager->method('findOne')->willReturnCallback(function ($arguments) use ($postSubmissions) {
72+
return $postSubmissions[$arguments[SubmissionEntity::FIELD_SOURCE_ID]] ?? null;
73+
});
74+
75+
$wpProxy = $this->createMock(WordpressFunctionProxyHelper::class);
76+
$wpProxy->method('maybe_unserialize')->willReturnCallback(function ($data) {
77+
return unserialize($data);
78+
});
79+
$source = [
80+
'location' => [
81+
[['param' => 'post_type', 'operator' => '==', 'value' => 'video']],
82+
[['param' => 'post_type', 'operator' => '==', 'value' => 'blog']],
83+
[['param' => 'post_type', 'operator' => '==', 'value' => 'bt_event']],
84+
[['param' => 'post_type', 'operator' => '==', 'value' => 'bt_news']],
85+
[['param' => 'post_type', 'operator' => '==', 'value' => 'report']],
86+
[['param' => 'post_type', 'operator' => '==', 'value' => 'webinar']],
87+
[['param' => 'post_type', 'operator' => '==', 'value' => 'solution-guide']],
88+
[['param' => 'post_type', 'operator' => '==', 'value' => 'ebook']],
89+
[['param' => 'post_type', 'operator' => '==', 'value' => 'whitepaper']],
90+
[['param' => 'page', 'operator' => '==', 'value' => (string)$pageSourceId]],
91+
[['param' => 'page', 'operator' => '==', 'value' => '690']],
92+
[['param' => 'page', 'operator' => '==', 'value' => '2111']],
93+
[['param' => 'page', 'operator' => '==', 'value' => '1662']],
94+
[['param' => 'page', 'operator' => '==', 'value' => '29']],
95+
[['param' => 'page', 'operator' => '==', 'value' => '31']],
96+
[['param' => 'page', 'operator' => '==', 'value' => '32']],
97+
[['param' => 'page', 'operator' => '==', 'value' => '33']],
98+
[['param' => 'page', 'operator' => '==', 'value' => '30']],
99+
[['param' => 'page', 'operator' => '==', 'value' => '818']],
100+
[['param' => 'page', 'operator' => '==', 'value' => '98']],
101+
[['param' => 'page', 'operator' => '==', 'value' => '824']],
102+
[['param' => 'page', 'operator' => '==', 'value' => '3387']],
103+
[['param' => 'page', 'operator' => '==', 'value' => '798']],
104+
],
105+
'position' => 'normal',
106+
'style' => 'default',
107+
'label_placement' => 'top',
108+
'instruction_placement' => 'label',
109+
'hide_on_screen' => '',
110+
'description' => '',
111+
'show_in_rest' => 0,
112+
];
113+
114+
$expectedContent = $source;
115+
$expectedContent['location'][9][0]['value'] = (string)$pageTargetId;
116+
117+
$wpProxy->method('get_post')->willReturn([
118+
'post_content' => serialize($source),
119+
]);
120+
$wpProxy->expects($this->once())->method('wp_update_post')->with([
121+
'ID' => $fieldGroupSubmissionTargetId,
122+
'post_content' => serialize($expectedContent),
123+
]);
124+
125+
$siteHelper = $this->createMock(SiteHelper::class);
126+
$siteHelper->method('withBlog')->willReturnCallback(function ($blogId, $callable) {
127+
return $callable();
128+
});
129+
130+
$x = new AcfDynamicSupport(
131+
new ArrayHelper(),
132+
$this->createMock(SettingsManager::class),
133+
$siteHelper,
134+
$submissionManager,
135+
$wpProxy
136+
);
137+
138+
$x->syncFieldGroup($fieldGroupSubmission);
139+
}
33140
}

0 commit comments

Comments
 (0)