PluginProbe
Subscriptions for WooCommerce with Stripe Recurring Payments / 2.4.0
Subscriptions for WooCommerce with Stripe Recurring Payments v2.4.0
2.4.0 2.3.0 2.2.0 2.1.0 2.0.0 1.11.2 1.11.1 1.11.0 1.10.9 1.10.8 1.10.7 1.10.6 1.10.5 1.10.4 1.10.3 1.10.2 1.10.1 1.10.0 1.9.6 1.9.5 trunk 1.3.0 1.3.1 1.3.2 1.4.0 All 65 releases
← All changes | includes/Illuminate/Plans/PlanRepository.php +81 -12 2.3.0 → 2.4.0 View file →
@@ -6,8 +6,10 @@
6 6 */
7 7
8 8 namespace SpringDevs\Subscription\Illuminate\Plans;
9 9
10 +use SpringDevs\Subscription\Illuminate\Multilingual;
11 +
10 12 /**
11 13 * Plan data-access layer.
12 14 *
13 15 * Read/write repository over the three plan tables (subscrpt_plan_group,
@@ -152,30 +154,36 @@
152 154 if ( ! $product_id ) {
153 155 return array();
154 156 }
155 157
156 - $cache_key = 'product_' . $product_id;
157 - $resolved = wp_cache_get( $cache_key, self::CACHE_GROUP );
158 + // A translation is a different post from the product its plans were
159 + // attached to, so read across every language of it. One cache entry
160 + // serves them all. Without WPML / Polylang this is just the product.
161 + $product_ids = Multilingual::translation_ids( $product_id );
162 + $cache_key = 'product_' . Multilingual::canonical_id( $product_id );
163 + $resolved = wp_cache_get( $cache_key, self::CACHE_GROUP );
158 164
159 165 if ( false === $resolved ) {
160 - $batch = self::resolve_for_products( array( $product_id ) );
161 - $resolved = $batch[ $product_id ] ?? array();
166 + $batch = self::resolve_for_products( $product_ids );
167 + $resolved = count( $product_ids ) > 1 ? self::merge_translation_rows( $batch ) : ( $batch[ $product_id ] ?? array() );
162 168 wp_cache_set( $cache_key, $resolved, self::CACHE_GROUP );
163 169 }
164 170
165 171 if ( $variation_id ) {
166 - $variation_id = absint( $variation_id );
167 - $resolved = array_values(
172 + $variation_ids = Multilingual::translation_ids( $variation_id );
173 + $resolved = array_values(
168 174 array_filter(
169 175 $resolved,
170 - static function ( $row ) use ( $variation_id ) {
176 + static function ( $row ) use ( $variation_ids ) {
171 177 // vid 0 = applies to the parent / all variations.
172 - return 0 === (int) $row['vid'] || $variation_id === (int) $row['vid'];
178 + return 0 === (int) $row['vid'] || in_array( (int) $row['vid'], $variation_ids, true );
173 179 }
174 180 )
175 181 );
176 182 }
177 183
184 + $resolved = Multilingual::translate_plan_rows( $resolved );
185 +
178 186 /**
179 187 * Filter the resolved plan rows for a product.
180 188 *
181 189 * Pro's extension point to inject taxonomy- and variation-resolved rows.
@@ -263,8 +271,42 @@
263 271 return $map;
264 272 }
265 273
266 274 /**
275 + * Merge the plan rows of every language of one product into one list.
276 + *
277 + * A plan attached in two languages would otherwise be offered twice, so a
278 + * plan appears once per variation it applies to, in group → plan order.
279 + *
280 + * @param array<int, array> $batch Map of product id → rows, from {@see resolve_for_products()}.
281 + *
282 + * @return array
283 + */
284 + private static function merge_translation_rows( array $batch ) {
285 + $merged = array();
286 +
287 + foreach ( $batch as $rows ) {
288 + foreach ( $rows as $row ) {
289 + $vid = (int) $row['vid'] ? Multilingual::canonical_id( $row['vid'] ) : 0;
290 + $key = (int) $row['plan_id'] . ':' . $vid;
291 +
292 + if ( ! isset( $merged[ $key ] ) ) {
293 + $merged[ $key ] = $row;
294 + }
295 + }
296 + }
297 +
298 + usort(
299 + $merged,
300 + static function ( $a, $b ) {
301 + return array( (int) $a['plan_group_id'], (int) $a['plan_id'] ) <=> array( (int) $b['plan_group_id'], (int) $b['plan_id'] );
302 + }
303 + );
304 +
305 + return $merged;
306 + }
307 +
308 + /**
267 309 * Decode a JSON column to an array, tolerating null / plain strings.
268 310 *
269 311 * @param string|null $value Raw column value.
270 312 *
@@ -306,14 +348,17 @@
306 348 $relation_table = self::relation_table();
307 349 $plan_table = self::plan_table();
308 350 $group_table = self::group_table();
309 351
310 - $where = 'r.type = %d AND r.oid = %d';
311 - $params = array( self::REL_PRODUCT, $product_id );
352 + // Every language of the product, as the storefront resolver reads them.
353 + $product_ids = Multilingual::translation_ids( $product_id );
354 + $where = 'r.type = %d AND r.oid IN (' . implode( ',', array_fill( 0, count( $product_ids ), '%d' ) ) . ')';
355 + $params = array_merge( array( self::REL_PRODUCT ), $product_ids );
312 356
313 357 if ( $variation_id ) {
314 - $where .= ' AND r.vid = %d';
315 - $params[] = absint( $variation_id );
358 + $variation_ids = Multilingual::translation_ids( $variation_id );
359 + $where .= ' AND r.vid IN (' . implode( ',', array_fill( 0, count( $variation_ids ), '%d' ) ) . ')';
360 + $params = array_merge( $params, $variation_ids );
316 361 }
317 362
318 363 // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
319 364 $rows = $wpdb->get_results(
@@ -419,8 +464,30 @@
419 464 return $row ? self::decode_plan_row( $row ) : null;
420 465 }
421 466
422 467 /**
468 + * Every non-empty group and plan title, any status, for string translation.
469 + *
470 + * @return array{groups:array<int,string>,plans:array<int,string>} Titles by id.
471 + */
472 + public static function get_titles() {
473 + global $wpdb;
474 +
475 + $group_table = self::group_table();
476 + $plan_table = self::plan_table();
477 +
478 + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery
479 + $groups = $wpdb->get_results( "SELECT id, title FROM {$group_table} WHERE title <> '' ORDER BY id ASC", ARRAY_A );
480 + $plans = $wpdb->get_results( "SELECT id, title FROM {$plan_table} WHERE title <> '' ORDER BY id ASC", ARRAY_A );
481 + // phpcs:enable
482 +
483 + return array(
484 + 'groups' => array_column( (array) $groups, 'title', 'id' ),
485 + 'plans' => array_column( (array) $plans, 'title', 'id' ),
486 + );
487 + }
488 +
489 + /**
423 490 * Fetch relations for a plan term.
424 491 *
425 492 * @param int $plan_id Plan id.
426 493 *
@@ -1023,8 +1090,10 @@
1023 1090 $product_id = absint( $product_id );
1024 1091
1025 1092 if ( $product_id ) {
1026 1093 wp_cache_delete( 'product_' . $product_id, self::CACHE_GROUP );
1094 + // The key every translation of the product shares, when that is another id.
1095 + wp_cache_delete( 'product_' . Multilingual::canonical_id( $product_id ), self::CACHE_GROUP );
1027 1096 return;
1028 1097 }
1029 1098
1030 1099 // No targeted product: drop the whole group when the backend supports it.