Skip to content

Commit b309933

Browse files
authored
Merge pull request #3211 from andrewlimaza/glotpress
Custom translation updates (GlotPress)
2 parents 773ed6f + 275e67c commit b309933

File tree

3 files changed

+309
-0
lines changed

3 files changed

+309
-0
lines changed

includes/lib/glotpress-helper.php

+270
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
<?php
2+
/**
3+
* Main Traduttore Registry library.
4+
*
5+
* @since 1.0.0
6+
*
7+
* Copyright (c) 2018-2020 required (email: info@required.com)
8+
*
9+
* This program is free software; you can redistribute it and/or modify
10+
* it under the terms of the GNU General Public License, version 2 or, at
11+
* your discretion, any later version, as published by the Free
12+
* Software Foundation.
13+
*
14+
* This program is distributed in the hope that it will be useful,
15+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
* GNU General Public License for more details.
18+
*
19+
* You should have received a copy of the GNU General Public License
20+
* along with this program; if not, write to the Free Software
21+
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22+
*/
23+
24+
namespace PMPro\Required\Traduttore_Registry;
25+
26+
use DateTime;
27+
28+
const TRANSIENT_KEY_PLUGIN = 'traduttore-registry-plugins';
29+
const TRANSIENT_KEY_THEME = 'traduttore-registry-themes';
30+
31+
/**
32+
* Adds a new project to load translations for.
33+
*
34+
* @since 1.0.0
35+
*
36+
* @param string $type Project type. Either plugin or theme.
37+
* @param string $slug Project directory slug.
38+
* @param string $api_url Full GlotPress API URL for the project.
39+
*/
40+
function add_project( $type, $slug, $api_url ) {
41+
if ( ! has_action( 'init', __NAMESPACE__ . '\register_clean_translations_cache' ) ) {
42+
add_action( 'init', __NAMESPACE__ . '\register_clean_translations_cache', 9999 );
43+
}
44+
45+
/**
46+
* Short-circuits translations API requests for private projects.
47+
*/
48+
add_filter(
49+
'translations_api',
50+
static function ( $result, $requested_type, $args ) use ( $type, $slug, $api_url ) {
51+
if ( $type . 's' === $requested_type && $slug === $args['slug'] ) {
52+
return get_translations( $type, $args['slug'], $api_url );
53+
}
54+
55+
return $result;
56+
},
57+
10,
58+
3
59+
);
60+
61+
/**
62+
* Filters the translations transients to include the private plugin or theme.
63+
*
64+
* @see wp_get_translation_updates()
65+
*/
66+
add_filter(
67+
'site_transient_update_' . $type . 's',
68+
static function ( $value ) use ( $type, $slug, $api_url ) {
69+
if ( ! $value ) {
70+
$value = new \stdClass();
71+
}
72+
73+
if ( ! isset( $value->translations ) ) {
74+
$value->translations = [];
75+
}
76+
77+
$translations = get_translations( $type, $slug, $api_url );
78+
79+
if ( ! isset( $translations['translations'] ) ) {
80+
return $value;
81+
}
82+
83+
$installed_translations = get_installed_translations( $type );
84+
$locales = get_available_locales();
85+
86+
/** This filter is documented in wp-includes/update.php */
87+
$locales = apply_filters( $type . 's_update_check_locales', $locales );
88+
$active_locales = array_unique( $locales );
89+
90+
foreach ( (array) $translations['translations'] as $translation ) {
91+
if ( ! \in_array( $translation['language'], $active_locales, true ) ) {
92+
continue;
93+
}
94+
95+
if ( $translation['updated'] && isset( $installed_translations[ $slug ][ $translation['language'] ] ) ) {
96+
$local = sanitize_date( $installed_translations[ $slug ][ $translation['language'] ]['PO-Revision-Date'] );
97+
$remote = new DateTime( $translation['updated'] );
98+
99+
if ( $local >= $remote ) {
100+
continue;
101+
}
102+
}
103+
104+
$translation['type'] = $type;
105+
$translation['slug'] = $slug;
106+
107+
$value->translations[] = $translation;
108+
}
109+
110+
return $value;
111+
}
112+
);
113+
}
114+
115+
/**
116+
* Registers actions for clearing translation caches.
117+
*
118+
* @since 1.1.0
119+
*/
120+
function register_clean_translations_cache() {
121+
$clear_plugin_translations = static function() {
122+
clean_translations_cache( 'plugin' );
123+
};
124+
$clear_theme_translations = static function() {
125+
clean_translations_cache( 'theme' );
126+
};
127+
128+
add_action( 'set_site_transient_update_plugins', $clear_plugin_translations );
129+
add_action( 'delete_site_transient_update_plugins', $clear_plugin_translations );
130+
131+
add_action( 'set_site_transient_update_themes', $clear_theme_translations );
132+
add_action( 'delete_site_transient_update_themes', $clear_theme_translations );
133+
}
134+
135+
/**
136+
* Clears existing translation cache for a given type.
137+
*
138+
* @since 1.1.0
139+
*
140+
* @param string $type Project type. Either plugin or theme.
141+
*/
142+
function clean_translations_cache( $type ) {
143+
$transient_key = constant( __NAMESPACE__ . '\TRANSIENT_KEY_' . strtoupper( $type ) );
144+
$translations = get_site_transient( $transient_key );
145+
146+
if ( ! \is_object( $translations ) ) {
147+
return;
148+
}
149+
150+
/*
151+
* Don't delete the cache if the transient gets changed multiple times
152+
* during a single request. Set cache lifetime to maximum 15 seconds.
153+
*/
154+
$cache_lifespan = 15;
155+
$time_not_changed = isset( $translations->_last_checked ) && ( time() - $translations->_last_checked ) > $cache_lifespan;
156+
157+
if ( ! $time_not_changed ) {
158+
return;
159+
}
160+
161+
delete_site_transient( $transient_key );
162+
}
163+
164+
/**
165+
* Gets the translations for a given project.
166+
*
167+
* @since 1.0.0
168+
*
169+
* @param string $type Project type. Either plugin or theme.
170+
* @param string $slug Project directory slug.
171+
* @param string $url Full GlotPress API URL for the project.
172+
* @return array Translation data.
173+
*/
174+
function get_translations( $type, $slug, $url ) {
175+
$transient_key = constant( __NAMESPACE__ . '\TRANSIENT_KEY_' . strtoupper( $type ) );
176+
$translations = get_site_transient( $transient_key );
177+
178+
if ( ! \is_object( $translations ) ) {
179+
$translations = new \stdClass();
180+
}
181+
182+
183+
if ( isset( $translations->{$slug} ) && \is_array( $translations->{$slug} ) ) {
184+
return $translations->{$slug};
185+
}
186+
187+
$result = json_decode( wp_remote_retrieve_body( wp_remote_get( $url, [ 'timeout' => 2 ] ) ), true );
188+
189+
if ( ! \is_array( $result ) ) {
190+
// Cache an empty result in case of a failure
191+
// and retry on next update check.
192+
$result = [];
193+
}
194+
195+
$translations->{$slug} = $result;
196+
$translations->_last_checked = time();
197+
198+
set_site_transient( $transient_key, $translations );
199+
200+
return $result;
201+
}
202+
203+
/**
204+
* Sanitizes a date string.
205+
*
206+
* DateTime fails to parse date strings that contain brackets, such as
207+
* “Tue Dec 22 2015 12:52:19 GMT+0100 (West-Europa)”, which appears in
208+
* PO-Revision-Date headers. Sanitization ensures such date headers are
209+
* parsed correctly into DateTime instances.
210+
*
211+
* @since 2.1.0
212+
*
213+
* @param string $date_string The date string to sanitize.
214+
* @return \DateTime Date from string if parsable, otherwise the Unix epoch.
215+
*/
216+
function sanitize_date( $date_string ) {
217+
$date_and_timezone = explode( '(', $date_string );
218+
$date_no_timezone = trim( $date_and_timezone[0] );
219+
220+
try {
221+
$date = new DateTime( $date_no_timezone );
222+
} catch ( \Exception $e ) {
223+
return new DateTime( '1970-01-01' );
224+
}
225+
226+
return $date;
227+
}
228+
229+
/**
230+
* Gets the installed translations.
231+
*
232+
* Results are cached.
233+
*
234+
* @since 2.2.0
235+
*
236+
* @see wp_get_installed_translations()
237+
*
238+
* @param string $type Project type. Either plugin or theme.
239+
* @return array Translation data.
240+
*/
241+
function get_installed_translations( $type ) {
242+
static $cache = [];
243+
244+
if ( ! isset( $cache[ $type ] ) ) {
245+
$cache[ $type ] = wp_get_installed_translations( $type . 's' );
246+
}
247+
248+
return $cache[ $type ];
249+
}
250+
251+
/**
252+
* Gets all available and installed locales.
253+
*
254+
* Results are cached.
255+
*
256+
* @since 2.2.0
257+
*
258+
* @see get_available_languages()
259+
*
260+
* @return array List of installed locales.
261+
*/
262+
function get_available_locales() {
263+
static $cache = null;
264+
265+
if ( ! isset( $cache ) ) {
266+
$cache = array_values( get_available_languages() );
267+
}
268+
269+
return $cache;
270+
}

