Plugin Directory

Changeset 3792868


Ignore:
Timestamp:
11/09/2026 04:32:10 PM (less than one hour ago)
Author:
WPZOOM
Message:

Update to version 0.9.0 from GitHub

Location:
wpzoom-ai-connect
Files:
2 added
18 edited
1 copied

Legend:

Unmodified
Added
Removed
  • wpzoom-ai-connect/tags/0.9.0/assets/admin/wpzoom-ai.css

    r3787541 r3792868  
    4949
    5050/* ---------- page column ---------- */
    51 .wzai-main { max-width: 1180px; min-width: 0; }
     51.wzai-main { max-width: 1180px; min-width: 0; margin-inline: auto; }
    5252.wzai-main > * + * { margin-top: 25px; }
     53.wzai-main > .wp-header-end + * { margin-top: 0; }
    5354
    5455/* ---------- brand (inside the hero) ---------- */
    … …  
    8182.wzai-card__foot { font-size: 13px; color: var(--wzai-text-3); margin-top: 14px; }
    8283.wzai-card__actions { display: flex; gap: 10px; flex-wrap: wrap; margin-top: auto; }
     84
     85/* ---------- custom post types ---------- */
     86.wzai-types__row { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 8px 16px; padding: 10px 0; border-top: 1px solid var(--wzai-border-soft); }
     87.wzai-types__row:last-child { border-bottom: 1px solid var(--wzai-border-soft); }
     88.wzai-types__note { font-size: 13px; color: var(--wzai-text-3); }
     89.wzai .wzai-types select { min-width: 190px; padding: 0 36px 0 12px; background-position: right 12px top 55%; }
    8390
    8491/* ---------- buttons (theme dashboard metrics) ---------- */
  • wpzoom-ai-connect/tags/0.9.0/includes/abilities-more.php

    r3787541 r3792868  
    267267                'permission_callback' => static function () {
    268268                        // Forms are published right away (an unpublished form cannot be embedded).
     269                        if ( ! WPZOOM_AI_Post_Types::may_write( 'wpzf-form' ) ) {
     270                                return false;
     271                        }
    269272                        $type = get_post_type_object( 'wpzf-form' );
    270273                        return $type ? current_user_can( $type->cap->create_posts ) && current_user_can( $type->cap->publish_posts ) : current_user_can( 'publish_posts' );
  • wpzoom-ai-connect/tags/0.9.0/includes/abilities-site.php

    r3789170 r3792868  
    7575                'permission_callback' => $can( 'edit_css' ),
    7676                'meta'                => $meta( false, true, true ),
     77        ) );
     78
     79        /* ----- custom post types ----- */
     80
     81        wp_register_ability( 'wpzoom/list-post-types', array(
     82                'label'               => 'List the custom post types open to the AI',
     83                'description'         => 'The custom post types the site owner opened on the AI Connect screen, each with its access (read or edit), REST route, supported fields and taxonomies. Types not listed are closed.',
     84                'category'            => 'wpzoom',
     85                'input_schema'        => $input( array() ),
     86                'output_schema'       => $object,
     87                'execute_callback'    => static function () {
     88                        return array( 'post_types' => WPZOOM_AI_Post_Types::list_open() );
     89                },
     90                'permission_callback' => $can( 'read' ),
     91                'meta'                => $meta( true, true ),
    7792        ) );
    7893
  • wpzoom-ai-connect/tags/0.9.0/includes/class-wpzoom-ai-admin.php

    r3789170 r3792868  
    3131                add_action( 'admin_post_wpzoom_ai_disconnect', array( __CLASS__, 'handle_disconnect' ) );
    3232                add_action( 'admin_post_wpzoom_ai_css_setting', array( __CLASS__, 'handle_css_setting' ) );
     33                add_action( 'admin_post_wpzoom_ai_post_types', array( __CLASS__, 'handle_post_types' ) );
    3334                add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue' ) );
    3435                add_filter( 'plugin_action_links_' . plugin_basename( WPZOOM_AI_CONNECT_FILE ), array( __CLASS__, 'action_links' ) );
    … …  
    145146                update_option( WPZOOM_AI_CSS::OPTION, empty( $_POST['allow_css'] ) ? '0' : '1', false );
    146147                wp_safe_redirect( self::url( array( 'wzai_css_saved' => 1 ) ) . '#wzai-start' );
     148                exit;
     149        }
     150
     151        /** Saves which custom post types the connection may read or change. */
     152        public static function handle_post_types(): void {
     153                check_admin_referer( 'wpzoom_ai_post_types' );
     154                if ( ! current_user_can( 'manage_options' ) ) {
     155                        wp_die( esc_html__( 'You are not allowed to change this setting.', 'wpzoom-ai-connect' ) );
     156                }
     157                $posted = isset( $_POST['post_types'] ) && is_array( $_POST['post_types'] ) ? map_deep( wp_unslash( $_POST['post_types'] ), 'sanitize_key' ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitized by map_deep; save() also checks every type and value against its own lists.
     158                WPZOOM_AI_Post_Types::save( $posted );
     159                wp_safe_redirect( self::url( array( 'wzai_types_saved' => 1 ) ) . '#wzai-post-types' );
    147160                exit;
    148161        }
    … …  
    385398                $kinds       = array_keys( array_filter( $s['content'] ) );
    386399                $kind_label  = array( 'forms' => __( 'forms', 'wpzoom-ai-connect' ), 'portfolio' => __( 'portfolio items', 'wpzoom-ai-connect' ), 'recipes' => __( 'recipe cards', 'wpzoom-ai-connect' ) );
     400                $post_types  = current_user_can( 'manage_options' ) ? WPZOOM_AI_Post_Types::choices() : array();
    387401                ?>
    388402                <div class="wrap wzai" id="wzai">
    389403                                <div class="wzai-main">
     404                                        <?php // WordPress moves other plugins' notices here; without it they land after the h1 inside the hero. ?>
     405                                        <hr class="wp-header-end">
    390406
    391407                                        <?php
    … …  
    399415                                                        ? esc_html__( 'Saved. Your AI may add Additional CSS on this site.', 'wpzoom-ai-connect' )
    400416                                                        : esc_html__( 'Saved. Your AI can no longer change Additional CSS on this site.', 'wpzoom-ai-connect' ) );
     417                                        }
     418                                        if ( isset( $_GET['wzai_types_saved'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
     419                                                self::notice( 'ok', esc_html__( 'Saved. Your AI now follows these choices for custom post types.', 'wpzoom-ai-connect' ) );
    401420                                        }
    402421                                        if ( ! $s['has_api'] ) {
    … …  
    599618                                        </section>
    600619
     620                                        <?php if ( $post_types ) : ?>
     621                                                <section class="wzai-section" id="wzai-post-types">
     622                                                        <div class="wzai-section__head">
     623                                                                <h2><?php esc_html_e( 'Custom post types your AI can use', 'wpzoom-ai-connect' ); ?></h2>
     624                                                                <p><?php esc_html_e( 'Choose what your AI may do with each type. Posts and pages are always available. New types stay closed until you open them, because some hold private data such as form entries or orders.', 'wpzoom-ai-connect' ); ?></p>
     625                                                        </div>
     626                                                        <form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
     627                                                                <?php wp_nonce_field( 'wpzoom_ai_post_types' ); ?>
     628                                                                <input type="hidden" name="action" value="wpzoom_ai_post_types">
     629                                                        <div class="wzai-types">
     630                                                                <?php foreach ( $post_types as $choice ) : ?>
     631                                                                        <?php $field = 'wzai-type-' . sanitize_html_class( $choice['type'] ); ?>
     632                                                                        <div class="wzai-types__row">
     633                                                                                <label for="<?php echo esc_attr( $field ); ?>"><strong><?php echo esc_html( $choice['label'] ); ?></strong> <code><?php echo esc_html( $choice['type'] ); ?></code></label>
     634                                                                                <?php if ( $choice['available'] ) : ?>
     635                                                                                        <select id="<?php echo esc_attr( $field ); ?>" name="post_types[<?php echo esc_attr( $choice['type'] ); ?>]">
     636                                                                                                <option value="none" <?php selected( $choice['mode'], WPZOOM_AI_Post_Types::NONE ); ?>><?php esc_html_e( 'No access', 'wpzoom-ai-connect' ); ?></option>
     637                                                                                                <option value="read" <?php selected( $choice['mode'], WPZOOM_AI_Post_Types::READ ); ?>><?php esc_html_e( 'Can read', 'wpzoom-ai-connect' ); ?></option>
     638                                                                                                <option value="edit" <?php selected( $choice['mode'], WPZOOM_AI_Post_Types::EDIT ); ?>><?php esc_html_e( 'Can read and edit', 'wpzoom-ai-connect' ); ?></option>
     639                                                                                        </select>
     640                                                                                <?php else : ?>
     641                                                                                        <span class="wzai-types__note"><?php esc_html_e( 'Not available: this type is closed to the WordPress REST API', 'wpzoom-ai-connect' ); ?></span>
     642                                                                                <?php endif; ?>
     643                                                                        </div>
     644                                                                <?php endforeach; ?>
     645                                                        </div>
     646                                                                <div class="wzai-card__actions wzai-mt-16"><button type="submit" class="wzai-btn wzai-btn--primary"><?php esc_html_e( 'Save', 'wpzoom-ai-connect' ); ?></button></div>
     647                                                                <div class="wzai-card__foot"><?php esc_html_e( 'Published pages your visitors can see stay public, so your AI can still view those like any visitor.', 'wpzoom-ai-connect' ); ?></div>
     648                                                        </form>
     649                                                </section>
     650                                        <?php endif; ?>
     651
    601652                                        <?php if ( $warnings ) : ?>
    602653                                                <section class="wzai-section" id="wzai-checkups">
  • wpzoom-ai-connect/tags/0.9.0/includes/class-wpzoom-ai-content.php

    r3787541 r3792868  
    9797         * Whether the current user may create posts of this type, by the type's own capabilities (a custom post type
    9898         * can map them to anything). A type that is not registered falls back to edit_posts, so the ability itself can
    99          * explain which plugin is missing.
     99         * explain which plugin is missing. The connection also needs the site owner's choice for the type to allow it.
    100100         */
    101101        public static function can_create( string $post_type ): bool {
     102                if ( ! WPZOOM_AI_Post_Types::may_write( $post_type ) ) {
     103                        return false;
     104                }
    102105                $type = get_post_type_object( $post_type );
    103106                return $type ? current_user_can( $type->cap->create_posts ) : current_user_can( 'edit_posts' );
  • wpzoom-ai-connect/tags/0.9.0/includes/class-wpzoom-ai-scope.php

    r3789170 r3792868  
    1313 *     an editor's). Additional CSS is the one exception: allowed unless the site owner switches it off on the
    1414 *     AI Connect screen, and still subject to WordPress's own edit_css rules;
    15  *   - reach REST routes outside WordPress core (wp/v2), the Abilities API and oEmbed, or use XML-RPC.
     15 *   - reach REST routes outside WordPress core (wp/v2), the Abilities API and oEmbed, or use XML-RPC;
     16 *   - read or change a custom post type beyond what the site owner chose on the AI Connect screen
     17 *     (WPZOOM_AI_Post_Types: closed unless they open it, except WPZOOM's own types).
    1618 * The user's own sessions and their other application passwords are not affected.
    1719 * Site owners can adjust the route rule with the `wpzoom_ai_connect_allowed_route` filter.
    … …  
    142144                        $denied = in_array( $cap, self::READ_ONLY_CAPS, true ) || array_intersect( $caps, self::READ_ONLY_CAPS );
    143145                }
     146                if ( ! $denied ) {
     147                        $denied = WPZOOM_AI_Post_Types::denies_cap( $cap, (array) $args, self::is_read_request() );
     148                }
    144149                if ( $denied && ! in_array( 'do_not_allow', $caps, true ) ) {
    145150                        $caps[] = 'do_not_allow';
    … …  
    151156         * Whether the connection may call a REST route: WordPress core's own API (wp/v2), the Abilities API, oEmbed and
    152157         * the index. Inside wp/v2, plugins, themes, users and settings are read-only, and application passwords and the
    153          * block directory are off limits. Everything else (other plugins' namespaces, batch, site health, the block
    154          * editor's helpers) is not reachable through the connection.
     158         * block directory are off limits. A custom post type's routes follow the owner's choice for that type, under its
     159         * own namespace too. Everything else (other plugins' namespaces, batch, site health, the block editor's helpers)
     160         * is not reachable through the connection.
    155161         */
    156162        public static function route_allowed( string $route, string $method ): bool {
    157163                $route = '/' . ltrim( $route, '/' );
    158164                $read  = in_array( strtoupper( $method ), array( 'GET', 'HEAD', 'OPTIONS' ), true );
     165                $mode  = WPZOOM_AI_Post_Types::mode_for_route( $route );
    159166                if ( in_array( $route, array( '/', '/wp/v2', '/wp-abilities/v1', '/oembed/1.0' ), true ) ) {
    160167                        $allowed = true;
    … …  
    171178                                $allowed = $read;
    172179                        } else {
    173                                 $allowed = true;
     180                                $allowed = null === $mode || WPZOOM_AI_Post_Types::mode_allows( $mode, $read );
    174181                        }
    175182                } else {
    176                         $allowed = false;
     183                        $allowed = null !== $mode && WPZOOM_AI_Post_Types::mode_allows( $mode, $read );
    177184                }
    178185                /**
    … …  
    193200                if ( self::route_allowed( $request->get_route(), $request->get_method() ) ) {
    194201                        return $result;
     202                }
     203                if ( null !== WPZOOM_AI_Post_Types::mode_for_route( '/' . ltrim( $request->get_route(), '/' ) ) ) {
     204                        return new WP_Error(
     205                                'wpzoom_ai_connect_post_type_closed',
     206                                sprintf(
     207                                        /* translators: 1: HTTP method, 2: REST route */
     208                                        __( 'The site owner has not given the WPZOOM AI Connect connection this access to this post type (%1$s %2$s). They can change it on the AI Connect screen in WordPress.', 'wpzoom-ai-connect' ),
     209                                        $request->get_method(),
     210                                        $request->get_route()
     211                                ),
     212                                array( 'status' => 403 )
     213                        );
    195214                }
    196215                return new WP_Error(
  • wpzoom-ai-connect/tags/0.9.0/includes/class-wpzoom-ai-site.php

    r3787541 r3792868  
    133133                                continue;
    134134                        }
     135                        if ( WPZOOM_AI_Scope::is_connector_request() && WPZOOM_AI_Post_Types::NONE === WPZOOM_AI_Post_Types::mode( $type->name ) ) {
     136                                continue; // The site owner keeps this type away from the connection.
     137                        }
    135138                        $count = wp_count_posts( $type->name );
    136139                        $counts[ $type->name ] = array(
  • wpzoom-ai-connect/tags/0.9.0/readme.txt

    r3792521 r3792868  
    55Tested up to: 7.1
    66Requires PHP: 7.4
    7 Stable tag: 0.8.0
     7Stable tag: 0.9.0
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    102102== Changelog ==
    103103
     104= 0.9.0 =
     105* New: choose on the AI Connect screen what your AI may do with each custom post type: no access, read, or read and edit. WordPress enforces your choice for the WPZOOM AI Connect connection. Posts and pages are not affected.
     106* Changed: custom post types are now closed to the connection until you open them, because some hold private data such as form entries or orders. WPZOOM's own types (portfolio, slideshow, recipes, forms and Elementor templates) stay open unless you close them. If your AI edits other custom post types built with Elementor, open them on the AI Connect screen.
     107
    104108= 0.8.0 =
    105109* New: read and add Additional CSS (Customizer, or Site Editor styles on block themes). The AI adds small changes as labelled blocks it can replace or remove; CSS you wrote is not touched and WordPress keeps a revision of every change.
  • wpzoom-ai-connect/tags/0.9.0/wpzoom-ai-connect.php

    r3789170 r3792868  
    44 * Plugin URI: https://www.wpzoom.com/ai-connect/
    55 * Description: Lets AI assistants (Claude, ChatGPT, Cursor and any MCP client) understand and change your WPZOOM theme: theme settings, color palettes, design patterns and pages, through the WordPress Abilities API.
    6  * Version: 0.8.0
     6 * Version: 0.9.0
    77 * Requires at least: 6.9
    88 * Requires PHP: 7.4
    … …  
    2020}
    2121
    22 define( 'WPZOOM_AI_CONNECT_VERSION', '0.8.0' );
     22define( 'WPZOOM_AI_CONNECT_VERSION', '0.9.0' );
    2323define( 'WPZOOM_AI_CONNECT_FILE', __FILE__ );
    2424define( 'WPZOOM_AI_CONNECT_DIR', plugin_dir_path( __FILE__ ) );
    … …  
    3737require_once WPZOOM_AI_CONNECT_DIR . 'includes/class-wpzoom-ai-updates.php';
    3838require_once WPZOOM_AI_CONNECT_DIR . 'includes/class-wpzoom-ai-elementor.php';
     39require_once WPZOOM_AI_CONNECT_DIR . 'includes/class-wpzoom-ai-post-types.php';
    3940require_once WPZOOM_AI_CONNECT_DIR . 'includes/class-wpzoom-ai-scope.php';
    4041require_once WPZOOM_AI_CONNECT_DIR . 'includes/class-wpzoom-ai-cache.php';
  • wpzoom-ai-connect/trunk/assets/admin/wpzoom-ai.css

    r3787541 r3792868  
    4949
    5050/* ---------- page column ---------- */
    51 .wzai-main { max-width: 1180px; min-width: 0; }
     51.wzai-main { max-width: 1180px; min-width: 0; margin-inline: auto; }
    5252.wzai-main > * + * { margin-top: 25px; }
     53.wzai-main > .wp-header-end + * { margin-top: 0; }
    5354
    5455/* ---------- brand (inside the hero) ---------- */
    … …  
    8182.wzai-card__foot { font-size: 13px; color: var(--wzai-text-3); margin-top: 14px; }
    8283.wzai-card__actions { display: flex; gap: 10px; flex-wrap: wrap; margin-top: auto; }
     84
     85/* ---------- custom post types ---------- */
     86.wzai-types__row { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 8px 16px; padding: 10px 0; border-top: 1px solid var(--wzai-border-soft); }
     87.wzai-types__row:last-child { border-bottom: 1px solid var(--wzai-border-soft); }
     88.wzai-types__note { font-size: 13px; color: var(--wzai-text-3); }
     89.wzai .wzai-types select { min-width: 190px; padding: 0 36px 0 12px; background-position: right 12px top 55%; }
    8390
    8491/* ---------- buttons (theme dashboard metrics) ---------- */
  • wpzoom-ai-connect/trunk/includes/abilities-more.php

    r3787541 r3792868  
    267267                'permission_callback' => static function () {
    268268                        // Forms are published right away (an unpublished form cannot be embedded).
     269                        if ( ! WPZOOM_AI_Post_Types::may_write( 'wpzf-form' ) ) {
     270                                return false;
     271                        }
    269272                        $type = get_post_type_object( 'wpzf-form' );
    270273                        return $type ? current_user_can( $type->cap->create_posts ) && current_user_can( $type->cap->publish_posts ) : current_user_can( 'publish_posts' );
  • wpzoom-ai-connect/trunk/includes/abilities-site.php

    r3789170 r3792868  
    7575                'permission_callback' => $can( 'edit_css' ),
    7676                'meta'                => $meta( false, true, true ),
     77        ) );
     78
     79        /* ----- custom post types ----- */
     80
     81        wp_register_ability( 'wpzoom/list-post-types', array(
     82                'label'               => 'List the custom post types open to the AI',
     83                'description'         => 'The custom post types the site owner opened on the AI Connect screen, each with its access (read or edit), REST route, supported fields and taxonomies. Types not listed are closed.',
     84                'category'            => 'wpzoom',
     85                'input_schema'        => $input( array() ),
     86                'output_schema'       => $object,
     87                'execute_callback'    => static function () {
     88                        return array( 'post_types' => WPZOOM_AI_Post_Types::list_open() );
     89                },
     90                'permission_callback' => $can( 'read' ),
     91                'meta'                => $meta( true, true ),
    7792        ) );
    7893
  • wpzoom-ai-connect/trunk/includes/class-wpzoom-ai-admin.php

    r3789170 r3792868  
    3131                add_action( 'admin_post_wpzoom_ai_disconnect', array( __CLASS__, 'handle_disconnect' ) );
    3232                add_action( 'admin_post_wpzoom_ai_css_setting', array( __CLASS__, 'handle_css_setting' ) );
     33                add_action( 'admin_post_wpzoom_ai_post_types', array( __CLASS__, 'handle_post_types' ) );
    3334                add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue' ) );
    3435                add_filter( 'plugin_action_links_' . plugin_basename( WPZOOM_AI_CONNECT_FILE ), array( __CLASS__, 'action_links' ) );
    … …  
    145146                update_option( WPZOOM_AI_CSS::OPTION, empty( $_POST['allow_css'] ) ? '0' : '1', false );
    146147                wp_safe_redirect( self::url( array( 'wzai_css_saved' => 1 ) ) . '#wzai-start' );
     148                exit;
     149        }
     150
     151        /** Saves which custom post types the connection may read or change. */
     152        public static function handle_post_types(): void {
     153                check_admin_referer( 'wpzoom_ai_post_types' );
     154                if ( ! current_user_can( 'manage_options' ) ) {
     155                        wp_die( esc_html__( 'You are not allowed to change this setting.', 'wpzoom-ai-connect' ) );
     156                }
     157                $posted = isset( $_POST['post_types'] ) && is_array( $_POST['post_types'] ) ? map_deep( wp_unslash( $_POST['post_types'] ), 'sanitize_key' ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitized by map_deep; save() also checks every type and value against its own lists.
     158                WPZOOM_AI_Post_Types::save( $posted );
     159                wp_safe_redirect( self::url( array( 'wzai_types_saved' => 1 ) ) . '#wzai-post-types' );
    147160                exit;
    148161        }
    … …  
    385398                $kinds       = array_keys( array_filter( $s['content'] ) );
    386399                $kind_label  = array( 'forms' => __( 'forms', 'wpzoom-ai-connect' ), 'portfolio' => __( 'portfolio items', 'wpzoom-ai-connect' ), 'recipes' => __( 'recipe cards', 'wpzoom-ai-connect' ) );
     400                $post_types  = current_user_can( 'manage_options' ) ? WPZOOM_AI_Post_Types::choices() : array();
    387401                ?>
    388402                <div class="wrap wzai" id="wzai">
    389403                                <div class="wzai-main">
     404                                        <?php // WordPress moves other plugins' notices here; without it they land after the h1 inside the hero. ?>
     405                                        <hr class="wp-header-end">
    390406
    391407                                        <?php
    … …  
    399415                                                        ? esc_html__( 'Saved. Your AI may add Additional CSS on this site.', 'wpzoom-ai-connect' )
    400416                                                        : esc_html__( 'Saved. Your AI can no longer change Additional CSS on this site.', 'wpzoom-ai-connect' ) );
     417                                        }
     418                                        if ( isset( $_GET['wzai_types_saved'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
     419                                                self::notice( 'ok', esc_html__( 'Saved. Your AI now follows these choices for custom post types.', 'wpzoom-ai-connect' ) );
    401420                                        }
    402421                                        if ( ! $s['has_api'] ) {
    … …  
    599618                                        </section>
    600619
     620                                        <?php if ( $post_types ) : ?>
     621                                                <section class="wzai-section" id="wzai-post-types">
     622                                                        <div class="wzai-section__head">
     623                                                                <h2><?php esc_html_e( 'Custom post types your AI can use', 'wpzoom-ai-connect' ); ?></h2>
     624                                                                <p><?php esc_html_e( 'Choose what your AI may do with each type. Posts and pages are always available. New types stay closed until you open them, because some hold private data such as form entries or orders.', 'wpzoom-ai-connect' ); ?></p>
     625                                                        </div>
     626                                                        <form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
     627                                                                <?php wp_nonce_field( 'wpzoom_ai_post_types' ); ?>
     628                                                                <input type="hidden" name="action" value="wpzoom_ai_post_types">
     629                                                        <div class="wzai-types">
     630                                                                <?php foreach ( $post_types as $choice ) : ?>
     631                                                                        <?php $field = 'wzai-type-' . sanitize_html_class( $choice['type'] ); ?>
     632                                                                        <div class="wzai-types__row">
     633                                                                                <label for="<?php echo esc_attr( $field ); ?>"><strong><?php echo esc_html( $choice['label'] ); ?></strong> <code><?php echo esc_html( $choice['type'] ); ?></code></label>
     634                                                                                <?php if ( $choice['available'] ) : ?>
     635                                                                                        <select id="<?php echo esc_attr( $field ); ?>" name="post_types[<?php echo esc_attr( $choice['type'] ); ?>]">
     636                                                                                                <option value="none" <?php selected( $choice['mode'], WPZOOM_AI_Post_Types::NONE ); ?>><?php esc_html_e( 'No access', 'wpzoom-ai-connect' ); ?></option>
     637                                                                                                <option value="read" <?php selected( $choice['mode'], WPZOOM_AI_Post_Types::READ ); ?>><?php esc_html_e( 'Can read', 'wpzoom-ai-connect' ); ?></option>
     638                                                                                                <option value="edit" <?php selected( $choice['mode'], WPZOOM_AI_Post_Types::EDIT ); ?>><?php esc_html_e( 'Can read and edit', 'wpzoom-ai-connect' ); ?></option>
     639                                                                                        </select>
     640                                                                                <?php else : ?>
     641                                                                                        <span class="wzai-types__note"><?php esc_html_e( 'Not available: this type is closed to the WordPress REST API', 'wpzoom-ai-connect' ); ?></span>
     642                                                                                <?php endif; ?>
     643                                                                        </div>
     644                                                                <?php endforeach; ?>
     645                                                        </div>
     646                                                                <div class="wzai-card__actions wzai-mt-16"><button type="submit" class="wzai-btn wzai-btn--primary"><?php esc_html_e( 'Save', 'wpzoom-ai-connect' ); ?></button></div>
     647                                                                <div class="wzai-card__foot"><?php esc_html_e( 'Published pages your visitors can see stay public, so your AI can still view those like any visitor.', 'wpzoom-ai-connect' ); ?></div>
     648                                                        </form>
     649                                                </section>
     650                                        <?php endif; ?>
     651
    601652                                        <?php if ( $warnings ) : ?>
    602653                                                <section class="wzai-section" id="wzai-checkups">
  • wpzoom-ai-connect/trunk/includes/class-wpzoom-ai-content.php

    r3787541 r3792868  
    9797         * Whether the current user may create posts of this type, by the type's own capabilities (a custom post type
    9898         * can map them to anything). A type that is not registered falls back to edit_posts, so the ability itself can
    99          * explain which plugin is missing.
     99         * explain which plugin is missing. The connection also needs the site owner's choice for the type to allow it.
    100100         */
    101101        public static function can_create( string $post_type ): bool {
     102                if ( ! WPZOOM_AI_Post_Types::may_write( $post_type ) ) {
     103                        return false;
     104                }
    102105                $type = get_post_type_object( $post_type );
    103106                return $type ? current_user_can( $type->cap->create_posts ) : current_user_can( 'edit_posts' );
  • wpzoom-ai-connect/trunk/includes/class-wpzoom-ai-scope.php

    r3789170 r3792868  
    1313 *     an editor's). Additional CSS is the one exception: allowed unless the site owner switches it off on the
    1414 *     AI Connect screen, and still subject to WordPress's own edit_css rules;
    15  *   - reach REST routes outside WordPress core (wp/v2), the Abilities API and oEmbed, or use XML-RPC.
     15 *   - reach REST routes outside WordPress core (wp/v2), the Abilities API and oEmbed, or use XML-RPC;
     16 *   - read or change a custom post type beyond what the site owner chose on the AI Connect screen
     17 *     (WPZOOM_AI_Post_Types: closed unless they open it, except WPZOOM's own types).
    1618 * The user's own sessions and their other application passwords are not affected.
    1719 * Site owners can adjust the route rule with the `wpzoom_ai_connect_allowed_route` filter.
    … …  
    142144                        $denied = in_array( $cap, self::READ_ONLY_CAPS, true ) || array_intersect( $caps, self::READ_ONLY_CAPS );
    143145                }
     146                if ( ! $denied ) {
     147                        $denied = WPZOOM_AI_Post_Types::denies_cap( $cap, (array) $args, self::is_read_request() );
     148                }
    144149                if ( $denied && ! in_array( 'do_not_allow', $caps, true ) ) {
    145150                        $caps[] = 'do_not_allow';
    … …  
    151156         * Whether the connection may call a REST route: WordPress core's own API (wp/v2), the Abilities API, oEmbed and
    152157         * the index. Inside wp/v2, plugins, themes, users and settings are read-only, and application passwords and the
    153          * block directory are off limits. Everything else (other plugins' namespaces, batch, site health, the block
    154          * editor's helpers) is not reachable through the connection.
     158         * block directory are off limits. A custom post type's routes follow the owner's choice for that type, under its
     159         * own namespace too. Everything else (other plugins' namespaces, batch, site health, the block editor's helpers)
     160         * is not reachable through the connection.
    155161         */
    156162        public static function route_allowed( string $route, string $method ): bool {
    157163                $route = '/' . ltrim( $route, '/' );
    158164                $read  = in_array( strtoupper( $method ), array( 'GET', 'HEAD', 'OPTIONS' ), true );
     165                $mode  = WPZOOM_AI_Post_Types::mode_for_route( $route );
    159166                if ( in_array( $route, array( '/', '/wp/v2', '/wp-abilities/v1', '/oembed/1.0' ), true ) ) {
    160167                        $allowed = true;
    … …  
    171178                                $allowed = $read;
    172179                        } else {
    173                                 $allowed = true;
     180                                $allowed = null === $mode || WPZOOM_AI_Post_Types::mode_allows( $mode, $read );
    174181                        }
    175182                } else {
    176                         $allowed = false;
     183                        $allowed = null !== $mode && WPZOOM_AI_Post_Types::mode_allows( $mode, $read );
    177184                }
    178185                /**
    … …  
    193200                if ( self::route_allowed( $request->get_route(), $request->get_method() ) ) {
    194201                        return $result;
     202                }
     203                if ( null !== WPZOOM_AI_Post_Types::mode_for_route( '/' . ltrim( $request->get_route(), '/' ) ) ) {
     204                        return new WP_Error(
     205                                'wpzoom_ai_connect_post_type_closed',
     206                                sprintf(
     207                                        /* translators: 1: HTTP method, 2: REST route */
     208                                        __( 'The site owner has not given the WPZOOM AI Connect connection this access to this post type (%1$s %2$s). They can change it on the AI Connect screen in WordPress.', 'wpzoom-ai-connect' ),
     209                                        $request->get_method(),
     210                                        $request->get_route()
     211                                ),
     212                                array( 'status' => 403 )
     213                        );
    195214                }
    196215                return new WP_Error(
  • wpzoom-ai-connect/trunk/includes/class-wpzoom-ai-site.php

    r3787541 r3792868  
    133133                                continue;
    134134                        }
     135                        if ( WPZOOM_AI_Scope::is_connector_request() && WPZOOM_AI_Post_Types::NONE === WPZOOM_AI_Post_Types::mode( $type->name ) ) {
     136                                continue; // The site owner keeps this type away from the connection.
     137                        }
    135138                        $count = wp_count_posts( $type->name );
    136139                        $counts[ $type->name ] = array(
  • wpzoom-ai-connect/trunk/readme.txt

    r3792521 r3792868  
    55Tested up to: 7.1
    66Requires PHP: 7.4
    7 Stable tag: 0.8.0
     7Stable tag: 0.9.0
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    102102== Changelog ==
    103103
     104= 0.9.0 =
     105* New: choose on the AI Connect screen what your AI may do with each custom post type: no access, read, or read and edit. WordPress enforces your choice for the WPZOOM AI Connect connection. Posts and pages are not affected.
     106* Changed: custom post types are now closed to the connection until you open them, because some hold private data such as form entries or orders. WPZOOM's own types (portfolio, slideshow, recipes, forms and Elementor templates) stay open unless you close them. If your AI edits other custom post types built with Elementor, open them on the AI Connect screen.
     107
    104108= 0.8.0 =
    105109* New: read and add Additional CSS (Customizer, or Site Editor styles on block themes). The AI adds small changes as labelled blocks it can replace or remove; CSS you wrote is not touched and WordPress keeps a revision of every change.
  • wpzoom-ai-connect/trunk/wpzoom-ai-connect.php

    r3789170 r3792868  
    44 * Plugin URI: https://www.wpzoom.com/ai-connect/
    55 * Description: Lets AI assistants (Claude, ChatGPT, Cursor and any MCP client) understand and change your WPZOOM theme: theme settings, color palettes, design patterns and pages, through the WordPress Abilities API.
    6  * Version: 0.8.0
     6 * Version: 0.9.0
    77 * Requires at least: 6.9
    88 * Requires PHP: 7.4
    … …  
    2020}
    2121
    22 define( 'WPZOOM_AI_CONNECT_VERSION', '0.8.0' );
     22define( 'WPZOOM_AI_CONNECT_VERSION', '0.9.0' );
    2323define( 'WPZOOM_AI_CONNECT_FILE', __FILE__ );
    2424define( 'WPZOOM_AI_CONNECT_DIR', plugin_dir_path( __FILE__ ) );
    … …  
    3737require_once WPZOOM_AI_CONNECT_DIR . 'includes/class-wpzoom-ai-updates.php';
    3838require_once WPZOOM_AI_CONNECT_DIR . 'includes/class-wpzoom-ai-elementor.php';
     39require_once WPZOOM_AI_CONNECT_DIR . 'includes/class-wpzoom-ai-post-types.php';
    3940require_once WPZOOM_AI_CONNECT_DIR . 'includes/class-wpzoom-ai-scope.php';
    4041require_once WPZOOM_AI_CONNECT_DIR . 'includes/class-wpzoom-ai-cache.php';
Note: See TracChangeset for help on using the changeset viewer.