From f07ce702df21d9eb07b0142c77c16101024d9a50 Mon Sep 17 00:00:00 2001 From: Vitalii Solovei Date: Thu, 9 Apr 2026 11:52:26 +0200 Subject: [PATCH 1/4] add support for mega menu (WP-980) Co-Authored-By: Claude Sonnet 4.6 --- composer.json | 2 +- .../Elementor/Elements/MegaMenu.php | 84 ++++++++ readme.txt | 5 +- smartling-connector.php | 2 +- .../ContentTypes/Elementor/MegaMenuTest.php | 192 ++++++++++++++++++ 5 files changed, 282 insertions(+), 3 deletions(-) create mode 100644 inc/Smartling/ContentTypes/Elementor/Elements/MegaMenu.php create mode 100644 tests/Smartling/ContentTypes/Elementor/MegaMenuTest.php diff --git a/composer.json b/composer.json index 0e0a0f91..41aa5d42 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "smartling/wordpress-connector", "license": "GPL-2.0-or-later", - "version": "5.3.3", + "version": "5.3.4", "description": "", "type": "wordpress-plugin", "repositories": [ diff --git a/inc/Smartling/ContentTypes/Elementor/Elements/MegaMenu.php b/inc/Smartling/ContentTypes/Elementor/Elements/MegaMenu.php new file mode 100644 index 00000000..34262b3f --- /dev/null +++ b/inc/Smartling/ContentTypes/Elementor/Elements/MegaMenu.php @@ -0,0 +1,84 @@ +getIntSettingByKey($key, $this->settings); + if ($id !== null) { + $return->addContent(new Content($id, ContentTypeHelper::POST_TYPE_ATTACHMENT), $this->id, "settings/$key"); + } + } + + foreach ($this->settings['menu_items'] ?? [] as $index => $item) { + if (isset($item[self::SETTING_KEY_DYNAMIC]) && is_array($item[self::SETTING_KEY_DYNAMIC])) { + $path = "settings/menu_items/$index/" . self::SETTING_KEY_DYNAMIC; + foreach ($this->getRelatedFromDynamic($item[self::SETTING_KEY_DYNAMIC], $path) as $relatedItem) { + $return->addContent($relatedItem->getContent(), $relatedItem->getContainerId(), $relatedItem->getPath()); + } + } + } + + return $return; + } + + public function getTranslatableStrings(): array + { + $return = []; + + if (array_key_exists('menu_name', $this->settings)) { + $return['menu_name'] = $this->settings['menu_name']; + } + + foreach ($this->settings['menu_items'] ?? [] as $index => $item) { + $key = 'menu_items/' . ($item['_id'] ?? $index); + if (array_key_exists('item_title', $item)) { + $return[$key]['item_title'] = $item['item_title']; + } + } + + return [$this->getId() => $return]; + } + + public function setTargetContent( + ExternalContentElementor $externalContentElementor, + RelatedContentInfo $info, + array $strings, + SubmissionEntity $submission, + ): static { + $this->raw = parent::setTargetContent($externalContentElementor, $info, $strings, $submission)->toArray(); + $this->settings = $this->raw['settings'] ?? []; + + foreach ($strings[$this->id] ?? [] as $array) { + if (is_array($array)) { + foreach ($array as $id => $values) { + foreach ($this->settings['menu_items'] ?? [] as $index => $item) { + if (($item['_id'] ?? '') === $id) { + foreach ($values as $property => $value) { + $this->settings['menu_items'][$index][$property] = $value; + } + } + } + } + } + } + $this->raw['settings'] = $this->settings; + + return new static($this->raw); + } +} diff --git a/readme.txt b/readme.txt index 6ac1193b..d3386109 100755 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Tags: translation, localization, multilingual, internationalization, smartling Requires at least: 5.5 Tested up to: 6.9 Requires PHP: 8.0 -Stable tag: 5.3.3 +Stable tag: 5.3.4 License: GPLv2 or later Translate content in WordPress quickly and seamlessly with Smartling, the industry-leading Translation Management System. @@ -62,6 +62,9 @@ Additional information on the Smartling Connector for WordPress can be found [he 3. Track translation status within WordPress from the Submissions Board. View overall progress of submitted translation requests as well as resend updated content. == Changelog == += 5.3.4 = +* Added support for Elementor mega-menu widget + = 5.3.3 = * Added support for Elementor gallery widget diff --git a/smartling-connector.php b/smartling-connector.php index 1abef387..e3eb7628 100755 --- a/smartling-connector.php +++ b/smartling-connector.php @@ -11,7 +11,7 @@ * Plugin Name: Smartling Connector * Plugin URI: https://www.smartling.com/products/automate/integrations/wordpress/ * Description: Integrate your WordPress site with Smartling to upload your content and download translations. - * Version: 5.3.3 + * Version: 5.3.4 * Author: Smartling * Author URI: https://www.smartling.com * License: GPL-2.0+ diff --git a/tests/Smartling/ContentTypes/Elementor/MegaMenuTest.php b/tests/Smartling/ContentTypes/Elementor/MegaMenuTest.php new file mode 100644 index 00000000..b89e3fa3 --- /dev/null +++ b/tests/Smartling/ContentTypes/Elementor/MegaMenuTest.php @@ -0,0 +1,192 @@ + '7399cf4', + 'elType' => 'widget', + 'widgetType' => 'mega-menu', + 'settings' => $settings, + 'elements' => [], + ]); + } + + public function testGetType(): void + { + $this->assertEquals('mega-menu', $this->makeWidget()->getType()); + } + + public function testGetRelatedReturnsSvgIconIds(): void + { + $widget = $this->makeWidget([ + 'menu_item_icon' => ['value' => ['url' => 'https://example.com/right.svg', 'id' => 21340], 'library' => 'svg'], + 'menu_toggle_icon_normal' => ['value' => ['url' => 'https://example.com/menu.svg', 'id' => 1203], 'library' => 'svg'], + 'menu_toggle_icon_active' => ['value' => ['url' => 'https://example.com/close.svg', 'id' => 21246], 'library' => 'svg'], + ]); + + $list = $widget->getRelated()->getRelatedContentList(); + + $this->assertArrayHasKey(ContentTypeHelper::POST_TYPE_ATTACHMENT, $list); + $this->assertContains(21340, $list[ContentTypeHelper::POST_TYPE_ATTACHMENT]); + $this->assertContains(1203, $list[ContentTypeHelper::POST_TYPE_ATTACHMENT]); + $this->assertContains(21246, $list[ContentTypeHelper::POST_TYPE_ATTACHMENT]); + } + + public function testGetRelatedSkipsEmptyIconId(): void + { + $widget = $this->makeWidget([ + 'menu_item_icon_active' => ['value' => '', 'library' => ''], + ]); + + $list = $widget->getRelated()->getRelatedContentList(); + + $this->assertArrayNotHasKey(ContentTypeHelper::POST_TYPE_ATTACHMENT, $list); + } + + public function testGetRelatedExtractsDynamicTagFromMenuItem(): void + { + $widget = $this->makeWidget([ + 'menu_items' => [ + [ + '_id' => '1dc9acb', + 'item_title' => 'Customers', + '__dynamic__' => [ + 'item_link' => '[elementor-tag id="70af237" name="internal-url" settings="%7B%22type%22%3A%22post%22%2C%22post_id%22%3A%228603%22%7D"]', + ], + ], + ], + ]); + + $list = $widget->getRelated()->getRelatedContentList(); + + $this->assertArrayHasKey(ContentTypeHelper::CONTENT_TYPE_POST, $list); + $this->assertContains(8603, $list[ContentTypeHelper::CONTENT_TYPE_POST]); + } + + public function testGetTranslatableStringsReturnsMenuName(): void + { + $widget = $this->makeWidget(['menu_name' => 'Menu']); + + $strings = $widget->getTranslatableStrings(); + + $this->assertArrayHasKey('7399cf4', $strings); + $this->assertEquals('Menu', $strings['7399cf4']['menu_name']); + } + + public function testGetTranslatableStringsReturnsItemTitles(): void + { + $widget = $this->makeWidget([ + 'menu_items' => [ + ['_id' => 'c819dfc', 'item_title' => 'Products', 'item_dropdown_content' => 'yes'], + ['_id' => '88cdd5a', 'item_title' => 'Solutions', 'item_dropdown_content' => 'yes'], + ['_id' => '8934ff0', 'item_title' => 'Support'], + ], + ]); + + $strings = $widget->getTranslatableStrings(); + + $this->assertEquals('Products', $strings['7399cf4']['menu_items/c819dfc']['item_title']); + $this->assertEquals('Solutions', $strings['7399cf4']['menu_items/88cdd5a']['item_title']); + $this->assertEquals('Support', $strings['7399cf4']['menu_items/8934ff0']['item_title']); + } + + public function testSetTargetContentAppliesTranslatedMenuName(): void + { + $proxy = $this->createMock(WordpressFunctionProxyHelper::class); + $externalContentElementor = $this->createMock(ExternalContentElementor::class); + $externalContentElementor->method('getWpProxy')->willReturn($proxy); + + $widget = $this->makeWidget(['menu_name' => 'Menu']); + + $strings = [ + '7399cf4' => ['menu_name' => 'Menü'], + ]; + + $result = $widget->setTargetContent( + $externalContentElementor, + new RelatedContentInfo([]), + $strings, + $this->createMock(SubmissionEntity::class), + )->toArray(); + + $this->assertEquals('Menü', $result['settings']['menu_name']); + } + + public function testSetTargetContentAppliesTranslatedItemTitles(): void + { + $proxy = $this->createMock(WordpressFunctionProxyHelper::class); + $externalContentElementor = $this->createMock(ExternalContentElementor::class); + $externalContentElementor->method('getWpProxy')->willReturn($proxy); + + $widget = $this->makeWidget([ + 'menu_items' => [ + ['_id' => 'c819dfc', 'item_title' => 'Products'], + ['_id' => '88cdd5a', 'item_title' => 'Solutions'], + ], + ]); + + $strings = [ + '7399cf4' => [ + 'menu_items' => [ + 'c819dfc' => ['item_title' => 'Produkte'], + '88cdd5a' => ['item_title' => 'Lösungen'], + ], + ], + ]; + + $result = $widget->setTargetContent( + $externalContentElementor, + new RelatedContentInfo([]), + $strings, + $this->createMock(SubmissionEntity::class), + )->toArray(); + + $this->assertEquals('Produkte', $result['settings']['menu_items'][0]['item_title']); + $this->assertEquals('Lösungen', $result['settings']['menu_items'][1]['item_title']); + } + + public function testSetTargetContentUpdatesIconId(): void + { + $sourceId = 21340; + $targetId = 99999; + + $proxy = $this->createMock(WordpressFunctionProxyHelper::class); + $externalContentElementor = $this->createMock(ExternalContentElementor::class); + $externalContentElementor->method('getTargetId') + ->with(0, $sourceId, 0, ContentTypeHelper::POST_TYPE_ATTACHMENT) + ->willReturn($targetId); + $externalContentElementor->method('getWpProxy')->willReturn($proxy); + + $submission = $this->createMock(SubmissionEntity::class); + $submission->method('getSourceBlogId')->willReturn(0); + $submission->method('getTargetBlogId')->willReturn(0); + + $widget = $this->makeWidget([ + 'menu_item_icon' => ['value' => ['url' => 'https://example.com/right.svg', 'id' => $sourceId], 'library' => 'svg'], + ]); + + $info = $widget->getRelated(); + + $result = $widget->setTargetContent( + $externalContentElementor, + $info, + [], + $submission, + )->toArray(); + + $this->assertEquals($targetId, $result['settings']['menu_item_icon']['value']['id']); + } +} From 2efbbcf99ca8c8ba54330c6dc506ab6162574ef2 Mon Sep 17 00:00:00 2001 From: Vitalii Solovei Date: Thu, 9 Apr 2026 13:56:00 +0200 Subject: [PATCH 2/4] add support for mega menu (WP-980) Co-Authored-By: Claude Sonnet 4.6 --- .../Elementor/Elements/MegaMenu.php | 25 ++++++++----- .../ContentTypes/Elementor/MegaMenuTest.php | 36 +++++++++++++++++++ .../ExternalContentElementorTest.php | 12 +++++++ 3 files changed, 64 insertions(+), 9 deletions(-) diff --git a/inc/Smartling/ContentTypes/Elementor/Elements/MegaMenu.php b/inc/Smartling/ContentTypes/Elementor/Elements/MegaMenu.php index 34262b3f..04160be2 100644 --- a/inc/Smartling/ContentTypes/Elementor/Elements/MegaMenu.php +++ b/inc/Smartling/ContentTypes/Elementor/Elements/MegaMenu.php @@ -8,7 +8,8 @@ use Smartling\Models\RelatedContentInfo; use Smartling\Submissions\SubmissionEntity; -class MegaMenu extends Unknown { +class MegaMenu extends Unknown +{ public function getType(): string { return 'mega-menu'; @@ -18,7 +19,12 @@ public function getRelated(): RelatedContentInfo { $return = parent::getRelated(); - foreach (['menu_item_icon/value/id', 'menu_item_icon_active/value/id', 'menu_toggle_icon_normal/value/id', 'menu_toggle_icon_active/value/id'] as $key) { + foreach ([ + 'menu_item_icon/value/id', + 'menu_item_icon_active/value/id', + 'menu_toggle_icon_normal/value/id', + 'menu_toggle_icon_active/value/id', + ] as $key) { $id = $this->getIntSettingByKey($key, $this->settings); if ($id !== null) { $return->addContent(new Content($id, ContentTypeHelper::POST_TYPE_ATTACHMENT), $this->id, "settings/$key"); @@ -39,20 +45,20 @@ public function getRelated(): RelatedContentInfo public function getTranslatableStrings(): array { - $return = []; + $return = parent::getTranslatableStrings(); if (array_key_exists('menu_name', $this->settings)) { - $return['menu_name'] = $this->settings['menu_name']; + $return[$this->id]['menu_name'] = $this->settings['menu_name']; } - foreach ($this->settings['menu_items'] ?? [] as $index => $item) { - $key = 'menu_items/' . ($item['_id'] ?? $index); + foreach ($this->settings['menu_items'] ?? [] as $item) { + $key = 'menu_items/' . ($item['_id']); if (array_key_exists('item_title', $item)) { - $return[$key]['item_title'] = $item['item_title']; + $return[$this->id][$key]['item_title'] = $item['item_title']; } } - return [$this->getId() => $return]; + return $return; } public function setTargetContent( @@ -60,7 +66,8 @@ public function setTargetContent( RelatedContentInfo $info, array $strings, SubmissionEntity $submission, - ): static { + ): static + { $this->raw = parent::setTargetContent($externalContentElementor, $info, $strings, $submission)->toArray(); $this->settings = $this->raw['settings'] ?? []; diff --git a/tests/Smartling/ContentTypes/Elementor/MegaMenuTest.php b/tests/Smartling/ContentTypes/Elementor/MegaMenuTest.php index b89e3fa3..4920cc02 100644 --- a/tests/Smartling/ContentTypes/Elementor/MegaMenuTest.php +++ b/tests/Smartling/ContentTypes/Elementor/MegaMenuTest.php @@ -158,6 +158,42 @@ public function testSetTargetContentAppliesTranslatedItemTitles(): void $this->assertEquals('Lösungen', $result['settings']['menu_items'][1]['item_title']); } + public function testSetTargetContentAppliesTranslatedMenuNameAndItemTitlesTogether(): void + { + $proxy = $this->createMock(WordpressFunctionProxyHelper::class); + $externalContentElementor = $this->createMock(ExternalContentElementor::class); + $externalContentElementor->method('getWpProxy')->willReturn($proxy); + + $widget = $this->makeWidget([ + 'menu_name' => 'Menu', + 'menu_items' => [ + ['_id' => 'c819dfc', 'item_title' => 'Products'], + ['_id' => '88cdd5a', 'item_title' => 'Solutions'], + ], + ]); + + $strings = [ + '7399cf4' => [ + 'menu_name' => 'Menü', + 'menu_items' => [ + 'c819dfc' => ['item_title' => 'Produkte'], + '88cdd5a' => ['item_title' => 'Lösungen'], + ], + ], + ]; + + $result = $widget->setTargetContent( + $externalContentElementor, + new RelatedContentInfo([]), + $strings, + $this->createMock(SubmissionEntity::class), + )->toArray(); + + $this->assertEquals('Menü', $result['settings']['menu_name']); + $this->assertEquals('Produkte', $result['settings']['menu_items'][0]['item_title']); + $this->assertEquals('Lösungen', $result['settings']['menu_items'][1]['item_title']); + } + public function testSetTargetContentUpdatesIconId(): void { $sourceId = 21340; diff --git a/tests/Smartling/ContentTypes/ExternalContentElementorTest.php b/tests/Smartling/ContentTypes/ExternalContentElementorTest.php index ba13c117..31b08ad0 100644 --- a/tests/Smartling/ContentTypes/ExternalContentElementorTest.php +++ b/tests/Smartling/ContentTypes/ExternalContentElementorTest.php @@ -271,6 +271,14 @@ public function extractElementorDataProvider(): array 'b966541/0841fb9/2dd1556/7399cf4/0b0011b/4ad1ceb/7a17916/caaf2cc/title' => 'Company – Let’s Talk', 'b966541/0841fb9/2dd1556/7399cf4/2012db2/e558561/text' => 'Contact Us', 'b966541/0841fb9/2dd1556/7399cf4/2012db2/0292a78/5227d76/icon_list/bb4f3a3/text' => 'China (CN)', + 'b966541/0841fb9/2dd1556/7399cf4/menu_name' => 'Menu', + 'b966541/0841fb9/2dd1556/7399cf4/menu_items/c819dfc/item_title' => 'Products', + 'b966541/0841fb9/2dd1556/7399cf4/menu_items/88cdd5a/item_title' => 'Solutions', + 'b966541/0841fb9/2dd1556/7399cf4/menu_items/1dc9acb/item_title' => 'Customers', + 'b966541/0841fb9/2dd1556/7399cf4/menu_items/0fc6785/item_title' => 'Resources', + 'b966541/0841fb9/2dd1556/7399cf4/menu_items/8934ff0/item_title' => 'Support', + 'b966541/0841fb9/2dd1556/7399cf4/menu_items/d7e704f/item_title' => 'Company', + 'b966541/0841fb9/2dd1556/7399cf4/menu_items/730c97a/item_title' => 'Contact Us', 'b966541/0841fb9/06f836b/9131e8d/text' => 'Contact Us', ], [ @@ -280,6 +288,9 @@ public function extractElementorDataProvider(): array 38, 40, 41, + 21340, + 1203, + 21246, 4830, ], ContentTypeHelper::CONTENT_TYPE_POST => [ @@ -288,6 +299,7 @@ public function extractElementorDataProvider(): array 2408, 17571, 9935, + 8603, ], ContentTypeHelper::CONTENT_TYPE_UNKNOWN => [ 31885, From 7069b069175422bd71df98b51340bc5a4a451792 Mon Sep 17 00:00:00 2001 From: Vitalii Solovei Date: Thu, 9 Apr 2026 14:11:01 +0200 Subject: [PATCH 3/4] add array key exists check, fix code style (WP-980) Co-Authored-By: Claude Sonnet 4.6 --- .../Elementor/Elements/MegaMenu.php | 19 +++++++++++-------- .../ContentTypes/Elementor/MegaMenuTest.php | 1 - 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/inc/Smartling/ContentTypes/Elementor/Elements/MegaMenu.php b/inc/Smartling/ContentTypes/Elementor/Elements/MegaMenu.php index 04160be2..464469df 100644 --- a/inc/Smartling/ContentTypes/Elementor/Elements/MegaMenu.php +++ b/inc/Smartling/ContentTypes/Elementor/Elements/MegaMenu.php @@ -8,8 +8,7 @@ use Smartling\Models\RelatedContentInfo; use Smartling\Submissions\SubmissionEntity; -class MegaMenu extends Unknown -{ +class MegaMenu extends Unknown { public function getType(): string { return 'mega-menu'; @@ -20,11 +19,11 @@ public function getRelated(): RelatedContentInfo $return = parent::getRelated(); foreach ([ - 'menu_item_icon/value/id', - 'menu_item_icon_active/value/id', - 'menu_toggle_icon_normal/value/id', - 'menu_toggle_icon_active/value/id', - ] as $key) { + 'menu_item_icon/value/id', + 'menu_item_icon_active/value/id', + 'menu_toggle_icon_normal/value/id', + 'menu_toggle_icon_active/value/id', + ] as $key) { $id = $this->getIntSettingByKey($key, $this->settings); if ($id !== null) { $return->addContent(new Content($id, ContentTypeHelper::POST_TYPE_ATTACHMENT), $this->id, "settings/$key"); @@ -52,7 +51,11 @@ public function getTranslatableStrings(): array } foreach ($this->settings['menu_items'] ?? [] as $item) { - $key = 'menu_items/' . ($item['_id']); + if (!array_key_exists('_id', $item)) { + $this->getLogger()->warning("Missing _id for menu item in $this->id"); + continue; + } + $key = "menu_items/{$item['_id']}"; if (array_key_exists('item_title', $item)) { $return[$this->id][$key]['item_title'] = $item['item_title']; } diff --git a/tests/Smartling/ContentTypes/Elementor/MegaMenuTest.php b/tests/Smartling/ContentTypes/Elementor/MegaMenuTest.php index 4920cc02..a838e151 100644 --- a/tests/Smartling/ContentTypes/Elementor/MegaMenuTest.php +++ b/tests/Smartling/ContentTypes/Elementor/MegaMenuTest.php @@ -7,7 +7,6 @@ use Smartling\ContentTypes\Elementor\Elements\MegaMenu; use Smartling\ContentTypes\ExternalContentElementor; use Smartling\Helpers\WordpressFunctionProxyHelper; -use Smartling\Models\Content; use Smartling\Models\RelatedContentInfo; use Smartling\Submissions\SubmissionEntity; From 013557add23b90187d3e39ef2d0086734acdd563 Mon Sep 17 00:00:00 2001 From: Vitalii Solovei Date: Thu, 9 Apr 2026 14:15:06 +0200 Subject: [PATCH 4/4] fix code style (WP-980) Co-Authored-By: Claude Sonnet 4.6 --- inc/Smartling/ContentTypes/Elementor/Elements/MegaMenu.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/inc/Smartling/ContentTypes/Elementor/Elements/MegaMenu.php b/inc/Smartling/ContentTypes/Elementor/Elements/MegaMenu.php index 464469df..c5720cba 100644 --- a/inc/Smartling/ContentTypes/Elementor/Elements/MegaMenu.php +++ b/inc/Smartling/ContentTypes/Elementor/Elements/MegaMenu.php @@ -69,8 +69,7 @@ public function setTargetContent( RelatedContentInfo $info, array $strings, SubmissionEntity $submission, - ): static - { + ): static { $this->raw = parent::setTargetContent($externalContentElementor, $info, $strings, $submission)->toArray(); $this->settings = $this->raw['settings'] ?? [];