PluginProbe ʕ •ᴥ•ʔ
MailPoet – Newsletters, Email Marketing, and Automation / 5.34.2
MailPoet – Newsletters, Email Marketing, and Automation v5.34.2
5.34.2 5.34.1 5.34.0 5.33.1 5.33.0 5.32.0 5.31.0 5.30.0 5.29.0 5.28.1 5.28.0 5.27.0 5.26.0 5.26.1 5.25.0 5.24.0 4.43.0 4.43.1 4.44.0 4.44.1 4.45.0 4.46.0 4.47.0 4.48.0 4.48.1 4.48.2 4.49.0 4.49.1 4.5.0 4.5.1 4.5.2 4.50.0 4.50.1 4.51.0 4.51.1 4.51.2 4.52.0 4.53.0 4.54.0 4.55.0 4.56.0 4.57.0 4.58.0 4.58.1 4.58.2 4.6.0 4.6.1 4.6.2 4.7.0 4.7.1 4.8.0 4.8.1 4.9.0 5.0.0 5.0.1 5.0.2 5.1.0 5.1.1 5.10.0 5.10.1 5.11.0 5.12.0 5.12.1 5.12.10 5.12.11 5.12.12 5.12.13 5.12.2 5.12.3 5.12.4 5.12.5 5.12.6 5.12.7 5.12.8 5.12.9 5.13.0 5.13.1 5.13.2 5.14.0 5.14.1 5.14.2 5.14.3 5.15.0 5.15.1 5.16.0 5.16.1 5.16.2 5.16.3 5.16.4 5.17.0 5.17.1 5.17.2 5.17.3 5.17.4 5.17.5 5.17.6 5.18.0 5.19.0 5.2.0 5.2.1 5.2.2 5.2.3 5.20.0 5.21.0 5.21.1 5.21.2 5.21.3 5.22.0 5.22.1 5.22.2 5.22.3 5.22.4 5.23.0 5.23.1 5.23.2 5.3.0 5.3.1 5.3.2 5.3.3 5.3.4 5.3.5 5.3.6 5.3.7 5.4.0 5.4.1 5.4.2 5.5.0 5.5.1 5.5.2 5.6.0 5.6.1 5.6.2 5.6.3 5.6.4 5.7.0 5.7.1 5.8.0 5.8.1 5.9.0 3.0.0-beta.15 3.7.1 3.0.0-beta.16 3.7.2 3.0.0-beta.17 3.7.3 3.0.0-beta.18 3.7.4 3.0.0-beta.19 3.7.5 3.0.0-beta.2 3.7.6 3.0.0-beta.20 3.7.8 3.0.0-beta.21 3.70.0 3.0.0-beta.22 3.71.0 3.0.0-beta.23 3.71.1 3.0.0-beta.23.1 3.71.2 3.0.0-beta.23.2 3.71.3 3.0.0-beta.24 3.72.0 3.0.0-beta.25 3.73.0 3.0.0-beta.26 3.73.1 3.0.0-beta.27 3.73.2 3.0.0-beta.28 3.74.0 3.0.0-beta.29 3.74.1 3.0.0-beta.3 3.74.2 3.0.0-beta.30 3.74.3 3.0.0-beta.31 3.75.0 3.0.0-beta.32 3.75.1 3.0.0-beta.33 3.76.0 3.0.0-beta.33.1 3.77.0 3.0.0-beta.34.0.0 3.77.1 3.0.0-beta.36.0.0 3.78.0 3.0.0-beta.36.0.1 3.79.0 3.0.0-beta.36.2.0 3.8 3.0.0-beta.36.3.0 3.8.1 3.0.0-beta.36.3.1 3.8.2 3.0.0-beta.37.0.0 3.8.3 3.0.0-beta.4 3.8.4 3.0.0-beta.5 3.8.5 3.0.0-beta.6 3.8.6 3.0.0-beta.7 3.80.0 3.0.0-beta.7.1 3.81.0 3.0.0-beta.8 3.82.0 3.0.0-beta.9 3.83.0 3.0.0-rc.1.0.0 3.84.0 3.0.0-rc.1.0.1 3.84.1 3.0.0-rc.1.0.2 3.85.0 3.0.0-rc.1.0.3 3.85.1 3.0.0-rc.1.0.4 3.86.0 3.0.0-rc.2.0.0 3.87.0 3.0.0-rc.2.0.1 3.87.1 3.0.0-rc.2.0.2 3.87.2 3.0.0-rc.2.0.3 3.88.0 3.0.1 3.88.1 3.0.2 3.88.2 3.0.3 3.89.0 3.0.4 3.89.1 3.0.5 3.89.2 3.0.6 3.89.3 3.0.7 3.89.4 3.0.8 3.9.0 3.0.9 3.9.1 3.1.0 3.90.0 3.10 3.90.1 3.10.1 3.90.2 3.100.0 3.91.0 3.100.1 3.91.1 3.100.2 3.92.0 3.101.0 3.92.1 3.101.1 3.93.0 3.102.0 3.93.1 3.102.1 3.94.0 3.103.0 3.95.0 3.103.1 3.95.1 3.11.0 3.96.0 3.11.1 3.96.1 3.11.2 3.97.0 3.11.3 3.98.0 3.11.4 3.98.1 3.11.5 3.99.0 3.12.0 3.99.1 3.12.1 4.0.0 3.13.0 4.0.1 3.14.0 4.1.0 3.14.1 4.1.1 3.15.0 4.10.0 3.16.0 4.11.0 3.16.1 4.11.1 3.16.2 4.12.0 3.16.3 4.12.1 3.17.0 4.12.2 3.17.1 4.13.0 3.17.2 4.14.0 3.18.0 4.15.0 3.18.1 4.16.0 3.18.2 4.17.0 3.19.0 4.17.1 3.19.1 4.18.0 3.19.2 4.18.1 3.19.3 4.19.0 3.2.0 4.2.0 3.2.1 4.20.0 3.2.2 4.20.1 3.2.3 4.20.2 3.2.4 4.21.0 3.2.5 4.22.0 3.20.0 4.22.1 3.21.0 4.22.2 3.21.1 4.23.0 3.22.0 4.24.0 3.23.0 4.25.0 3.23.1 4.26.0 3.23.2 4.26.1 3.24.0 4.27.0 3.25.0 4.28.0 3.25.1 4.29.0 3.26.0 4.3.0 3.26.1 4.3.1 3.27.0 4.30.0 3.28.0 4.31.0 3.29.0 4.31.1 3.3.0 4.32.0 3.3.1 4.33.0 3.3.2 4.34.0 3.3.3 4.35.0 3.3.4 4.35.1 3.3.5 4.36.0 3.3.6 4.37.0 3.30.0 4.38.0 3.31.0 4.39.0 3.31.1 4.4.0 3.32.0 4.40.0 3.32.1 4.41.0 3.32.2 4.41.1 3.33.0 4.41.2 3.34.0 4.41.3 3.34.1 4.42.0 3.34.2 4.42.1 3.34.3 3.34.4 3.35.0 3.35.1 3.35.3 3.35.4 3.36.0 3.37.0 3.37.1 3.37.2 3.37.3 3.38.0 3.38.1 3.39.0 3.39.1 3.39.2 3.4.0 3.4.1 3.4.2 3.4.3 3.4.4 3.40.0 3.40.1 3.41.0 3.41.1 3.41.2 3.42.0 3.42.1 3.42.2 3.42.3 3.43.0 3.43.1 3.44.0 3.45.0 3.45.1 3.46.0 3.46.1 3.46.10 3.46.11 3.46.12 3.46.13 3.46.14 3.46.2 3.46.3 3.46.4 3.46.5 3.46.6 3.46.7 3.46.8 3.46.9 3.47.0 3.47.1 3.47.10 3.47.11 3.47.2 3.47.3 3.47.5 3.47.6 3.47.7 3.47.9 3.48.0 3.48.1 3.49.0 3.49.1 3.5.0 3.5.1 3.50.0 3.51.0 3.51.1 3.51.2 3.52.0 3.53.0 3.54.0 3.54.1 3.54.2 3.54.3 3.55.0 3.55.1 3.56.0 3.56.1 3.56.2 3.57.0 3.57.1 3.58.0 3.59.0 3.59.1 3.59.2 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.60.0 3.60.1 3.60.10 3.60.11 3.60.12 3.60.2 3.60.3 3.60.4 3.60.6 3.60.7 3.60.8 3.60.9 3.61.0 3.62.0 3.62.1 3.63.0 3.64.0 3.64.1 3.64.2 3.64.3 3.65.0 trunk 3.65.1 3.0.0 3.66.0 3.0.0-beta.1 3.67.0 3.0.0-beta.10 3.67.1 3.0.0-beta.11 3.68.0 3.0.0-beta.12 3.69.0 3.0.0-beta.13 3.69.1 3.0.0-beta.14 3.7.0
mailpoet / lib / Config / TranslationUpdater.php
mailpoet / lib / Config Last commit date
PopulatorData 2 months ago AccessControl.php 2 years ago Activator.php 2 months ago AssetsLoader.php 2 weeks ago Capabilities.php 2 months ago Changelog.php 2 months ago DeactivationPoll.php 3 years ago DeferredAdminNotices.php 2 months ago Env.php 6 months ago Hooks.php 4 weeks ago HooksWooCommerce.php 1 month ago Initializer.php 4 weeks ago Installer.php 10 months ago Localizer.php 3 years ago Menu.php 1 month ago PersonalDataErasers.php 1 month ago PersonalDataExporters.php 1 month ago PluginActivatedHook.php 3 years ago Populator.php 2 weeks ago PrivacyPolicy.php 1 month ago Renderer.php 1 year ago RendererFactory.php 3 years ago RequirementsChecker.php 2 months ago Router.php 2 months ago ServicesChecker.php 3 years ago Shortcodes.php 1 month ago SilentUpgraderSkin.php 3 years ago SubscriberChangesNotifier.php 2 months ago TranslationUpdater.php 3 months ago TwigEnvironment.php 1 year ago TwigFileSystemCache.php 3 years ago Updater.php 4 days ago index.php 3 years ago
TranslationUpdater.php
215 lines
1 <?php declare(strict_types = 1);
2
3 namespace MailPoet\Config;
4
5 if (!defined('ABSPATH')) exit;
6
7
8 use MailPoet\WP\Functions as WPFunctions;
9 use MailPoetVendor\Carbon\CarbonImmutable;
10 use Tracy\Debugger;
11 use Tracy\ILogger;
12 use WP_Error;
13
14 class TranslationUpdater {
15 const API_UPDATES_BASE_URI = 'https://translate.wordpress.com/api/translations-updates/mailpoet/';
16 const MAILPOET_FREE_DOT_COM_PROJECT_ID = 'MailPoet - MailPoet';
17 const TRANSIENT_KEY_PREFIX = 'mailpoet_translation_updates_';
18 const TRANSIENT_EXPIRATION = 300; // 5 minutes
19
20 /** @var WPFunctions */
21 private $wpFunctions;
22
23 /** @var string */
24 private $freeSlug;
25
26 /** @var string */
27 private $freeVersion;
28
29 /** @var string */
30 private $premiumSlug;
31
32 /** @var string|null */
33 private $premiumVersion;
34
35 public function __construct(
36 WPFunctions $wpFunctions,
37 string $freeSlug,
38 string $freeVersion,
39 string $premiumSlug,
40 ?string $premiumVersion
41 ) {
42 $this->wpFunctions = $wpFunctions;
43 $this->freeSlug = $freeSlug;
44 $this->freeVersion = $freeVersion;
45 $this->premiumSlug = $premiumSlug;
46 $this->premiumVersion = $premiumVersion;
47 }
48
49 public function init(): void {
50 $this->wpFunctions->addFilter('pre_set_site_transient_update_plugins', [$this, 'checkForTranslations'], 21);
51 }
52
53 public function checkForTranslations($transient) {
54 if (!$transient instanceof \stdClass) {
55 $transient = new \stdClass;
56 }
57
58 $locales = $this->getLocales();
59 if (empty($locales)) {
60 return $transient;
61 }
62
63 $languagePacksData = $this->getAvailableTranslations($locales);
64 $translations = $this->selectUpdatesToInstall($languagePacksData);
65 // We want to ignore translations from .org in case a translation pack for the same locale is available from .com
66 $dotOrgTranslations = $this->removeDuplicateTranslationsFromOrg($transient->translations ?? [], $languagePacksData[$this->freeSlug] ?? []);
67 $transient->translations = array_merge($dotOrgTranslations ?? [], $translations);
68 return $transient;
69 }
70
71 /**
72 * Find available languages
73 * @return array
74 */
75 private function getLocales(): array {
76 $locales = array_values($this->wpFunctions->getAvailableLanguages());
77 $locales = apply_filters('plugins_update_check_locales', $locales);
78 return array_unique($locales);
79 }
80
81 private function getAvailableTranslations(array $locales): array {
82 $requestBody = [
83 'locales' => $locales,
84 'plugins' => [
85 $this->freeSlug => ['version' => $this->freeVersion],
86 ],
87 ];
88 if ($this->premiumVersion) {
89 $requestBody['plugins'][$this->premiumSlug] = ['version' => $this->premiumVersion];
90 }
91
92 $cacheKey = self::TRANSIENT_KEY_PREFIX . md5(serialize($requestBody));
93 $rawResponse = $this->wpFunctions->getTransient($cacheKey);
94 if (!$rawResponse) {
95 $rawResponse = $this->fetchApiResponse($requestBody);
96 if ($rawResponse instanceof WP_Error) {
97 // Don't continue if there was an error.
98 $this->logError("MailPoet: Failed to fetch translations from WordPress.com API with error: " . $rawResponse->get_error_message());
99 return [];
100 }
101
102 $responseCode = $this->wpFunctions->wpRemoteRetrieveResponseCode($rawResponse);
103 // Wait a couple of seconds and retry when 429 is returned.
104 if ($responseCode === 429) {
105 sleep(2);
106 $rawResponse = $this->fetchApiResponse($requestBody);
107 if ($rawResponse instanceof WP_Error) {
108 // Don't continue if there was an error.
109 $this->logError("MailPoet: Failed retrying to fetch translations from WordPress.com API with error: " . $rawResponse->get_error_message());
110 return [];
111 }
112 $responseCode = $this->wpFunctions->wpRemoteRetrieveResponseCode($rawResponse);
113 }
114 // Don't continue when API request failed.
115 if ($responseCode !== 200) {
116 $this->logError("MailPoet: Failed to fetch translations from WordPress.com API with $responseCode and response message: " . $this->wpFunctions->wpRemoteRetrieveResponseMessage($rawResponse));
117 return [];
118 }
119 $this->wpFunctions->setTransient($cacheKey, $rawResponse, self::TRANSIENT_EXPIRATION);
120 }
121 $response = json_decode($this->wpFunctions->wpRemoteRetrieveBody($rawResponse), true);
122 if (
123 !is_array($response)
124 || (array_key_exists('success', $response) && $response['success'] === false)
125 || !array_key_exists('data', $response)
126 || !is_array($response['data'])
127 ) {
128 $this->logError("MailPoet: Failed to fetch translations from WordPress.com API with code 200 and response: " . json_encode($response));
129 return [];
130 }
131 return $response['data'];
132 }
133
134 private function selectUpdatesToInstall(array $responseData) {
135 $installedTranslations = $this->wpFunctions->wpGetInstalledTranslations('plugins');
136 $translationsToInstall = [];
137 foreach ($responseData as $pluginName => $languagePacks) {
138 foreach ($languagePacks as $languagePack) {
139 // Check revision date if translation is already installed.
140 if (array_key_exists($pluginName, $installedTranslations) && array_key_exists($languagePack['wp_locale'], $installedTranslations[$pluginName])) {
141 $installedFromWpOrg = ($pluginName === $this->freeSlug) && ($installedTranslations[$pluginName][$languagePack['wp_locale']]['Project-Id-Version'] !== self::MAILPOET_FREE_DOT_COM_PROJECT_ID);
142 $installedTranslationRevisionTime = new CarbonImmutable($installedTranslations[$pluginName][$languagePack['wp_locale']]['PO-Revision-Date']);
143 $newTranslationRevisionTime = new CarbonImmutable($languagePack['last_modified']);
144
145 // In case installed translation pack comes from WP.org make sure that the one coming from WP.com has newer date
146 if ($installedFromWpOrg && $newTranslationRevisionTime <= $installedTranslationRevisionTime) {
147 $languagePack['last_modified'] = $installedTranslationRevisionTime->addSecond()->toDateTimeString();
148 $newTranslationRevisionTime = new CarbonImmutable($languagePack['last_modified']);
149 }
150
151 // Skip if translation language pack is not newer than what is installed already.
152 if ($newTranslationRevisionTime <= $installedTranslationRevisionTime) {
153 continue;
154 }
155 }
156 $translationsToInstall[] = [
157 'type' => 'plugin',
158 'slug' => $pluginName,
159 'language' => $languagePack['wp_locale'],
160 'version' => $languagePack['version'],
161 'updated' => $languagePack['last_modified'],
162 'package' => $languagePack['package'],
163 'autoupdate' => true,
164 ];
165 }
166 }
167
168 return $translationsToInstall;
169 }
170
171 private function removeDuplicateTranslationsFromOrg(array $translationsDotOrg, array $translationsDotComData) {
172 $localesAvailableFromDotCom = array_unique(array_column($translationsDotComData, 'wp_locale'));
173 return array_filter($translationsDotOrg, function ($translation) use($localesAvailableFromDotCom) {
174 if (
175 $translation['slug'] !== $this->freeSlug
176 || !in_array($translation['language'], $localesAvailableFromDotCom, true)
177 ) {
178 return true;
179 }
180 return false;
181 });
182 }
183
184 protected function logError(string $message): void {
185 if (class_exists(Debugger::class) && Debugger::$logDirectory) {
186 Debugger::log($message, ILogger::ERROR);
187 }
188 if (function_exists('error_log')) {
189 // phpcs:disable QITStandard.PHP.DebugCode.DebugFunctionFound
190 error_log($message); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged
191 // phpcs:enable QITStandard.PHP.DebugCode.DebugFunctionFound
192 }
193 }
194
195 /**
196 * @return array|\WP_Error
197 */
198 private function fetchApiResponse(array $requestBody) {
199 // Ten seconds, plus one extra second for every 10 locales.
200 $timeout = 10 + (int)(count($requestBody['locales']) / 10);
201
202 $body = wp_json_encode($requestBody);
203 if ($body === false) {
204 return new WP_Error('wp_json_encode_error', 'Failed to encode request body to retrieve translations');
205 }
206
207 $response = $this->wpFunctions->wpRemotePost(self::API_UPDATES_BASE_URI, [
208 'body' => $body,
209 'headers' => ['Content-Type: application/json'],
210 'timeout' => $timeout,
211 ]);
212 return $response;
213 }
214 }
215