Plugin Directory

Changeset 3470221


Ignore:
Timestamp:
02/26/2026 11:32:23 AM (4 weeks ago)
Author:
KingYes
Message:

Upload v1.1.1

Location:
angie/trunk
Files:
13 edited

Legend:

Unmodified
Added
Removed
  • angie/trunk/angie.php

    r3464872 r3470221  
    44 * Description: Agentic AI for WordPress
    55 * Plugin URI: https://elementor.com/pages/angie-early-access
    6  * Version: 1.1.0
     6 * Version: 1.1.1
    77 * Author: Elementor.com
    88 * Author URI: https://elementor.com/
     
    2121}
    2222
    23 define( 'ANGIE_VERSION', '1.1.0' );
     23define( 'ANGIE_VERSION', '1.1.1' );
    2424define( 'ANGIE_PATH', plugin_dir_path( __FILE__ ) );
    2525define( 'ANGIE_URL', plugins_url( '/', __FILE__ ) );
  • angie/trunk/modules/angie-app/components/angie-app.php

    r3464872 r3470221  
    3737        // Custom SVG icon for Angie menu
    3838        $svg_icon = 'data:image/svg+xml;base64,' . base64_encode(
    39             '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="#a7aaad" viewBox="0 0 20 20"><path d="M0 3.36h3.36v14.428H0zM17.788 0v3.36H3.36V0zM12.225 4.413c.818 3.938 3.701 7.21 7.719 7.79-4.146.6-7.035 3.622-7.72 7.74-.643-3.872-3.96-6.885-7.755-7.74 3.798-.814 7.063-3.946 7.756-7.79ZM17.748 4.47c.233 1.113 1.109 2.036 2.252 2.2-1.18.17-2.057 1.047-2.252 2.213-.183-1.096-1.081-1.971-2.161-2.213 1.08-.23 1.964-1.113 2.16-2.2Z"/></svg>'
     39            '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
     40<path d="M21.7142 12.0059L21.0316 12.2611C19.4275 12.8645 18.4491 14.1524 17.8575 15.7883L17.6072 16.4845L17.3569 15.7883C16.7653 14.1524 15.73 12.8645 14.1259 12.2611L13.4433 12.0059L14.1259 11.7506C15.73 11.1473 16.7653 9.85935 17.3569 8.22337L17.6072 7.52721L17.8575 8.22337C18.4491 9.85935 19.4275 11.1473 21.0316 11.7506L21.7142 12.0059Z" fill="#ABAFB2"/>
     41<path d="M3 11.1104C8.94295 11.1104 13.707 16.0116 13.707 22L10.707 22C10.707 17.6126 7.23077 14.1104 3 14.1104L3 11.1104Z" fill="#ABAFB2"/>
     42<path d="M3 9.88965C7.23076 9.88965 10.707 6.38737 10.707 2L13.707 2C13.707 7.98835 8.94295 12.8896 3 12.8896L3 9.88965Z" fill="#ABAFB2"/>
     43</svg>'
    4044        );
    4145
     
    243247                        <div class="angie-app-start" id="angie-app-start" data-testid="angie-app-start">
    244248                        <h4>
    245                             <?php esc_html_e( 'Meet Angie,', 'angie' ); ?><br>
    246                             <?php esc_html_e( 'your new AI assistant', 'angie' ); ?>
     249                            <?php esc_html_e( 'Let\'s build something new with Angie', 'angie' ); ?><br>
    247250                        </h4>
    248                     <p><?php esc_html_e( 'Build and manage WordPress sites effortlessly with Agentic AI.', 'angie' ); ?></p>
     251                        <p><?php esc_html_e( 'Generate custom Elementor widgets, forms, layouts, and code snippets tailored to your site.', 'angie' ); ?><br><?php esc_html_e( 'Scale your capabilities and experiment freely. Everything is editable and nothing goes live until you approve.', 'angie' ); ?></p>
    249252                    <?php if ( $is_in_oauth_flow ) : ?>
    250253                            <div class="angie-loading-state" data-testid="angie-loading-state">
  • angie/trunk/modules/code-snippets/classes/deployment-meta-box.php

    r3464872 r3470221  
    147147
    148148        echo '<p>';
     149        $is_deploy_button_disabled = Dev_Mode_Manager::is_deploy_button_disabled( $dev_time, $prod_time );
    149150        $deploy_action = ( $dev_time > 0 ) ? 'push-to-production' : 'publish-to-dev';
    150151        $button_text = ( $dev_time > 0 ) ? esc_html__( 'Push to Production', 'angie' ) : esc_html__( 'Push to Test', 'angie' );
    151         submit_button( $button_text, 'primary', 'angie_push_to_production', false, [ 'data-action' => $deploy_action ] );
     152        $button_attrs = [ 'data-action' => $deploy_action ];
     153        if ( $is_deploy_button_disabled ) {
     154            $button_attrs['disabled'] = 'disabled';
     155        }
     156        submit_button( $button_text, 'primary', 'angie_push_to_production', false, $button_attrs );
    152157        echo '</p>';
    153158        echo '</div>';
  • angie/trunk/modules/code-snippets/classes/dev-mode-admin-ui.php

    r3464872 r3470221  
    2828
    2929        if ( $is_dev_mode ) {
    30             echo '<div class="notice notice-warning is-dismissible">';
     30            echo '<div class="notice notice-warning">';
    3131            echo '<p>';
    3232            echo '<strong>' . esc_html__( 'Test Mode is Active', 'angie' ) . '</strong> - ' . esc_html__( 'Snippets are loading from the development environment.', 'angie' ) . ' ';
  • angie/trunk/modules/code-snippets/classes/dev-mode-manager.php

    r3464872 r3470221  
    177177    }
    178178
     179    public static function is_deploy_button_disabled( $dev_time, $prod_time ) {
     180        return $dev_time > 0 && $prod_time >= $dev_time;
     181    }
     182
    179183    public static function push_snippet_to_dev( $post_id ) {
    180184        $files = get_post_meta( $post_id, '_angie_snippet_files', true );
  • angie/trunk/modules/code-snippets/classes/list-table-manager.php

    r3464872 r3470221  
    171171        $dev_time = $timestamps['dev'];
    172172        $prod_time = $timestamps['prod'];
    173 
    174         if ( $dev_time === 0 ) {
    175             $is_disabled = false;
    176         } elseif ( $dev_time > 0 && ( $prod_time === 0 || $dev_time > $prod_time ) ) {
    177             $is_disabled = false;
    178         } else {
    179             $is_disabled = true;
    180         }
     173        $is_deploy_button_disabled = Dev_Mode_Manager::is_deploy_button_disabled( $dev_time, $prod_time );
    181174
    182175        $post = get_post( $post_id );
    183176        $snippet_slug = $post ? $post->post_name : '';
    184177
    185         $disabled_attr = $is_disabled ? ' disabled' : '';
     178        $disabled_attr = $is_deploy_button_disabled ? ' disabled' : '';
    186179        $deploy_action = ( $dev_time > 0 ) ? 'push-to-production' : 'publish-to-dev';
    187180        $button_text = ( $dev_time > 0 ) ? esc_html__( 'Push to Live', 'angie' ) : esc_html__( 'Push to Test', 'angie' );
  • angie/trunk/modules/code-snippets/classes/rest-api-controller.php

    r3464872 r3470221  
    3636                    ],
    3737                ],
     38                [
     39                    'methods' => \WP_REST_Server::CREATABLE,
     40                    'callback' => [ $this, 'create_snippet_post' ],
     41                    'permission_callback' => [ $this, 'check_permission' ],
     42                    'args' => [
     43                        'title' => [
     44                            'required'          => true,
     45                            'type'              => 'string',
     46                            'sanitize_callback' => 'sanitize_text_field',
     47                        ],
     48                        'type' => [
     49                            'required'          => false,
     50                            'type'              => 'string',
     51                            'sanitize_callback' => 'sanitize_text_field',
     52                        ],
     53                    ],
     54                ],
     55            ]
     56        );
     57
     58        register_rest_route(
     59            self::NAMESPACE,
     60            '/snippets/(?P<id>\d+)',
     61            [
     62                [
     63                    'methods' => \WP_REST_Server::DELETABLE,
     64                    'callback' => [ $this, 'delete_snippet' ],
     65                    'permission_callback' => [ $this, 'check_permission' ],
     66                    'args' => [
     67                        'id' => [
     68                            'required' => true,
     69                            'type' => 'integer',
     70                            'sanitize_callback' => 'absint',
     71                        ],
     72                    ],
     73                ],
     74            ]
     75        );
     76
     77        register_rest_route(
     78            self::NAMESPACE,
     79            '/snippets/(?P<id>\d+)/files',
     80            [
     81                [
     82                    'methods' => \WP_REST_Server::READABLE,
     83                    'callback' => [ $this, 'list_snippet_files' ],
     84                    'permission_callback' => [ $this, 'check_permission' ],
     85                    'args' => [
     86                        'id' => [
     87                            'required' => true,
     88                            'type' => 'integer',
     89                            'sanitize_callback' => 'absint',
     90                        ],
     91                    ],
     92                ],
     93                [
     94                    'methods' => 'PUT, PATCH',
     95                    'callback' => [ $this, 'update_snippet_files' ],
     96                    'permission_callback' => [ $this, 'check_permission' ],
     97                    'args' => [
     98                        'id' => [
     99                            'required' => true,
     100                            'type' => 'integer',
     101                            'sanitize_callback' => 'absint',
     102                        ],
     103                        'files' => [
     104                            'required' => true,
     105                            'type' => 'array',
     106                        ],
     107                        'type' => [
     108                            'required' => false,
     109                            'type' => 'string',
     110                            'sanitize_callback' => 'sanitize_text_field',
     111                        ],
     112                    ],
     113                ],
     114            ]
     115        );
     116
     117        register_rest_route(
     118            self::NAMESPACE,
     119            '/snippets/(?P<id>\d+)/files/(?P<filename>.+)',
     120            [
     121                [
     122                    'methods' => \WP_REST_Server::READABLE,
     123                    'callback' => [ $this, 'get_snippet_file' ],
     124                    'permission_callback' => [ $this, 'check_permission' ],
     125                    'args' => [
     126                        'id' => [
     127                            'required' => true,
     128                            'type' => 'integer',
     129                            'sanitize_callback' => 'absint',
     130                        ],
     131                        'filename' => [
     132                            'required' => true,
     133                            'type' => 'string',
     134                            'sanitize_callback' => 'sanitize_text_field',
     135                        ],
     136                    ],
     137                ],
     138            ]
     139        );
     140
     141        register_rest_route(
     142            self::NAMESPACE,
     143            '/snippets/(?P<id>\d+)/publish',
     144            [
     145                [
     146                    'methods' => \WP_REST_Server::CREATABLE,
     147                    'callback' => [ $this, 'publish_snippet' ],
     148                    'permission_callback' => [ $this, 'check_permission' ],
     149                    'args' => [
     150                        'id' => [
     151                            'required' => true,
     152                            'type' => 'integer',
     153                            'sanitize_callback' => 'absint',
     154                        ],
     155                    ],
     156                ],
    38157            ]
    39158        );
     
    109228                            'type' => 'string',
    110229                            'sanitize_callback' => 'sanitize_text_field',
     230                        ],
     231                        'types'     => [
     232                            'required' => false,
     233                            'type'     => 'array',
     234                            'items'    => [
     235                                'type'              => 'string',
     236                                'sanitize_callback' => 'sanitize_text_field',
     237                            ],
    111238                        ],
    112239                    ],
     
    251378
    252379    public function list_snippet_files( $request ) {
    253         $slug = $request->get_param( 'slug' );
    254         $post = Snippet_Repository::find_snippet_post_by_slug( $slug );
     380        $post = $this->resolve_snippet_post( $request );
    255381
    256382        if ( ! $post ) {
     
    271397
    272398    public function get_snippet_file( $request ) {
    273         $slug = $request->get_param( 'slug' );
     399        $post = $this->resolve_snippet_post( $request );
    274400        $filename = $request->get_param( 'filename' );
    275         $post = Snippet_Repository::find_snippet_post_by_slug( $slug );
    276401
    277402        if ( ! $post ) {
     
    300425
    301426        return rest_ensure_response( [
    302             'name' => $file['name'],
     427            'name'    => $file['name'],
    303428            'content' => $content,
    304             'size' => strlen( $content ),
     429            'size'    => strlen( $content ),
    305430        ] );
    306431    }
     
    310435        $files = $request->get_param( 'files' );
    311436        $overwrite = $request->get_param( 'overwrite' );
    312         $type = $request->get_param( 'type' );
     437        $types     = $request->get_param( 'types' );
     438        if ( ! is_array( $types ) || empty( $types ) ) {
     439            $single_type = $request->get_param( 'type' );
     440            if ( is_string( $single_type ) && '' !== trim( $single_type ) ) {
     441                $types = [ sanitize_text_field( $single_type ) ];
     442            }
     443        }
    313444
    314445        if ( ! is_array( $files ) || empty( $files ) ) {
     
    396527        }
    397528
    398         if ( ! empty( $type ) && ! Taxonomy_Manager::is_valid_type( $type ) ) {
    399             return new \WP_Error(
    400                 'invalid_type',
    401                 sprintf(
    402                     /* translators: %s: provided type value */
    403                     esc_html__( 'Invalid snippet type: %s. Valid types are: code-snippet, elementor-widget, gutenberg-block, popup, form, visual-app.', 'angie' ),
    404                     $type
    405                 ),
    406                 [ 'status' => 400 ]
    407             );
     529        if ( ! empty( $types ) ) {
     530            if ( ! is_array( $types ) ) {
     531                return new \WP_Error(
     532                    'invalid_types',
     533                    esc_html__( 'Types parameter must be an array.', 'angie' ),
     534                    [ 'status' => 400 ]
     535                );
     536            }
     537
     538            foreach ( $types as $snippet_type ) {
     539                if ( ! Taxonomy_Manager::is_valid_type( $snippet_type ) ) {
     540                    return new \WP_Error(
     541                        'invalid_type',
     542                        sprintf(
     543                            /* translators: %s: provided type value */
     544                            esc_html__( 'Invalid snippet type: %s. Valid types are: code-snippet, elementor-widget, gutenberg-block, popup, form, visual-app.', 'angie' ),
     545                            $snippet_type
     546                        ),
     547                        [ 'status' => 400 ]
     548                    );
     549                }
     550            }
    408551        }
    409552
     
    429572            }
    430573
    431             if ( ! empty( $type ) ) {
    432                 wp_set_object_terms( $post_id, $type, Taxonomy_Manager::TAXONOMY_NAME );
     574            if ( ! empty( $types ) ) {
     575                wp_set_object_terms( $post_id, $types, Taxonomy_Manager::TAXONOMY_NAME );
    433576            }
    434577
     
    466609        }
    467610
    468         if ( ! empty( $type ) ) {
    469             wp_set_object_terms( $post->ID, $type, Taxonomy_Manager::TAXONOMY_NAME );
     611        if ( ! empty( $types ) ) {
     612            wp_set_object_terms( $post->ID, $types, Taxonomy_Manager::TAXONOMY_NAME );
    470613        }
    471614
     
    485628
    486629    public function delete_snippet( $request ) {
    487         $slug = $request->get_param( 'slug' );
    488         $post = Snippet_Repository::find_snippet_post_by_slug( $slug );
     630        $post = $this->resolve_snippet_post( $request );
    489631
    490632        if ( ! $post ) {
     
    514656            'success' => true,
    515657            'message' => esc_html__( 'Snippet deleted successfully.', 'angie' ),
    516             'slug' => $slug,
     658            'post_id' => $post->ID,
    517659        ] );
    518660    }
     
    549691
    550692    public function publish_snippet( $request ) {
    551         $slug = $request->get_param( 'slug' );
    552         $post = Snippet_Repository::find_snippet_post_by_slug( $slug );
     693        $post = $this->resolve_snippet_post( $request );
    553694
    554695        if ( ! $post ) {
     
    576717            'success' => true,
    577718            'message' => esc_html__( 'Snippet published to production successfully.', 'angie' ),
    578             'slug' => $slug,
    579719            'post_id' => $post->ID,
    580             'files' => count( $files ),
     720            'files'   => count( $files ),
    581721        ] );
     722    }
     723
     724    public function create_snippet_post( $request ) {
     725        $title = $request->get_param( 'title' );
     726        $type = $request->get_param( 'type' );
     727
     728        if ( empty( $title ) ) {
     729            return new \WP_Error(
     730                'missing_title',
     731                esc_html__( 'Title is required.', 'angie' ),
     732                [ 'status' => 400 ]
     733            );
     734        }
     735
     736        if ( ! empty( $type ) && ! Taxonomy_Manager::is_valid_type( $type ) ) {
     737            return new \WP_Error(
     738                'invalid_type',
     739                sprintf(
     740                    /* translators: %s: provided type value */
     741                    esc_html__( 'Invalid snippet type: %s.', 'angie' ),
     742                    $type
     743                ),
     744                [ 'status' => 400 ]
     745            );
     746        }
     747
     748        $post_id = Snippet_Repository::create_snippet( $title );
     749
     750        if ( is_wp_error( $post_id ) ) {
     751            return new \WP_Error(
     752                'snippet_create_failed',
     753                esc_html__( 'Failed to create snippet.', 'angie' ),
     754                [ 'status' => 500 ]
     755            );
     756        }
     757
     758        if ( ! empty( $type ) ) {
     759            wp_set_object_terms( $post_id, $type, Taxonomy_Manager::TAXONOMY_NAME );
     760        }
     761
     762        return rest_ensure_response( [
     763            'id'    => $post_id,
     764            'title' => sanitize_text_field( $title ),
     765        ] );
     766    }
     767
     768    public function update_snippet_files( $request ) {
     769        $post = $this->resolve_snippet_post( $request );
     770
     771        if ( ! $post ) {
     772            return new \WP_Error(
     773                'snippet_not_found',
     774                esc_html__( 'Snippet not found.', 'angie' ),
     775                [ 'status' => 404 ]
     776            );
     777        }
     778
     779        $files = $request->get_param( 'files' );
     780        $type = $request->get_param( 'type' );
     781
     782        $existing_files = Snippet_Repository::get_snippet_files_by_post( $post );
     783        $merged_files = Snippet_Repository::merge_snippet_files( $existing_files, $files );
     784
     785        $sanitized_files = $this->sanitize_uploaded_files( $merged_files );
     786        if ( is_wp_error( $sanitized_files ) ) {
     787            return $sanitized_files;
     788        }
     789
     790        if ( ! Snippet_Repository::has_main_php_file( $sanitized_files ) ) {
     791            return new \WP_Error(
     792                'main_php_required',
     793                esc_html__( 'Snippet must have a main.php file.', 'angie' ),
     794                [ 'status' => 400 ]
     795            );
     796        }
     797
     798        if ( ! empty( $type ) && ! Taxonomy_Manager::is_valid_type( $type ) ) {
     799            return new \WP_Error(
     800                'invalid_type',
     801                sprintf(
     802                    /* translators: %s: provided type value */
     803                    esc_html__( 'Invalid snippet type: %s.', 'angie' ),
     804                    $type
     805                ),
     806                [ 'status' => 400 ]
     807            );
     808        }
     809
     810        if ( ! empty( $type ) ) {
     811            wp_set_object_terms( $post->ID, $type, Taxonomy_Manager::TAXONOMY_NAME );
     812        }
     813
     814        Snippet_Repository::update_snippet_files( $post->ID, $sanitized_files );
     815        File_System_Handler::write_snippet_files_to_disk( Dev_Mode_Manager::ENV_DEV, $post->ID, $sanitized_files );
     816        Cache_Manager::clear_published_snippet_cache();
     817
     818        return rest_ensure_response( [
     819            'success' => true,
     820            'message' => esc_html__( 'Snippet files updated successfully.', 'angie' ),
     821            'post_id' => $post->ID,
     822            'files'   => count( $sanitized_files ),
     823        ] );
     824    }
     825
     826    private function resolve_snippet_post( $request ) {
     827        $id = $request->get_param( 'id' );
     828
     829        if ( $id ) {
     830            return Snippet_Repository::find_snippet_post_by_id( $id );
     831        }
     832
     833        return Snippet_Repository::find_snippet_post_by_slug( $request->get_param( 'slug' ) );
     834    }
     835
     836    private function sanitize_uploaded_files( $files ) {
     837        if ( ! is_array( $files ) || empty( $files ) ) {
     838            return new \WP_Error(
     839                'invalid_files',
     840                esc_html__( 'Files parameter must be a non-empty array.', 'angie' ),
     841                [ 'status' => 400 ]
     842            );
     843        }
     844
     845        if ( count( $files ) > self::MAX_FILES_PER_REQUEST ) {
     846            return new \WP_Error(
     847                'too_many_files',
     848                sprintf(
     849                    /* translators: %d: maximum number of files */
     850                    esc_html__( 'Cannot process more than %d files per request.', 'angie' ),
     851                    self::MAX_FILES_PER_REQUEST
     852                ),
     853                [ 'status' => 400 ]
     854            );
     855        }
     856
     857        $sanitized_files = [];
     858
     859        foreach ( $files as $file ) {
     860            if ( ! isset( $file['name'] ) || ! isset( $file['content'] ) ) {
     861                return new \WP_Error(
     862                    'invalid_file_format',
     863                    esc_html__( 'Each file must have "name" and "content" properties.', 'angie' ),
     864                    [ 'status' => 400 ]
     865                );
     866            }
     867
     868            $name = File_Validator::sanitize_filename( $file['name'] );
     869            $content = $file['content'];
     870
     871            if ( empty( $name ) ) {
     872                return new \WP_Error(
     873                    'invalid_filename',
     874                    esc_html__( 'File name cannot be empty or contains invalid characters.', 'angie' ),
     875                    [ 'status' => 400 ]
     876                );
     877            }
     878
     879            $allowed_extensions = [ 'php', 'css', 'js' ];
     880            $extension = strtolower( pathinfo( $name, PATHINFO_EXTENSION ) );
     881            if ( ! in_array( $extension, $allowed_extensions, true ) ) {
     882                return new \WP_Error(
     883                    'invalid_file_type',
     884                    sprintf(
     885                        /* translators: %s: filename */
     886                        esc_html__( 'Invalid file type for: %s. Only PHP, CSS, and JS files are allowed.', 'angie' ),
     887                        $name
     888                    ),
     889                    [ 'status' => 400 ]
     890                );
     891            }
     892
     893            if ( strlen( $content ) > self::MAX_FILE_SIZE_BYTES ) {
     894                return new \WP_Error(
     895                    'file_too_large',
     896                    sprintf(
     897                        /* translators: %s: filename */
     898                        esc_html__( 'File too large: %s. Maximum size is 100KB.', 'angie' ),
     899                        $name
     900                    ),
     901                    [ 'status' => 400 ]
     902                );
     903            }
     904
     905            $sanitized_files[] = [
     906                'name'        => $name,
     907                'content_b64' => base64_encode( $content ),
     908            ];
     909        }
     910
     911        $validation_result = Snippet_Validator::validate_snippet_files( $sanitized_files );
     912
     913        if ( is_wp_error( $validation_result ) ) {
     914            return new \WP_Error(
     915                $validation_result->get_error_code(),
     916                $validation_result->get_error_message(),
     917                [ 'status' => 400 ]
     918            );
     919        }
     920
     921        return $sanitized_files;
    582922    }
    583923
     
    607947            $content = $file['content'];
    608948
    609             $check_result = File_Validator::check_forbidden_functions( $content );
    610             if ( ! $check_result['allowed'] ) {
    611                 return new \WP_Error(
    612                     'forbidden_function',
    613                     sprintf(
    614                         /* translators: %s: function name */
    615                         esc_html__( 'Forbidden function detected: %s', 'angie' ),
    616                         $check_result['function']
    617                     ),
    618                     [ 'status' => 400 ]
    619                 );
     949            $extension = strtolower( pathinfo( $name, PATHINFO_EXTENSION ) );
     950            if ( 'php' === $extension ) {
     951                $check_result = File_Validator::check_forbidden_functions( $content );
     952                if ( ! $check_result['allowed'] ) {
     953                    return new \WP_Error(
     954                        'forbidden_function',
     955                        sprintf(
     956                            /* translators: %s: function name */
     957                            esc_html__( 'Forbidden function detected: %s', 'angie' ),
     958                            $check_result['function']
     959                        ),
     960                        [ 'status' => 400 ]
     961                    );
     962                }
    620963            }
    621964
  • angie/trunk/modules/code-snippets/classes/snippet-repository.php

    r3464872 r3470221  
    99
    1010class Snippet_Repository {
     11
     12    public static function find_snippet_post_by_id( $id ) {
     13        $post = get_post( (int) $id );
     14
     15        if ( ! $post || Module::CPT_NAME !== $post->post_type ) {
     16            return null;
     17        }
     18
     19        return $post;
     20    }
    1121
    1222    public static function find_snippet_post_by_slug( $slug ) {
     
    4454    }
    4555
    46     public static function create_snippet( $slug ) {
    47         $post_id = wp_insert_post( [
    48             'post_title'  => $slug,
     56    public static function create_snippet( $title ) {
     57        return wp_insert_post( [
     58            'post_title'  => sanitize_text_field( $title ),
    4959            'post_type'   => Module::CPT_NAME,
    5060            'post_status' => 'publish',
    5161        ], true );
    52 
    53         return $post_id;
    5462    }
    5563
     
    6876    public static function update_snippet_files( $post_id, $files ) {
    6977        return update_post_meta( $post_id, '_angie_snippet_files', $files );
     78    }
     79
     80    public static function get_snippet_files_by_post( $post ) {
     81        $files = self::get_snippet_files( $post->ID );
     82        foreach ( $files as &$file ) {
     83            if ( ! isset( $file['content_b64'] ) || ! is_string( $file['content_b64'] ) ) {
     84                $file['content'] = '';
     85                continue;
     86            }
     87            $decoded = base64_decode( $file['content_b64'], true );
     88            $file['content'] = ( false === $decoded ) ? '' : $decoded;
     89        }
     90        return $files;
    7091    }
    7192
     
    106127        $files = self::get_snippet_files( $post->ID );
    107128        $terms = wp_get_object_terms( $post->ID, Taxonomy_Manager::TAXONOMY_NAME, [ 'fields' => 'slugs' ] );
     129        $types = is_wp_error( $terms ) ? [] : $terms;
    108130        $timestamps = Dev_Mode_Manager::get_snippet_environment_timestamps( $post->ID );
    109131        $is_elementor_widget = ! is_wp_error( $terms ) && in_array( 'elementor-widget', $terms, true );
    110132
    111133        $data = [
    112             'id'     => $post->ID,
    113             'slug'   => self::get_snippet_slug_from_post( $post ),
    114             'title'  => $post->post_title,
    115             'status' => $post->post_status,
    116             'files'  => self::build_file_list( $files ),
    117             'type'   => is_wp_error( $terms ) ? [] : $terms,
    118             'deploymentStatus' => $timestamps['status'],
     134            'id'                => $post->ID,
     135            'slug'              => self::get_snippet_slug_from_post( $post ),
     136            'title'             => $post->post_title,
     137            'status'            => $post->post_status,
     138            'types'             => $types,
     139            'files'             => self::build_file_list( $files ),
     140            'deploymentStatus'  => $timestamps['status'],
    119141        ];
    120142
  • angie/trunk/modules/code-snippets/classes/snippet-validator.php

    r3464872 r3470221  
    1010    public static function validate_snippet_files( $files ) {
    1111        foreach ( $files as $file ) {
     12            $extension = strtolower( pathinfo( $file['name'], PATHINFO_EXTENSION ) );
     13            if ( 'php' !== $extension ) {
     14                continue;
     15            }
     16
    1217            $content_clean = base64_decode( $file['content_b64'] );
    1318
  • angie/trunk/modules/sidebar/components/sidebar-admin-bar.php

    r3363303 r3470221  
    5757            $wp_admin_bar->add_node( [
    5858                'id'    => 'angie-sidebar-toggle',
    59                 'title' => 'Toggle Angie',
     59                'title' => '',
    6060                'href'  => '#',
    6161                'meta'  => [
    6262                    'class' => 'angie-sidebar-toggle-item',
    63                     'title' => 'Toggle Angie',
     63                    'title' => '',
    6464                ],
    6565            ] );
     
    6767            // No admin bar object = Elementor editor context, just create the basic element JavaScript needs
    6868            echo '<div id="wp-admin-bar-angie-sidebar-toggle" class="angie-sidebar-toggle-item" style="display: none;">
    69                 <a href="#" class="ab-item" title="Toggle Angie">Toggle Angie</a>
     69                <a href="#" class="ab-item" title=""></a>
    7070            </div>';
    7171        }
  • angie/trunk/readme.txt

    r3464872 r3470221  
    55Tested up to: 6.9
    66Requires PHP: 7.4
    7 Stable tag: 1.1.0
     7Stable tag: 1.1.1
    88License: GPLv3
    99License URI: https://www.gnu.org/licenses/gpl-3.0.html
    1010
     11== Description ==
     12
    1113Angie Code: Your expert WordPress developer, powered by AI. Build anything you can imagine without writing a single line of code.
    12 
    13 == Description ==
    1414
    1515**Disclaimer:** Angie is currently in Beta. While core functionality within WordPress and the Elementor Editor is stable and ready to explore, some actions and integrations with third-party tools are still evolving. We are continuously refining Angie's capabilities and expanding its capabilities. Please ensure you back up your site before use. During this Beta phase, enjoy free daily credits and help us shape the future of AI-driven web creation with your feedback.
     
    161161
    162162== Changelog ==
     163
     164= 1.1.1 - 2026-02-26 =
     165* Tweak: Improved Code Snippets user interface
     166* Tweak: Added support for multiple snippet types
     167* Tweak: Refactored Code Snippets feature for improved structure
     168* Tweak: User interface refinements
    163169
    164170= 1.1.0 - 2026-02-19 =
Note: See TracChangeset for help on using the changeset viewer.