PluginProbe ʕ •ᴥ•ʔ
Atarim – AI Agency for WordPress: Edit Pages, Fix Code, Update Plugins, SEO & Client Feedback / 5.1
Atarim – AI Agency for WordPress: Edit Pages, Fix Code, Update Plugins, SEO & Client Feedback v5.1
5.1.3 5.1.2 5.1.1 5.1 5.0 trunk 3.10 3.11 3.12 3.13 3.14 3.15 3.16 3.17 3.18 3.19 3.2.0 3.2.1 3.22 3.22.1 3.22.2 3.22.3 3.22.4 3.22.5 3.22.6 3.3.0 3.3.1 3.3.2 3.3.2.1 3.3.2.2 3.3.3 3.30 3.31 3.32 3.4 3.4.1 3.4.3 3.4.4 3.5 3.5.1 3.6 3.6.1 3.7 3.8 3.9 3.9.1 3.9.2 3.9.3 3.9.4 3.9.6 3.9.6.1 4.0 4.0.1 4.0.2 4.0.3 4.0.4 4.0.5 4.0.6 4.0.7 4.0.8 4.0.9 4.1.0 4.1.1 4.1.2 4.1.3 4.2 4.2.1 4.2.2 4.3 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.4
atarim-visual-collaboration / doit / abilities / class-avcf-abilities-core.php
atarim-visual-collaboration / doit / abilities Last commit date
class-avcf-abilities-base.php 3 weeks ago class-avcf-abilities-block-navigation.php 3 weeks ago class-avcf-abilities-cache.php 3 weeks ago class-avcf-abilities-content.php 3 weeks ago class-avcf-abilities-core.php 3 weeks ago class-avcf-abilities-global-styles.php 3 weeks ago class-avcf-abilities-gutenberg.php 3 weeks ago class-avcf-abilities-media.php 3 weeks ago class-avcf-abilities-metadata.php 3 weeks ago class-avcf-abilities-navigation.php 3 weeks ago class-avcf-abilities-patterns.php 3 weeks ago class-avcf-abilities-plugins.php 3 weeks ago class-avcf-abilities-settings.php 3 weeks ago class-avcf-abilities-taxonomies.php 3 weeks ago class-avcf-abilities-templates.php 3 weeks ago class-avcf-abilities-theme-files.php 3 weeks ago class-avcf-abilities-themes.php 3 weeks ago class-avcf-abilities-users.php 3 weeks ago
class-avcf-abilities-core.php
604 lines
1 <?php
2 /**
3 * WordPress core / environment MCP abilities.
4 *
5 * Reports on the host environment: WordPress version and pending updates,
6 * PHP version and WordPress support status, database server version, and
7 * multisite configuration. Built so the AI layer can audit a site before
8 * recommending major operations (upgrades, theme switches, plugin installs)
9 * that depend on the runtime environment.
10 *
11 * Exposed abilities:
12 * atarim/get-core-status WP + PHP + DB + multisite snapshot in one call.
13 * atarim/check-core-updates Force a fresh WordPress.org update check.
14 * atarim/update-core Update core to the latest (or a specific offered) version.
15 * atarim/reinstall-core Re-install the current version to repair core files.
16 *
17 * Note: abilities are exposed to the MCP server automatically (dynamic
18 * enumeration minus the avcf_mcp_blocked_abilities blacklist); no manual
19 * registration list needs editing when adding abilities here.
20 *
21 * @package atarim-visual-collaboration
22 */
23
24 if ( ! defined('ABSPATH') ) {
25 exit;
26 }
27
28 class AVCF_Abilities_Core extends AVCF_Abilities_Base {
29
30 /**
31 * Register all core / environment abilities.
32 * Called from AVCF_MCP::avcf_mcp_register_abilities() on wp_abilities_api_init.
33 */
34 public function register() {
35
36 // ---- get-core-status ----
37 wp_register_ability( 'atarim/get-core-status', [
38 'label' => 'Get Core Status',
39 'description' => 'Returns a snapshot of the WordPress core, PHP, and database environment plus pending core update info and multisite configuration. Use this to audit the site before recommending or attempting major operations like theme switches, large plugin installs, or PHP-dependent upgrades.',
40 'category' => 'atarim',
41 'input_schema' => [
42 'type' => 'object',
43 'properties' => new \stdClass(),
44 'additionalProperties' => false,
45 ],
46 'output_schema' => [
47 'type' => 'object',
48 'properties' => [
49 'wordpress' => [
50 'type' => 'object',
51 'properties' => [
52 'version' => [ 'type' => 'string' ],
53 'latest_version' => [ 'type' => 'string' ],
54 'update_available' => [ 'type' => 'boolean' ],
55 'update_type' => [ 'type' => 'string' ],
56 'automatic_updates_enabled' => [ 'type' => 'boolean' ],
57 'language' => [ 'type' => 'string' ],
58 ],
59 ],
60 'php' => [
61 'type' => 'object',
62 'properties' => [
63 'version' => [ 'type' => 'string' ],
64 'recommended_minimum' => [ 'type' => 'string' ],
65 'is_supported_by_wp' => [ 'type' => 'boolean' ],
66 'memory_limit' => [ 'type' => 'string' ],
67 'max_execution_time' => [ 'type' => 'integer' ],
68 ],
69 ],
70 'database' => [
71 'type' => 'object',
72 'properties' => [
73 'server_version' => [ 'type' => 'string' ],
74 'server_type' => [ 'type' => 'string' ],
75 'charset' => [ 'type' => 'string' ],
76 'collate' => [ 'type' => 'string' ],
77 ],
78 ],
79 'multisite' => [
80 'type' => 'object',
81 'properties' => [
82 'is_multisite' => [ 'type' => 'boolean' ],
83 'is_main_site' => [ 'type' => 'boolean' ],
84 'site_count' => [ 'type' => 'integer' ],
85 'network_id' => [ 'type' => 'integer' ],
86 ],
87 ],
88 'message' => [ 'type' => 'string' ],
89 ],
90 'required' => [ 'wordpress', 'php', 'database', 'multisite' ],
91 ],
92 'execute_callback' => function( $input = [] ) {
93 global $wp_version, $wpdb;
94
95 // --- WordPress core ---
96 if ( ! function_exists( 'get_core_updates' ) ) {
97 require_once ABSPATH . 'wp-admin/includes/update.php';
98 }
99
100 $core_updates = get_core_updates();
101 $latest_version = $wp_version;
102 $update_available = false;
103 $update_type = 'none';
104
105 if ( is_array( $core_updates ) && ! empty( $core_updates ) ) {
106 foreach ( $core_updates as $update ) {
107 if ( isset( $update->response ) && $update->response === 'upgrade' && isset( $update->current ) ) {
108 $latest_version = $update->current;
109 $update_available = true;
110 // Determine major vs minor vs patch.
111 $current_parts = explode( '.', $wp_version );
112 $latest_parts = explode( '.', $latest_version );
113 if ( isset( $current_parts[0], $latest_parts[0] ) && $current_parts[0] !== $latest_parts[0] ) {
114 $update_type = 'major';
115 } elseif ( isset( $current_parts[1], $latest_parts[1] ) && ( ! isset( $current_parts[1] ) || ! isset( $latest_parts[1] ) || $current_parts[1] !== $latest_parts[1] ) ) {
116 $update_type = 'minor';
117 } else {
118 $update_type = 'patch';
119 }
120 break;
121 }
122 }
123 }
124
125 // WordPress auto-update preferences. WP exposes these via constants and filters.
126 $auto_updates_enabled = ! defined( 'AUTOMATIC_UPDATER_DISABLED' ) || ! AUTOMATIC_UPDATER_DISABLED;
127 // Core minor updates default-on in modern WP; respect explicit user opt-out.
128 if ( defined( 'WP_AUTO_UPDATE_CORE' ) ) {
129 $auto_updates_enabled = (bool) WP_AUTO_UPDATE_CORE;
130 }
131
132 // --- PHP ---
133 // WordPress's recommended PHP minimum, surfaced in admin notices.
134 // Current as of WP 6.x: 7.4 absolute minimum, 8.1+ recommended.
135 // We expose WP's hard-floor (RECOMMENDED_PHP_VERSION) when defined.
136 $php_recommended = '';
137 if ( defined( 'WP_PHP_MIN_VERSION' ) ) {
138 $php_recommended = (string) WP_PHP_MIN_VERSION;
139 } elseif ( function_exists( 'wp_check_php_version' ) ) {
140 // wp_check_php_version() returns a structure with recommended_version
141 $check = wp_check_php_version();
142 if ( is_array( $check ) && isset( $check['recommended_version'] ) ) {
143 $php_recommended = (string) $check['recommended_version'];
144 }
145 }
146 if ( $php_recommended === '' ) {
147 // Fallback to the value WP has used as the floor for several years.
148 $php_recommended = '7.4';
149 }
150
151 $php_supported = version_compare( PHP_VERSION, $php_recommended, '>=' );
152
153 // --- Database ---
154 $db_version = '';
155 $db_type = '';
156 if ( $wpdb && method_exists( $wpdb, 'db_server_info' ) ) {
157 $db_version = (string) $wpdb->db_version();
158 $raw_info = (string) $wpdb->db_server_info();
159 $db_type = ( stripos( $raw_info, 'mariadb' ) !== false ) ? 'MariaDB' : 'MySQL';
160 } elseif ( $wpdb ) {
161 $db_version = (string) $wpdb->db_version();
162 $db_type = 'MySQL';
163 }
164
165 // --- Multisite ---
166 $is_multisite = is_multisite();
167 $multisite = [
168 'is_multisite' => $is_multisite,
169 'is_main_site' => $is_multisite ? is_main_site() : true,
170 'site_count' => $is_multisite ? (int) get_blog_count() : 1,
171 'network_id' => $is_multisite ? (int) get_current_network_id() : 0,
172 ];
173
174 $message = sprintf(
175 'WordPress %s on PHP %s, %s %s. %s',
176 $wp_version,
177 PHP_VERSION,
178 $db_type !== '' ? $db_type : 'database',
179 $db_version,
180 $update_available
181 ? sprintf( 'Core update available: %s (%s).', $latest_version, $update_type )
182 : 'Core is up to date.'
183 );
184
185 return [
186 'wordpress' => [
187 'version' => $wp_version,
188 'latest_version' => $latest_version,
189 'update_available' => $update_available,
190 'update_type' => $update_type,
191 'automatic_updates_enabled' => (bool) $auto_updates_enabled,
192 'language' => (string) get_locale(),
193 ],
194 'php' => [
195 'version' => PHP_VERSION,
196 'recommended_minimum' => $php_recommended,
197 'is_supported_by_wp' => $php_supported,
198 'memory_limit' => (string) ini_get( 'memory_limit' ),
199 'max_execution_time' => (int) ini_get( 'max_execution_time' ),
200 ],
201 'database' => [
202 'server_version' => $db_version,
203 'server_type' => $db_type,
204 'charset' => isset( $wpdb->charset ) ? (string) $wpdb->charset : '',
205 'collate' => isset( $wpdb->collate ) ? (string) $wpdb->collate : '',
206 ],
207 'multisite' => $multisite,
208 'message' => $message,
209 ];
210 },
211 'permission_callback' => function() {
212 // Reading environment info is moderately sensitive (PHP version, server type can
213 // help an attacker fingerprint the host). Gate on update_core which is held by
214 // administrators only, matching WP's own dashboard widget visibility.
215 return current_user_can( 'update_core' );
216 },
217 'meta' => [
218 'mcp' => [ 'public' => true, 'type' => 'tool' ],
219 'annotations' => [
220 'readonly' => true,
221 'destructive' => false,
222 'idempotent' => true,
223 ],
224 ],
225 ] );
226
227 // ---- check-core-updates ----
228 wp_register_ability( 'atarim/check-core-updates', [
229 'label' => 'Check For Core Updates',
230 'description' => 'Forces a fresh check with WordPress.org for core updates, bypassing the cached result (which can be up to ~12 hours stale), and reports whether an update is available. Use this to get an authoritative, up-to-the-minute answer before deciding whether to run update-core.',
231 'category' => 'atarim',
232 'input_schema' => [
233 'type' => 'object',
234 'properties' => new \stdClass(),
235 'additionalProperties' => false,
236 ],
237 'output_schema' => [
238 'type' => 'object',
239 'properties' => [
240 'current_version' => [ 'type' => 'string' ],
241 'latest_version' => [ 'type' => 'string' ],
242 'update_available' => [ 'type' => 'boolean' ],
243 'update_type' => [ 'type' => 'string' ],
244 'checked_at' => [ 'type' => 'string' ],
245 'message' => [ 'type' => 'string' ],
246 ],
247 'required' => [ 'current_version', 'latest_version', 'update_available', 'message' ],
248 ],
249 'execute_callback' => function( $input = [] ) {
250 global $wp_version;
251
252 if ( ! function_exists( 'get_core_updates' ) ) {
253 require_once ABSPATH . 'wp-admin/includes/update.php';
254 }
255
256 // Force a fresh remote check (bypasses the update_core transient).
257 wp_version_check( [], true );
258
259 $updates = get_core_updates();
260 $current_version = $wp_version;
261 $latest_version = $wp_version;
262 $update_available = false;
263 $update_type = 'none';
264
265 if ( is_array( $updates ) && ! empty( $updates ) ) {
266 foreach ( $updates as $update ) {
267 if ( isset( $update->response ) && $update->response === 'upgrade' && isset( $update->current ) ) {
268 $latest_version = (string) $update->current;
269 $update_available = true;
270 $update_type = $this->avcf_core_update_type( $current_version, $latest_version );
271 break;
272 }
273 }
274 }
275
276 return [
277 'current_version' => $current_version,
278 'latest_version' => $latest_version,
279 'update_available' => $update_available,
280 'update_type' => $update_type,
281 'checked_at' => gmdate( 'c' ),
282 'message' => $update_available
283 ? sprintf( 'Core update available: %s -> %s (%s).', $current_version, $latest_version, $update_type )
284 : 'WordPress core is up to date.',
285 ];
286 },
287 'permission_callback' => function() {
288 return current_user_can( 'update_core' );
289 },
290 'meta' => [
291 'mcp' => [ 'public' => true, 'type' => 'tool' ],
292 'annotations' => [
293 'readonly' => false,
294 'destructive' => false,
295 'idempotent' => true,
296 ],
297 ],
298 ] );
299
300 // ---- update-core ----
301 wp_register_ability( 'atarim/update-core', [
302 'label' => 'Update WordPress Core',
303 'description' => 'Updates WordPress core to the latest available update, or to a specific version that WordPress.org is currently offering for this site. Only offered versions are accepted — arbitrary version strings and downgrades are rejected. Runs the database upgrade routine afterward so the site is not left needing a manual DB update. High impact: on constrained hosts the download/extract can exceed max_execution_time (the update may still complete server-side). Requires direct filesystem access (no FTP/SSH credential prompts are possible here).',
304 'category' => 'atarim',
305 'input_schema' => [
306 'type' => 'object',
307 'properties' => [
308 'version' => [
309 'type' => 'string',
310 'description' => 'Optional. Pin to a specific version WordPress is currently offering as an update (e.g. "6.5.2"). If omitted, updates to the latest offered update. Rejected if the version is not an offered update.',
311 ],
312 'locale' => [
313 'type' => 'string',
314 'description' => 'Optional WordPress locale for the update package (e.g. "en_US"). Defaults to the site locale.',
315 ],
316 ],
317 'additionalProperties' => false,
318 ],
319 'output_schema' => [
320 'type' => 'object',
321 'properties' => [
322 'success' => [ 'type' => 'boolean' ],
323 'updated' => [ 'type' => 'boolean' ],
324 'from_version' => [ 'type' => 'string' ],
325 'to_version' => [ 'type' => 'string' ],
326 'update_type' => [ 'type' => 'string' ],
327 'db_upgraded' => [ 'type' => 'boolean' ],
328 'message' => [ 'type' => 'string' ],
329 ],
330 'required' => [ 'success', 'updated', 'message' ],
331 ],
332 'execute_callback' => function( $input = [] ) {
333 global $wp_version;
334
335 $pre = $this->avcf_core_update_preflight();
336 if ( $pre !== null ) {
337 return [ 'success' => false, 'updated' => false, 'message' => $pre ];
338 }
339
340 $locale = ( isset( $input['locale'] ) && $input['locale'] !== '' ) ? (string) $input['locale'] : get_locale();
341 $from_version = $wp_version;
342
343 // Act on current data.
344 wp_version_check( [], true );
345 $updates = get_core_updates();
346 if ( ! is_array( $updates ) || empty( $updates ) ) {
347 return [
348 'success' => true,
349 'updated' => false,
350 'from_version' => $from_version,
351 'to_version' => $from_version,
352 'update_type' => 'none',
353 'db_upgraded' => false,
354 'message' => 'No core updates are available; WordPress is up to date.',
355 ];
356 }
357
358 $want_version = isset( $input['version'] ) ? trim( (string) $input['version'] ) : '';
359 $target = null;
360 foreach ( $updates as $update ) {
361 if ( ! isset( $update->response ) || $update->response !== 'upgrade' || ! isset( $update->current ) ) {
362 continue;
363 }
364 if ( $want_version !== '' ) {
365 if ( (string) $update->current === $want_version ) {
366 $target = $update;
367 break;
368 }
369 } else {
370 $target = $update;
371 break;
372 }
373 }
374
375 if ( $target === null ) {
376 if ( $want_version !== '' ) {
377 return [
378 'success' => false,
379 'updated' => false,
380 'from_version' => $from_version,
381 'to_version' => $from_version,
382 'message' => sprintf( 'Version "%s" is not currently offered as a core update for this site. Use check-core-updates to see what is available.', $want_version ),
383 ];
384 }
385 return [
386 'success' => true,
387 'updated' => false,
388 'from_version' => $from_version,
389 'to_version' => $from_version,
390 'update_type' => 'none',
391 'db_upgraded' => false,
392 'message' => 'No core upgrade offer found; WordPress is up to date.',
393 ];
394 }
395
396 $to_version = (string) $target->current;
397 $update_type = $this->avcf_core_update_type( $from_version, $to_version );
398
399 // Prefer the requested locale's package if a different one is available.
400 if ( $locale !== '' && isset( $target->locale ) && $target->locale !== $locale ) {
401 $located = find_core_update( $to_version, $locale );
402 if ( $located ) {
403 $target = $located;
404 }
405 }
406
407 $skin = new \WP_Ajax_Upgrader_Skin();
408 $upgrader = new \Core_Upgrader( $skin );
409 $result = $upgrader->upgrade( $target );
410
411 if ( is_wp_error( $result ) ) {
412 return [
413 'success' => false,
414 'updated' => false,
415 'from_version' => $from_version,
416 'to_version' => $to_version,
417 'update_type' => $update_type,
418 'db_upgraded' => false,
419 'message' => 'Core update failed: ' . $result->get_error_message(),
420 ];
421 }
422 if ( $result === false ) {
423 return [
424 'success' => false,
425 'updated' => false,
426 'from_version' => $from_version,
427 'to_version' => $to_version,
428 'update_type' => $update_type,
429 'db_upgraded' => false,
430 'message' => 'Core update did not complete (the upgrader returned no result).',
431 ];
432 }
433
434 // Run the DB upgrade routine so the site is not left "needs database update".
435 $db_upgraded = false;
436 if ( ! function_exists( 'wp_upgrade' ) ) {
437 require_once ABSPATH . 'wp-admin/includes/upgrade.php';
438 }
439 if ( function_exists( 'wp_upgrade' ) ) {
440 wp_upgrade();
441 $db_upgraded = true;
442 }
443
444 return [
445 'success' => true,
446 'updated' => true,
447 'from_version' => $from_version,
448 'to_version' => $to_version,
449 'update_type' => $update_type,
450 'db_upgraded' => $db_upgraded,
451 'message' => sprintf( 'WordPress updated from %s to %s (%s).', $from_version, $to_version, $update_type ),
452 ];
453 },
454 'permission_callback' => function() {
455 return current_user_can( 'update_core' );
456 },
457 'meta' => [
458 'mcp' => [ 'public' => true, 'type' => 'tool' ],
459 'annotations' => [
460 'readonly' => false,
461 'destructive' => false,
462 'idempotent' => false,
463 ],
464 ],
465 ] );
466
467 // ---- reinstall-core ----
468 wp_register_ability( 'atarim/reinstall-core', [
469 'label' => 'Reinstall WordPress Core',
470 'description' => 'Re-installs the currently running WordPress version to repair corrupted or modified core files (equivalent to the dashboard "Re-install now" button). Does not change the version. Requires direct filesystem access.',
471 'category' => 'atarim',
472 'input_schema' => [
473 'type' => 'object',
474 'properties' => [
475 'locale' => [
476 'type' => 'string',
477 'description' => 'Optional WordPress locale for the package (e.g. "en_US"). Defaults to the site locale.',
478 ],
479 ],
480 'additionalProperties' => false,
481 ],
482 'output_schema' => [
483 'type' => 'object',
484 'properties' => [
485 'success' => [ 'type' => 'boolean' ],
486 'reinstalled' => [ 'type' => 'boolean' ],
487 'version' => [ 'type' => 'string' ],
488 'message' => [ 'type' => 'string' ],
489 ],
490 'required' => [ 'success', 'reinstalled', 'message' ],
491 ],
492 'execute_callback' => function( $input = [] ) {
493 global $wp_version;
494
495 $pre = $this->avcf_core_update_preflight();
496 if ( $pre !== null ) {
497 return [ 'success' => false, 'reinstalled' => false, 'version' => $wp_version, 'message' => $pre ];
498 }
499
500 $locale = ( isset( $input['locale'] ) && $input['locale'] !== '' ) ? (string) $input['locale'] : get_locale();
501
502 // Refresh, then locate the package for the CURRENT version (reinstall).
503 wp_version_check( [], true );
504 $update = find_core_update( $wp_version, $locale );
505 if ( ! $update ) {
506 return [
507 'success' => false,
508 'reinstalled' => false,
509 'version' => $wp_version,
510 'message' => sprintf( 'No reinstall package is available for the current version (%s, locale %s).', $wp_version, $locale ),
511 ];
512 }
513
514 $skin = new \WP_Ajax_Upgrader_Skin();
515 $upgrader = new \Core_Upgrader( $skin );
516 $result = $upgrader->upgrade( $update );
517
518 if ( is_wp_error( $result ) ) {
519 return [
520 'success' => false,
521 'reinstalled' => false,
522 'version' => $wp_version,
523 'message' => 'Core reinstall failed: ' . $result->get_error_message(),
524 ];
525 }
526 if ( $result === false ) {
527 return [
528 'success' => false,
529 'reinstalled' => false,
530 'version' => $wp_version,
531 'message' => 'Core reinstall did not complete (the upgrader returned no result).',
532 ];
533 }
534
535 return [
536 'success' => true,
537 'reinstalled' => true,
538 'version' => $wp_version,
539 'message' => sprintf( 'WordPress %s core files re-installed successfully.', $wp_version ),
540 ];
541 },
542 'permission_callback' => function() {
543 return current_user_can( 'update_core' );
544 },
545 'meta' => [
546 'mcp' => [ 'public' => true, 'type' => 'tool' ],
547 'annotations' => [
548 'readonly' => false,
549 'destructive' => false,
550 'idempotent' => true,
551 ],
552 ],
553 ] );
554 }
555
556 /**
557 * Shared safety preflight for core file operations (update / reinstall).
558 * Loads the upgrader includes and blocks when the environment cannot safely
559 * perform a core file operation. Returns null when OK, or an error string.
560 */
561 private function avcf_core_update_preflight() {
562 if ( defined( 'DISALLOW_FILE_MODS' ) && DISALLOW_FILE_MODS ) {
563 return 'File modifications are disabled on this site (DISALLOW_FILE_MODS), so core file operations are unavailable.';
564 }
565
566 if ( ! function_exists( 'get_filesystem_method' ) ) {
567 require_once ABSPATH . 'wp-admin/includes/file.php';
568 }
569 if ( function_exists( 'get_filesystem_method' ) && get_filesystem_method() !== 'direct' ) {
570 return 'The filesystem is not directly writable (FTP/SSH credentials would be required, which cannot be provided here). Core file operations are unavailable on this host configuration.';
571 }
572
573 if ( ! function_exists( 'get_core_updates' ) ) {
574 require_once ABSPATH . 'wp-admin/includes/update.php';
575 }
576 if ( ! class_exists( '\Core_Upgrader' ) ) {
577 require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
578 }
579
580 // Give the download/extract more headroom where the host permits it.
581 @set_time_limit( 300 );
582
583 return null;
584 }
585
586 /**
587 * Classify a version delta as major / minor / patch.
588 */
589 private function avcf_core_update_type( $from, $to ) {
590 $f = explode( '.', (string) $from );
591 $t = explode( '.', (string) $to );
592 $f0 = isset( $f[0] ) ? $f[0] : '';
593 $t0 = isset( $t[0] ) ? $t[0] : '';
594 $f1 = isset( $f[1] ) ? $f[1] : '';
595 $t1 = isset( $t[1] ) ? $t[1] : '';
596 if ( $f0 !== $t0 ) {
597 return 'major';
598 }
599 if ( $f1 !== $t1 ) {
600 return 'minor';
601 }
602 return 'patch';
603 }
604 }