PluginProbe
Cooked – Recipe Management / trunk
Cooked – Recipe Management vtrunk
1.16.0 1.15.0 trunk 1.10.0 1.11.0 1.11.1 1.11.2 1.11.3 1.11.4 1.12.0 1.13.0 1.14.0 1.7.10 1.7.11 1.7.12 1.7.13 1.7.15.1 1.7.15.3 1.7.15.4 1.8.0 1.8.1 1.8.2 1.8.3 1.8.4 1.8.5 All 36 releases
cooked / includes / class.cooked-updates.php

class.cooked-updates.php in Cooked – Recipe Management trunk, at includes/class.cooked-updates.php

397 lines 13.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Plugin Updates and Data Migrations
4 *
5 * @package Cooked_Updates
6 * @subpackage Cooked_Updates / Core
7 * @since 1.11.2
8 */
9
10 // Exit if accessed directly
11 if ( !defined( 'ABSPATH' ) ) exit;
12
13 /**
14 * Cooked_Updates Class
15 *
16 * This class handles all version updates and data migrations for the Cooked plugin.
17 * It follows WordPress best practices for plugin updates and ensures data integrity.
18 *
19 * @since 1.11.2
20 */
21 class Cooked_Updates {
22
23 /**
24 * Current plugin version
25 */
26 private static $current_version;
27
28 /**
29 * Previous plugin version
30 */
31 private static $previous_version;
32
33 /**
34 * Current pro plugin version
35 */
36 private static $current_pro_version;
37
38 /**
39 * Previous pro plugin version
40 */
41 private static $previous_pro_version;
42
43 /**
44 * Cooked Settings Saved
45 */
46 private static $cooked_settings_saved;
47
48 /**
49 * Initialize the updates system
50 */
51 public function __construct() {
52 // Add action to check version and update settings at the end of page load.
53 add_action( 'shutdown', [__CLASS__, 'init'] );
54 }
55
56 /**
57 * Initialize the updates
58 */
59 public static function init() {
60 self::$cooked_settings_saved = get_option( 'cooked_settings_saved', false );
61 self::$current_version = COOKED_VERSION;
62 self::$previous_version = get_option( 'cooked_settings_version', '1.0.0' );
63 self::$current_pro_version = defined('COOKED_PRO_VERSION') ? COOKED_PRO_VERSION : null;
64 self::$previous_pro_version = get_option( 'cooked_pro_settings_version', '1.0.0' );
65
66 if ( !self::$cooked_settings_saved ) {
67 global $_cooked_settings;
68
69 if ( empty($_cooked_settings) ) {
70 $_cooked_settings = Cooked_Settings::get();
71 }
72
73 update_option( 'cooked_settings', $_cooked_settings );
74 self::$cooked_settings_saved = true;
75 }
76
77 // Only run updates if version has changed
78 if ( self::needs_update() ) {
79 self::run_updates();
80 }
81 }
82
83 /**
84 * Check if an update is needed
85 *
86 * @return bool True if update is needed
87 */
88 public static function needs_update() {
89 // Check both versions.
90 $cooked_version_compare = version_compare( self::$previous_version, self::$current_version );
91 $cooked_pro_version_compare = ( defined('COOKED_PRO_VERSION') && self::$current_pro_version !== null ) ? version_compare( self::$previous_pro_version, self::$current_pro_version ) : 0;
92
93 // Update if either version has changed.
94 if ( $cooked_version_compare < 0 || $cooked_pro_version_compare < 0 ) {
95 return true;
96 }
97
98 return false;
99 }
100
101 /**
102 * Run all necessary updates
103 */
104 private static function run_updates() {
105 // Store the previous version for logging
106 $old_version = self::$previous_version;
107
108 // Run version-specific updates
109 self::run_version_updates();
110
111 // Update both version numbers.
112 update_option( 'cooked_settings_version', self::$current_version );
113 if ( defined('COOKED_PRO_VERSION') ) {
114 update_option( 'cooked_pro_settings_version', self::$current_pro_version );
115 }
116
117 // Log the update
118 error_log( sprintf( 'Cooked: Updated from version %s to %s', $old_version, self::$current_version ) );
119 }
120
121 /**
122 * Run version-specific updates
123 */
124 private static function run_version_updates() {
125 $updates = self::get_version_updates();
126
127 foreach ( $updates as $version => $update_methods ) {
128 if ( version_compare( self::$previous_version, $version, '<' ) ) {
129 foreach ( $update_methods as $method ) {
130 if ( method_exists( __CLASS__, $method ) ) {
131 try {
132 call_user_func( [__CLASS__, $method] );
133 } catch ( Exception $e ) {
134 error_log( sprintf( 'Cooked: Error running update method %s: %s', $method, $e->getMessage() ) );
135 }
136 }
137 }
138 }
139 }
140 }
141
142 /**
143 * Get list of tools that can be run on demand (for Tools page and WP-CLI).
144 *
145 * @return array[] List of tools: [ 'id' => method_name, 'name' => label, 'description' => desc ]
146 */
147 public static function get_runnable_tools() {
148 $labels = [
149 'update_rewrite_rules' => [
150 'name' => __( 'Flush rewrite rules', 'cooked' ),
151 'description' => __( 'Refreshes permalink rules for recipe and profile URLs.', 'cooked' ),
152 ],
153 'fix_recipe_line_endings' => [
154 'name' => __( 'Fix recipe line endings', 'cooked' ),
155 'description' => __( 'Normalizes line endings in recipe content for exporter/importer compatibility.', 'cooked' ),
156 ],
157 'purge_legacy_related_recipes_cache' => [
158 'name' => __( 'Purge legacy related recipes cache', 'cooked' ),
159 'description' => __( 'Removes old transients and options from the previous related-recipes cache.', 'cooked' ),
160 ],
161 'remove_recipes_from_cooked_user_meta' => [
162 'name' => __( 'Remove recipes from user meta', 'cooked' ),
163 'description' => __( 'Removes the legacy "recipes" key from all users\' cooked_user_meta.', 'cooked' ),
164 ],
165 ];
166
167 $updates = self::get_version_updates();
168 $method_names = [];
169 foreach ( $updates as $methods ) {
170 foreach ( $methods as $method ) {
171 $method_names[ $method ] = true;
172 }
173 }
174 $method_names = array_keys( $method_names );
175
176 $tools = [];
177 foreach ( $method_names as $method ) {
178 $tools[] = [
179 'id' => $method,
180 'name' => isset( $labels[ $method ]['name'] ) ? $labels[ $method ]['name'] : $method,
181 'description' => isset( $labels[ $method ]['description'] ) ? $labels[ $method ]['description'] : '',
182 ];
183 }
184 return $tools;
185 }
186
187 /**
188 * Run a single migration/tool by name (on demand). Used by Tools page and WP-CLI.
189 *
190 * @param string $tool_name Method name (must exist in get_version_updates).
191 * @return true|WP_Error True on success, WP_Error on failure.
192 */
193 public static function run_tool( $tool_name ) {
194 $tool_name = is_string( $tool_name ) ? trim( $tool_name ) : '';
195 if ( $tool_name === '' ) {
196 return new \WP_Error( 'cooked_tool_empty', __( 'Tool name is required.', 'cooked' ) );
197 }
198
199 $updates = self::get_version_updates();
200 $allowed = [];
201 foreach ( $updates as $methods ) {
202 foreach ( $methods as $method ) {
203 $allowed[ $method ] = true;
204 }
205 }
206 if ( ! isset( $allowed[ $tool_name ] ) ) {
207 return new \WP_Error( 'cooked_tool_invalid', sprintf( __( 'Unknown tool: %s.', 'cooked' ), $tool_name ) );
208 }
209 if ( ! method_exists( __CLASS__, $tool_name ) ) {
210 return new \WP_Error( 'cooked_tool_missing', sprintf( __( 'Tool method %s does not exist.', 'cooked' ), $tool_name ) );
211 }
212
213 try {
214 call_user_func( [ __CLASS__, $tool_name ] );
215 return true;
216 } catch ( \Exception $e ) {
217 return new \WP_Error( 'cooked_tool_error', $e->getMessage() );
218 }
219 }
220
221 /**
222 * Define version-specific updates
223 *
224 * @return array Array of version updates with their corresponding methods
225 */
226 private static function get_version_updates() {
227 return apply_filters( 'cooked_version_updates', [
228 '1.9.0' => [
229 'update_rewrite_rules'
230 ],
231 '1.9.1' => [
232 'update_rewrite_rules'
233 ],
234 '1.9.2' => [
235 'update_rewrite_rules'
236 ],
237 '1.9.4' => [
238 'update_rewrite_rules'
239 ],
240 '1.9.5' => [
241 'update_rewrite_rules'
242 ],
243 '1.11.2' => [
244 'fix_recipe_line_endings',
245 'update_rewrite_rules'
246 ],
247 '1.13.0' => [
248 'purge_legacy_related_recipes_cache',
249 'remove_recipes_from_cooked_user_meta',
250 ],
251 ]);
252 }
253
254 /**
255 * Fix line endings in existing recipes to prevent WordPress exporter/importer issues
256 *
257 * @since 1.11.2
258 */
259 private static function fix_recipe_line_endings() {
260 // Get all recipe posts
261 $recipes = get_posts([
262 'post_type' => 'cp_recipe',
263 'posts_per_page' => -1,
264 'post_status' => 'any'
265 ]);
266
267 if ( empty($recipes) ) {
268 return;
269 }
270
271 $updated_count = 0;
272
273 foreach ( $recipes as $recipe ) {
274 $recipe_settings = get_post_meta( $recipe->ID, '_recipe_settings', true );
275
276 if ( empty($recipe_settings) ) {
277 continue;
278 }
279
280 $needs_update = false;
281
282 // Fix content field
283 if ( isset( $recipe_settings['content'] ) ) {
284 $original_content = $recipe_settings['content'];
285 $recipe_settings['content'] = str_replace( ["\r\n", "\r"], "\n", $recipe_settings['content'] );
286 if ( $original_content !== $recipe_settings['content'] ) {
287 $needs_update = true;
288 }
289 }
290
291 // Fix excerpt field
292 if ( isset( $recipe_settings['excerpt'] ) ) {
293 $original_excerpt = $recipe_settings['excerpt'];
294 $recipe_settings['excerpt'] = str_replace( ["\r\n", "\r"], "\n", $recipe_settings['excerpt'] );
295 if ( $original_excerpt !== $recipe_settings['excerpt'] ) {
296 $needs_update = true;
297 }
298 }
299
300 // Fix notes field
301 if ( isset( $recipe_settings['notes'] ) ) {
302 $original_notes = $recipe_settings['notes'];
303 $recipe_settings['notes'] = str_replace( ["\r\n", "\r"], "\n", $recipe_settings['notes'] );
304 if ( $original_notes !== $recipe_settings['notes'] ) {
305 $needs_update = true;
306 }
307 }
308
309 // Fix directions content
310 if ( isset( $recipe_settings['directions'] ) && is_array( $recipe_settings['directions'] ) ) {
311 foreach ( $recipe_settings['directions'] as $key => $direction ) {
312 if ( isset( $direction['content'] ) ) {
313 $original_direction_content = $direction['content'];
314 $recipe_settings['directions'][$key]['content'] = str_replace( ["\r\n", "\r"], "\n", $direction['content'] );
315 if ( $original_direction_content !== $recipe_settings['directions'][$key]['content'] ) {
316 $needs_update = true;
317 }
318 }
319 }
320 }
321
322 // Update the recipe if changes were made
323 if ( $needs_update ) {
324 update_post_meta( $recipe->ID, '_recipe_settings', $recipe_settings );
325 $updated_count++;
326 }
327 }
328
329 // Log the update if any recipes were modified
330 if ( $updated_count > 0 ) {
331 error_log( sprintf( 'Cooked: Fixed line endings in %d recipes for WordPress exporter/importer compatibility.', $updated_count ) );
332 }
333 }
334
335 /**
336 * Purge legacy related-recipes transients and options (cache and pre-calculation data).
337 * One-time cleanup when moving to on-demand related recipes with no cache.
338 *
339 * @since 1.13.0
340 */
341 private static function purge_legacy_related_recipes_cache() {
342 global $wpdb;
343
344 $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_cooked_related_%' OR option_name LIKE '_transient_timeout_cooked_related_%'" );
345 delete_option( 'cooked_related_version' );
346 delete_option( 'cooked_related_calculation_last' );
347
348 error_log( 'Cooked: Purged legacy related-recipes cache and options.' );
349 }
350
351 /**
352 * Remove legacy 'recipes' key from cooked_user_meta for all users.
353 * Recipes are computed on demand in Cooked_Users::get(); they must not be stored in user meta.
354 *
355 * @since 1.13.0
356 */
357 private static function remove_recipes_from_cooked_user_meta() {
358 $user_ids = get_users( [
359 'meta_key' => 'cooked_user_meta',
360 'fields' => 'ID',
361 ] );
362
363 if ( empty( $user_ids ) ) {
364 return;
365 }
366
367 $updated_count = 0;
368
369 foreach ( $user_ids as $user_id ) {
370 $meta = get_user_meta( $user_id, 'cooked_user_meta', true );
371
372 if ( ! is_array( $meta ) || ! isset( $meta['recipes'] ) ) {
373 continue;
374 }
375
376 unset( $meta['recipes'] );
377 update_user_meta( $user_id, 'cooked_user_meta', $meta );
378 $updated_count++;
379 }
380
381 if ( $updated_count > 0 ) {
382 error_log( sprintf( 'Cooked: Removed legacy recipes key from %d user(s) cooked_user_meta.', $updated_count ) );
383 }
384 }
385
386 /**
387 * Update rewrite rules if needed
388 *
389 * @since 1.11.2
390 */
391 private static function update_rewrite_rules() {
392 flush_rewrite_rules();
393 error_log( 'Cooked: Flushed rewrite rules due to version update.' );
394 }
395
396 }
397