includes/localization.php

+38
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,41 @@ function pmpro_translate_billing_period($period, $number = 1)
5656
return __("Years", 'paid-memberships-pro' );
5757
}
5858
}
59+
60+
/**
61+
* Handle translation updates from our own translation server.
62+
* @since TBD
63+
*/
64+
function pmpro_check_for_translations() {
65+
// Run it only on a PMPro page in the admin.
66+
if ( ! current_user_can( 'update_plugins' ) ) {
67+
return;
68+
}
69+
70+
$is_pmpro_admin = ! empty( $_REQUEST['page'] ) && strpos( $_REQUEST['page'], 'pmpro' ) !== false;
71+
$is_update_or_plugins_page = strpos( $_SERVER['REQUEST_URI'], 'update-core.php' ) !== false || strpos( $_SERVER['REQUEST_URI'], 'plugins.php' ) !== false;
72+
73+
// Only run this check when we're in the PMPro Page or plugins/update page to save some resources.
74+
if ( ! $is_pmpro_admin && ! $is_update_or_plugins_page ) {
75+
return;
76+
}
77+
78+
$pmpro_add_ons = pmpro_getAddOns();
79+
foreach( $pmpro_add_ons as $add_on ) {
80+
// Skip if the plugin isn't active.
81+
if ( ! pmpro_is_plugin_active( $add_on['plugin'] ) ) {
82+
continue;
83+
}
84+
85+
$plugin_slug = $add_on['Slug'];
86+
87+
// This uses the Traduttore plugin to check for translations for locales etc.
88+
PMPro\Required\Traduttore_Registry\add_project(
89+
'plugin',
90+
$plugin_slug,
91+
'https://translate.strangerstudios.com/api/translations/' . $plugin_slug
92+
);
93+
}
94+
95+
}
96+
add_action( 'admin_init', 'pmpro_check_for_translations' );

paid-memberships-pro.php

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
require_once( PMPRO_DIR . '/includes/sessions.php' ); // start/close PHP session vars
3131

3232
require_once( PMPRO_DIR . '/includes/localization.php' ); // localization functions
33+
require_once( PMPRO_DIR . '/includes/lib/glotpress-helper.php' ); // handles translation updates logic from our own server.
3334
require_once( PMPRO_DIR . '/includes/lib/name-parser.php' ); // parses "Jason Coleman" into firstname=>Jason, lastname=>Coleman
3435
require_once( PMPRO_DIR . '/includes/functions.php' ); // misc functions used by the plugin
3536
require_once( PMPRO_DIR . '/includes/updates.php' ); // database and other updates

0 commit comments

Comments
 (0)