Plugin Directory

Changeset 3734304


Ignore:
Timestamp:
10/08/2026 10:41:59 AM (2 weeks ago)
Author:
zoofiai
Message:

Version 2.1.0: Advanced Theme Lab, Builder-aware content engine, expanded WP-CLI emulator

Location:
zoofiai-mcp-connector
Files:
33 added
6 edited

Legend:

Unmodified
Added
Removed
  • zoofiai-mcp-connector/trunk/includes/class-zoofi-api.php

    r3711936 r3734304  
    299299                        'methods' => 'POST', 'callback' => array( __CLASS__, 'theme_draft_file_search' ), 'permission_callback' => static function () { return current_user_can( 'manage_options' ); },
    300300                ) );
     301                register_rest_route( self::NS, '/theme-drafts/(?P<id>[A-Za-z0-9_-]+)/files/outline', array(
     302                        'methods' => 'POST', 'callback' => array( __CLASS__, 'theme_draft_file_outline' ), 'permission_callback' => static function () { return current_user_can( 'manage_options' ); },
     303                ) );
    301304                register_rest_route( self::NS, '/theme-drafts/(?P<id>[A-Za-z0-9_-]+)/files/write', array(
    302305                        'methods' => 'POST', 'callback' => array( __CLASS__, 'theme_draft_file_write' ), 'permission_callback' => static function () { return current_user_can( 'manage_options' ); },
    … …  
    313316                register_rest_route( self::NS, '/theme-drafts/(?P<id>[A-Za-z0-9_-]+)/rollback', array(
    314317                        'methods' => 'POST', 'callback' => array( __CLASS__, 'theme_draft_rollback' ), 'permission_callback' => static function () { return current_user_can( 'manage_options' ); },
     318                ) );
     319
     320
     321                // --- Builder-aware surgical content engine: inspect -> search -> exact edit ---
     322                register_rest_route( self::NS, '/content/profile/(?P<id>\d+)', array(
     323                        'methods' => 'GET', 'callback' => array( __CLASS__, 'content_profile' ), 'permission_callback' => static function () { return current_user_can( 'edit_posts' ); },
     324                ) );
     325                register_rest_route( self::NS, '/content/search', array(
     326                        'methods' => 'POST', 'callback' => array( __CLASS__, 'content_search' ), 'permission_callback' => static function () { return current_user_can( 'edit_posts' ) || current_user_can( 'manage_options' ); },
     327                ) );
     328                register_rest_route( self::NS, '/content/edit', array(
     329                        'methods' => 'POST', 'callback' => array( __CLASS__, 'content_edit' ), 'permission_callback' => static function () { return current_user_can( 'edit_posts' ) || current_user_can( 'manage_options' ); },
    315330                ) );
    316331
    … …  
    11031118                        'pro_ops_features' => array( 'diagnostics', 'change_history', 'local_capabilities', 'restore_points', 'bulk_ops', 'scheduled_tasks', 'site_security_audit', 'broken_links', 'seo_bulk', 'database_toolkit' ),
    11041119                        'change_checkpoints' => class_exists( 'Zoofi_Smart' ),
     1120                        'content_surgical_engine' => class_exists( 'Zoofi_Content_Engine' ),
     1121                        'content_builder_profiles' => array( 'elementor', 'divi', 'bricks', 'breakdance', 'beaver_builder', 'gutenberg', 'classic' ),
     1122                        'serialized_builder_structural_edit' => class_exists( 'Zoofi_Content_Engine' ),
     1123                        'theme_draft_outline' => class_exists( 'Zoofi_Theme_Drafts' ),
     1124                        'theme_draft_publish_validation' => class_exists( 'Zoofi_Theme_Drafts' ),
     1125                        'cli_extended_management' => true,
    11051126                        'background_tasks' => class_exists( 'Zoofi_Smart' ),
    11061127                        'durable_jobs' => class_exists( 'Zoofi_Durable_Jobs' ),
    … …  
    15141535                $json = $request->get_json_params();
    15151536                $result = Zoofi_Theme_Drafts::search_files( sanitize_key( (string) $request['id'] ), (string) ( $json['needle'] ?? '' ), ! empty( $json['case_sensitive'] ), isset( $json['max'] ) ? absint( $json['max'] ) : 100 );
     1537                return is_wp_error( $result ) ? $result : rest_ensure_response( $result );
     1538        }
     1539
     1540        public static function theme_draft_file_outline( $request ) {
     1541                $json = (array) $request->get_json_params();
     1542                $result = Zoofi_Theme_Drafts::outline_file( sanitize_key( (string) $request['id'] ), (string) ( $json['file'] ?? '' ) );
     1543                return is_wp_error( $result ) ? $result : rest_ensure_response( $result );
     1544        }
     1545
     1546        public static function content_profile( $request ) {
     1547                $result = Zoofi_Content_Engine::profile( absint( $request['id'] ) );
     1548                return is_wp_error( $result ) ? $result : rest_ensure_response( $result );
     1549        }
     1550
     1551        public static function content_search( $request ) {
     1552                $result = Zoofi_Content_Engine::search( (array) $request->get_json_params() );
     1553                return is_wp_error( $result ) ? $result : rest_ensure_response( $result );
     1554        }
     1555
     1556        public static function content_edit( $request ) {
     1557                $result = Zoofi_Content_Engine::edit( (array) $request->get_json_params() );
    15161558                return is_wp_error( $result ) ? $result : rest_ensure_response( $result );
    15171559        }
  • zoofiai-mcp-connector/trunk/includes/class-zoofi-cli.php

    r3706882 r3734304  
    7979                'user list'              => array( 'handler' => 'cmd_user_list', 'tier' => 'read', 'cap' => 'list_users' ),
    8080                'user get'               => array( 'handler' => 'cmd_user_get', 'tier' => 'read', 'cap' => 'list_users' ),
     81                'user meta get'          => array( 'handler' => 'cmd_user_meta_get', 'tier' => 'read', 'cap' => 'list_users' ),
     82                'user meta list'         => array( 'handler' => 'cmd_user_meta_list', 'tier' => 'read', 'cap' => 'list_users' ),
    8183                'post list'              => array( 'handler' => 'cmd_post_list', 'tier' => 'read', 'cap' => 'edit_posts' ),
    8284                'post get'               => array( 'handler' => 'cmd_post_get', 'tier' => 'read', 'cap' => 'edit_posts' ),
    8385                'taxonomy list'          => array( 'handler' => 'cmd_taxonomy_list', 'tier' => 'read', 'cap' => 'edit_posts' ),
    8486                'term list'              => array( 'handler' => 'cmd_term_list', 'tier' => 'read', 'cap' => 'manage_categories' ),
     87                'option pluck'           => array( 'handler' => 'cmd_option_pluck', 'tier' => 'read', 'cap' => 'manage_options' ),
     88                'cron test'              => array( 'handler' => 'cmd_cron_test', 'tier' => 'read', 'cap' => 'manage_options' ),
    8589                'media list'             => array( 'handler' => 'cmd_media_list', 'tier' => 'read', 'cap' => 'upload_files' ),
    8690                'comment list'           => array( 'handler' => 'cmd_comment_list', 'tier' => 'read', 'cap' => 'moderate_comments' ),
    … …  
    122126                'theme delete'           => array( 'handler' => 'cmd_theme_delete', 'tier' => 'write', 'cap' => 'delete_themes', 'check_file_mods' => true, 'destructive' => true ),
    123127                'option update'          => array( 'handler' => 'cmd_option_update', 'tier' => 'write', 'cap' => 'manage_options' ),
     128                'option patch update'    => array( 'handler' => 'cmd_option_patch_update', 'tier' => 'write', 'cap' => 'manage_options' ),
    124129                'option add'             => array( 'handler' => 'cmd_option_add', 'tier' => 'write', 'cap' => 'manage_options' ),
    125130                'option delete'          => array( 'handler' => 'cmd_option_delete', 'tier' => 'write', 'cap' => 'manage_options', 'destructive' => true ),
    … …  
    129134                'user create'            => array( 'handler' => 'cmd_user_create', 'tier' => 'write', 'cap' => 'create_users' ),
    130135                'user update'            => array( 'handler' => 'cmd_user_update', 'tier' => 'write', 'cap' => 'edit_users' ),
     136                'user meta add'          => array( 'handler' => 'cmd_user_meta_add', 'tier' => 'write', 'cap' => 'edit_users' ),
     137                'user meta update'       => array( 'handler' => 'cmd_user_meta_update', 'tier' => 'write', 'cap' => 'edit_users' ),
     138                'user meta delete'       => array( 'handler' => 'cmd_user_meta_delete', 'tier' => 'write', 'cap' => 'edit_users', 'destructive' => true ),
    131139                'user delete'            => array( 'handler' => 'cmd_user_delete', 'tier' => 'write', 'cap' => 'delete_users', 'destructive' => true ),
    132140                'post create'            => array( 'handler' => 'cmd_post_create', 'tier' => 'write', 'cap' => 'edit_posts' ),
    … …  
    136144                'post meta add'          => array( 'handler' => 'cmd_post_meta_update', 'tier' => 'write', 'cap' => 'edit_posts' ),
    137145                'post meta delete'       => array( 'handler' => 'cmd_post_meta_delete', 'tier' => 'write', 'cap' => 'edit_posts', 'destructive' => true ),
     146                'term create'            => array( 'handler' => 'cmd_term_create', 'tier' => 'write', 'cap' => 'manage_categories' ),
     147                'term update'            => array( 'handler' => 'cmd_term_update', 'tier' => 'write', 'cap' => 'manage_categories' ),
     148                'term delete'            => array( 'handler' => 'cmd_term_delete', 'tier' => 'write', 'cap' => 'manage_categories', 'destructive' => true ),
     149                'post term set'          => array( 'handler' => 'cmd_post_term_set', 'tier' => 'write', 'cap' => 'edit_posts' ),
     150                'post term add'          => array( 'handler' => 'cmd_post_term_add', 'tier' => 'write', 'cap' => 'edit_posts' ),
     151                'post term remove'       => array( 'handler' => 'cmd_post_term_remove', 'tier' => 'write', 'cap' => 'edit_posts' ),
     152                'theme mod set'          => array( 'handler' => 'cmd_theme_mod_set', 'tier' => 'write', 'cap' => 'edit_theme_options' ),
     153                'role create'            => array( 'handler' => 'cmd_role_create', 'tier' => 'write', 'cap' => 'promote_users' ),
     154                'role delete'            => array( 'handler' => 'cmd_role_delete', 'tier' => 'write', 'cap' => 'promote_users', 'destructive' => true ),
     155                'cap add'                => array( 'handler' => 'cmd_cap_add', 'tier' => 'write', 'cap' => 'promote_users' ),
     156                'cap remove'             => array( 'handler' => 'cmd_cap_remove', 'tier' => 'write', 'cap' => 'promote_users', 'destructive' => true ),
     157                'cron event run'         => array( 'handler' => 'cmd_cron_event_run', 'tier' => 'write', 'cap' => 'manage_options' ),
     158                'cron event delete'      => array( 'handler' => 'cmd_cron_event_delete', 'tier' => 'write', 'cap' => 'manage_options', 'destructive' => true ),
    138159                'core update'            => array( 'handler' => 'cmd_core_update', 'tier' => 'write', 'cap' => 'update_core', 'check_file_mods' => true, 'destructive' => true ),
    139160                'search-replace'         => array( 'handler' => 'cmd_search_replace', 'tier' => 'write', 'cap' => 'manage_options', 'destructive' => true ),
    … …  
    21862207         * @return string|false
    21872208         */
     2209
     2210        /** User meta helpers. */
     2211        private static function cmd_user_meta_get( array $pos, array $assoc ) {
     2212                $user_id = absint( $pos[0] ?? 0 ); $key = (string) ( $pos[1] ?? '' );
     2213                if ( ! $user_id || '' === $key ) return array( 'ok' => false, 'message' => 'Usage: user meta get <user-id> <key>', 'data' => array() );
     2214                if ( self::sensitive_meta_key( $key ) ) return array( 'ok' => false, 'message' => 'Sensitive user meta key is blocked.', 'data' => array() );
     2215                return array( 'ok' => true, 'message' => 'User meta read', 'data' => array( 'user_id' => $user_id, 'key' => $key, 'value' => get_user_meta( $user_id, $key, true ) ) );
     2216        }
     2217        private static function cmd_user_meta_list( array $pos, array $assoc ) {
     2218                $user_id = absint( $pos[0] ?? 0 ); if ( ! $user_id ) return array( 'ok' => false, 'message' => 'Usage: user meta list <user-id>', 'data' => array() );
     2219                $all = get_user_meta( $user_id ); $safe = array();
     2220                foreach ( $all as $key => $values ) if ( ! self::sensitive_meta_key( $key ) ) $safe[ $key ] = array_map( 'maybe_unserialize', $values );
     2221                return array( 'ok' => true, 'message' => 'User meta listed', 'data' => $safe );
     2222        }
     2223        private static function cmd_user_meta_add( array $pos, array $assoc ) {
     2224                $user_id = absint( $pos[0] ?? 0 ); $key = (string) ( $pos[1] ?? '' ); $value = $pos[2] ?? '';
     2225                if ( ! $user_id || '' === $key || self::sensitive_meta_key( $key ) ) return array( 'ok' => false, 'message' => 'Invalid or protected user meta target.', 'data' => array() );
     2226                $id = add_user_meta( $user_id, $key, $value, ! empty( $assoc['unique'] ) );
     2227                return array( 'ok' => false !== $id, 'message' => false !== $id ? 'User meta added' : 'User meta add failed', 'data' => array( 'meta_id' => $id ) );
     2228        }
     2229        private static function cmd_user_meta_update( array $pos, array $assoc ) {
     2230                $user_id = absint( $pos[0] ?? 0 ); $key = (string) ( $pos[1] ?? '' ); $value = $pos[2] ?? '';
     2231                if ( ! $user_id || '' === $key || self::sensitive_meta_key( $key ) ) return array( 'ok' => false, 'message' => 'Invalid or protected user meta target.', 'data' => array() );
     2232                $ok = update_user_meta( $user_id, $key, $value ); return array( 'ok' => false !== $ok, 'message' => 'User meta updated', 'data' => array( 'user_id' => $user_id, 'key' => $key ) );
     2233        }
     2234        private static function cmd_user_meta_delete( array $pos, array $assoc ) {
     2235                $user_id = absint( $pos[0] ?? 0 ); $key = (string) ( $pos[1] ?? '' );
     2236                if ( ! $user_id || '' === $key || self::sensitive_meta_key( $key ) ) return array( 'ok' => false, 'message' => 'Invalid or protected user meta target.', 'data' => array() );
     2237                $ok = delete_user_meta( $user_id, $key ); return array( 'ok' => (bool) $ok, 'message' => $ok ? 'User meta deleted' : 'User meta not changed', 'data' => array() );
     2238        }
     2239        private static function sensitive_meta_key( $key ) {
     2240                $key = strtolower( (string) $key );
     2241                return false !== strpos( $key, 'password' ) || false !== strpos( $key, 'secret' ) || false !== strpos( $key, 'token' ) || false !== strpos( $key, 'session' ) || false !== strpos( $key, 'application_password' );
     2242        }
     2243
     2244        /** Option pluck/patch without round-tripping the entire object. */
     2245        private static function cmd_option_pluck( array $pos, array $assoc ) {
     2246                $name = (string) ( $pos[0] ?? '' ); $path = (string) ( $pos[1] ?? '' );
     2247                if ( ! $name || ! $path || in_array( $name, self::READABLE_BLOCKED_OPTIONS, true ) ) return array( 'ok' => false, 'message' => 'Usage: option pluck <name> <dot.path>', 'data' => array() );
     2248                $value = get_option( $name ); foreach ( explode( '.', $path ) as $part ) { if ( ! is_array( $value ) || ! array_key_exists( $part, $value ) ) return array( 'ok' => false, 'message' => 'Path not found', 'data' => array() ); $value = $value[ $part ]; }
     2249                return array( 'ok' => true, 'message' => 'Option value plucked', 'data' => $value );
     2250        }
     2251        private static function cmd_option_patch_update( array $pos, array $assoc ) {
     2252                $name = (string) ( $pos[0] ?? '' ); $path = (string) ( $pos[1] ?? '' ); $value = $pos[2] ?? '';
     2253                if ( ! $name || ! $path || in_array( $name, self::READABLE_BLOCKED_OPTIONS, true ) ) return array( 'ok' => false, 'message' => 'Usage: option patch update <name> <dot.path> <value>', 'data' => array() );
     2254                $root = get_option( $name ); if ( ! is_array( $root ) ) return array( 'ok' => false, 'message' => 'Option is not an array/object and cannot be patched.', 'data' => array() );
     2255                $ref =& $root; $parts = explode( '.', $path ); $last = array_pop( $parts ); foreach ( $parts as $part ) { if ( ! isset( $ref[ $part ] ) || ! is_array( $ref[ $part ] ) ) $ref[ $part ] = array(); $ref =& $ref[ $part ]; } $ref[ $last ] = $value;
     2256                $ok = update_option( $name, $root ); return array( 'ok' => false !== $ok, 'message' => 'Option patched', 'data' => array( 'name' => $name, 'path' => $path ) );
     2257        }
     2258
     2259        /** Taxonomy/term operations. */
     2260        private static function cmd_term_create( array $pos, array $assoc ) {
     2261                $taxonomy = (string) ( $pos[0] ?? '' ); $name = (string) ( $pos[1] ?? '' ); if ( ! $taxonomy || ! $name ) return array( 'ok' => false, 'message' => 'Usage: term create <taxonomy> <name>', 'data' => array() );
     2262                $args = array(); if ( isset( $assoc['slug'] ) ) $args['slug'] = sanitize_title( $assoc['slug'] ); if ( isset( $assoc['description'] ) ) $args['description'] = (string) $assoc['description']; if ( isset( $assoc['parent'] ) ) $args['parent'] = absint( $assoc['parent'] );
     2263                $r = wp_insert_term( $name, $taxonomy, $args ); if ( is_wp_error( $r ) ) return array( 'ok' => false, 'message' => $r->get_error_message(), 'data' => array() ); return array( 'ok' => true, 'message' => 'Term created', 'data' => $r );
     2264        }
     2265        private static function cmd_term_update( array $pos, array $assoc ) {
     2266                $taxonomy = (string) ( $pos[0] ?? '' ); $term_id = absint( $pos[1] ?? 0 ); if ( ! $taxonomy || ! $term_id ) return array( 'ok' => false, 'message' => 'Usage: term update <taxonomy> <term-id> [--name=...]', 'data' => array() );
     2267                $args = array(); foreach ( array( 'name','slug','description','parent' ) as $k ) if ( isset( $assoc[ $k ] ) ) $args[ $k ] = 'parent' === $k ? absint( $assoc[ $k ] ) : (string) $assoc[ $k ]; if ( ! $args ) return array( 'ok' => false, 'message' => 'No term fields supplied.', 'data' => array() );
     2268                $r = wp_update_term( $term_id, $taxonomy, $args ); if ( is_wp_error( $r ) ) return array( 'ok' => false, 'message' => $r->get_error_message(), 'data' => array() ); return array( 'ok' => true, 'message' => 'Term updated', 'data' => $r );
     2269        }
     2270        private static function cmd_term_delete( array $pos, array $assoc ) {
     2271                $taxonomy = (string) ( $pos[0] ?? '' ); $term_id = absint( $pos[1] ?? 0 ); if ( ! $taxonomy || ! $term_id ) return array( 'ok' => false, 'message' => 'Usage: term delete <taxonomy> <term-id>', 'data' => array() );
     2272                $r = wp_delete_term( $term_id, $taxonomy ); if ( is_wp_error( $r ) ) return array( 'ok' => false, 'message' => $r->get_error_message(), 'data' => array() ); return array( 'ok' => (bool) $r, 'message' => 'Term deleted', 'data' => $r );
     2273        }
     2274        private static function cmd_post_term_set( array $pos, array $assoc ) { return self::post_term_change( $pos, 'set' ); }
     2275        private static function cmd_post_term_add( array $pos, array $assoc ) { return self::post_term_change( $pos, 'add' ); }
     2276        private static function cmd_post_term_remove( array $pos, array $assoc ) { return self::post_term_change( $pos, 'remove' ); }
     2277        private static function post_term_change( array $pos, $mode ) {
     2278                $post_id = absint( $pos[0] ?? 0 ); $taxonomy = (string) ( $pos[1] ?? '' ); $raw = (string) ( $pos[2] ?? '' ); if ( ! $post_id || ! $taxonomy || '' === $raw ) return array( 'ok' => false, 'message' => 'Usage: post term ' . $mode . ' <post-id> <taxonomy> <comma-separated ids/slugs>', 'data' => array() );
     2279                $terms = array_values( array_filter( array_map( 'trim', explode( ',', $raw ) ), 'strlen' ) ); $terms = array_map( static function( $v ) { return ctype_digit( $v ) ? (int) $v : $v; }, $terms );
     2280                if ( 'remove' === $mode ) { $existing = wp_get_object_terms( $post_id, $taxonomy, array( 'fields' => 'all' ) ); if ( is_wp_error( $existing ) ) return array( 'ok' => false, 'message' => $existing->get_error_message(), 'data' => array() ); $remove_ids = array(); foreach ( $existing as $term ) if ( in_array( $term->term_id, $terms, true ) || in_array( $term->slug, $terms, true ) ) $remove_ids[] = $term->term_id; $r = wp_remove_object_terms( $post_id, $remove_ids, $taxonomy ); }
     2281                else $r = wp_set_object_terms( $post_id, $terms, $taxonomy, 'add' === $mode );
     2282                if ( is_wp_error( $r ) ) return array( 'ok' => false, 'message' => $r->get_error_message(), 'data' => array() ); return array( 'ok' => true, 'message' => 'Post terms updated', 'data' => $r );
     2283        }
     2284
     2285        private static function cmd_theme_mod_set( array $pos, array $assoc ) { $name=(string)($pos[0]??''); $value=$pos[1]??''; if(!$name)return array('ok'=>false,'message'=>'Usage: theme mod set <name> <value>','data'=>array()); set_theme_mod($name,$value); return array('ok'=>true,'message'=>'Theme mod set','data'=>array('name'=>$name)); }
     2286        private static function cmd_role_create( array $pos, array $assoc ) { $slug=sanitize_key($pos[0]??''); $name=(string)($pos[1]??$slug); if(!$slug)return array('ok'=>false,'message'=>'Usage: role create <slug> <name>','data'=>array()); $role=add_role($slug,$name,array('read'=>true)); return array('ok'=>(bool)$role,'message'=>$role?'Role created':'Role already exists or could not be created','data'=>array('role'=>$slug)); }
     2287        private static function cmd_role_delete( array $pos, array $assoc ) { $slug=sanitize_key($pos[0]??''); if(!$slug||in_array($slug,array('administrator','editor','author','contributor','subscriber'),true))return array('ok'=>false,'message'=>'Core roles cannot be deleted.','data'=>array()); remove_role($slug); return array('ok'=>true,'message'=>'Role deleted','data'=>array('role'=>$slug)); }
     2288        private static function cmd_cap_add( array $pos, array $assoc ) { return self::role_cap_change($pos,true); }
     2289        private static function cmd_cap_remove( array $pos, array $assoc ) { return self::role_cap_change($pos,false); }
     2290        private static function role_cap_change( array $pos, $add ) { $role=get_role(sanitize_key($pos[0]??'')); $cap=sanitize_key($pos[1]??''); if(!$role||!$cap)return array('ok'=>false,'message'=>'Usage: cap '.($add?'add':'remove').' <role> <capability>','data'=>array()); if($add)$role->add_cap($cap,true);else $role->remove_cap($cap); return array('ok'=>true,'message'=>'Capability '.($add?'added':'removed'),'data'=>array('capability'=>$cap)); }
     2291
     2292        private static function cmd_cron_test( array $pos, array $assoc ) { $disabled=defined('DISABLE_WP_CRON')&&DISABLE_WP_CRON; return array('ok'=>!$disabled,'message'=>$disabled?'WP-Cron is disabled by DISABLE_WP_CRON.':'WP-Cron is available.','data'=>array('disabled'=>$disabled,'next_scheduled_events'=>count(_get_cron_array()))); }
     2293        private static function cmd_cron_event_run( array $pos, array $assoc ) { $hook=(string)($pos[0]??''); if(!$hook)return array('ok'=>false,'message'=>'Usage: cron event run <hook>','data'=>array()); do_action($hook); return array('ok'=>true,'message'=>'Cron hook executed','data'=>array('hook'=>$hook)); }
     2294        private static function cmd_cron_event_delete( array $pos, array $assoc ) { $hook=(string)($pos[0]??''); if(!$hook)return array('ok'=>false,'message'=>'Usage: cron event delete <hook>','data'=>array()); $count=0; while(false!==($ts=wp_next_scheduled($hook))){ wp_unschedule_event($ts,$hook); $count++; if($count>1000)break; } return array('ok'=>true,'message'=>'Cron events removed','data'=>array('hook'=>$hook,'count'=>$count)); }
     2295
    21882296        private static function resolve_plugin_file( $slug ) {
    21892297                if ( ! function_exists( 'get_plugins' ) ) {
  • zoofiai-mcp-connector/trunk/includes/class-zoofi-smart.php

    r3706882 r3734304  
    320320                $id = wp_generate_password( 24, false, false );
    321321                $meta = array();
     322                $builder_meta = array( '_elementor_data', '_elementor_edit_mode', '_bricks_page_content_2', '_bricks_page_header_2', '_bricks_page_footer_2', '_breakdance_data', '_fl_builder_data', '_fl_builder_draft' );
    322323                foreach ( get_post_meta( $post_id ) as $key => $values ) {
    323                         if ( '_thumbnail_id' === $key || '_wp_page_template' === $key || 0 === strpos( $key, 'rank_math_' ) || 0 === strpos( $key, '_yoast_wpseo_' ) ) {
     324                        if ( '_thumbnail_id' === $key || '_wp_page_template' === $key || in_array( $key, $builder_meta, true ) || 0 === strpos( $key, 'rank_math_' ) || 0 === strpos( $key, '_yoast_wpseo_' ) ) {
    324325                                $meta[ $key ] = array_map( 'maybe_unserialize', $values );
    325326                        }
  • zoofiai-mcp-connector/trunk/includes/class-zoofi-theme-drafts.php

    r3706882 r3734304  
    176176        }
    177177
     178        /** Structural outline of a draft file without reading the whole source. */
     179        public static function outline_file( $id, $file ) {
     180                $record = self::record( $id );
     181                if ( is_wp_error( $record ) ) return $record;
     182                $result = Zoofi_File_Ops::outline( $record['draft_slug'], $file );
     183                if ( ! is_wp_error( $result ) ) {
     184                        $result['draft_id'] = $id;
     185                        $result['live_theme_unchanged'] = true;
     186                }
     187                return $result;
     188        }
     189
    178190        /** Write only inside a draft clone. */
    179191        public static function write_file( $id, $file, $content ) {
    … …  
    208220                if ( is_wp_error( $current ) ) return $current;
    209221                $body = (string) ( $current['content'] ?? '' );
    210                 if ( false === strpos( $body, (string) $old_content ) ) return Zoofi_Error::invalid( 'old_not_found', 'old_content was not found in the draft file.' );
    211                 $result = self::write_file( $id, $file, str_replace( (string) $old_content, (string) $new_content, $body ) );
     222                $matches = substr_count( $body, (string) $old_content );
     223                if ( 0 === $matches ) return Zoofi_Error::invalid( 'old_not_found', 'old_content was not found in the draft file.' );
     224                if ( 1 !== $matches ) return Zoofi_Error::invalid( 'multiple_matches', 'old_content matched more than once. Add surrounding context so the edit is unambiguous.', array( 'status' => 409, 'matches' => $matches ) );
     225                $pos = strpos( $body, (string) $old_content );
     226                $updated = substr( $body, 0, $pos ) . (string) $new_content . substr( $body, $pos + strlen( (string) $old_content ) );
     227                $result = self::write_file( $id, $file, $updated );
    212228                if ( ! is_wp_error( $result ) ) $result['mode'] = 'edit';
    213229                return $result;
    … …  
    277293                        if ( is_wp_error( $check ) ) $errors[] = array( 'file' => $relative, 'message' => $check->get_error_message() );
    278294                }
    279                 return array( 'ok' => empty( $errors ), 'draft_id' => $id, 'php_files_checked' => $checked, 'errors' => $errors );
     295                $summary = self::summary( $id );
     296                $required = array( 'style.css' );
     297                if ( empty( $record['is_child'] ) ) $required[] = 'index.php';
     298                $missing_required = array();
     299                foreach ( $required as $required_file ) if ( ! is_file( trailingslashit( $dir ) . $required_file ) ) $missing_required[] = $required_file;
     300                if ( $missing_required ) $errors[] = array( 'file' => implode( ', ', $missing_required ), 'message' => 'Required theme file(s) missing.' );
     301                return array( 'ok' => empty( $errors ), 'draft_id' => $id, 'php_files_checked' => $checked, 'errors' => $errors, 'missing_required_files' => $missing_required, 'changes' => is_wp_error( $summary ) ? null : $summary['changes'], 'publish_ready' => empty( $errors ) );
    280302        }
    281303
  • zoofiai-mcp-connector/trunk/readme.txt

    r3711936 r3734304  
    55Tested up to: 7.1
    66Requires PHP: 7.4
    7 Stable tag: 2.0.2
     7Stable tag: 2.1.0
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    1010
    11 Securely connect WordPress to ZoofiAI and MCP-compatible AI clients for audited content, media, settings, plugin/theme management, and bulk workflows. Version 2.0 is a mega update with Durable Jobs for persistent, resumable long AI workflows.
     11Securely connect WordPress to ZoofiAI and MCP-compatible AI clients for audited content, media, settings, plugin/theme management, and bulk workflows. Version 2.1 adds builder-aware surgical editing, stronger theme-draft validation/outlines and expanded safe WP-CLI coverage while retaining Durable Jobs for persistent, resumable long AI workflows.
    1212
    1313== Description ==
    … …  
    6060== Changelog ==
    6161
     62= 2.1.0 =
     63* Advanced Theme Lab: structural draft-file outlines, exact-match editing, publish-readiness validation, live-theme conflict detection, private preview, full publish backup and reversible rollback.
     64* Builder-aware surgical content engine for Elementor, Divi, Bricks, Breakdance, Beaver Builder, Gutenberg and classic editor content. Large payloads stay on WordPress; AI receives only matching snippets.
     65* Safe structural replacement for recognized serialized builder arrays; serialization byte lengths are regenerated instead of raw byte replacement. Object graphs remain blocked.
     66* Automatic content checkpoints now include supported page-builder meta so builder edits can be rolled back with Zoofi checkpoints.
     67* Expanded WP-CLI emulator coverage: user meta, option pluck/patch, taxonomy terms, post-term assignments, theme mods, custom roles/capabilities, cron test/run/delete, alongside existing plugin/theme/post/user/cache/database operations.
     68* Full Administrator signed connections continue to execute authorized operations without per-operation preview-token prompts.
     69
     70
    6271= 2.0.2 =
    63 * Updated plugin icon.
    64 * Performance optimizations.
    65 * Bug fixes.
     72* Fixed an activation regression where the connector could treat its own plugin basename as a conflicting Zoofi edition and immediately deactivate itself after WordPress reported “Plugin activated.”
     73* Edition switching now skips the current plugin and only deactivates an actually different Zoofi edition.
     74* Renamed the custom app.zoofiai.com plugin-row link from “View details” to “ZoofiAI Dashboard” so it no longer duplicates WordPress's built-in “View details” link.
     75* Preserves the 2.0.1 Full Administrator no-preview-token execution behavior and all existing pairing/security controls.
    6676
    6777= 2.0.1 =
  • zoofiai-mcp-connector/trunk/zoofiai-mcp-connector.php

    r3711936 r3734304  
    33 * Plugin Name:       ZoofiAI – Connect Your Site with ChatGPT, Claude & Any AI Agent via MCP
    44 * Description:       Securely connect WordPress to ZoofiAI and authorized MCP-compatible AI clients for content, media, settings, plugin/theme management, bulk workflows, diagnostics, and audited automation.
    5  * Version:           2.0.2
     5 * Version:           2.1.0
    66 * Plugin URI:        https://app.zoofiai.com/
    77 * Author:            Zoofi
    … …  
    159159
    160160if ( zoofi_private_runtime_allowed() ) {
    161 define( 'ZOOFI_VERSION', '2.0.2' );
     161define( 'ZOOFI_VERSION', '2.1.0' );
    162162define( 'ZOOFI_PLUGIN_FILE', __FILE__ );
    163163define( 'ZOOFI_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
    … …  
    171171require_once ZOOFI_PLUGIN_DIR . 'includes/class-zoofi-file-ops.php';
    172172require_once ZOOFI_PLUGIN_DIR . 'includes/class-zoofi-theme-drafts.php';
     173require_once ZOOFI_PLUGIN_DIR . 'includes/class-zoofi-content-engine.php';
    173174require_once ZOOFI_PLUGIN_DIR . 'includes/class-zoofi-plugin-file-ops.php';
    174175require_once ZOOFI_PLUGIN_DIR . 'includes/class-zoofi-receipts.php';
Note: See TracChangeset for help on using the changeset viewer.