Changeset 3464872
- Timestamp:
- 02/19/2026 08:54:15 AM (5 weeks ago)
- Location:
- angie/trunk
- Files:
-
- 2 added
- 2 deleted
- 20 edited
- angie.php (modified) (2 diffs)
- modules/angie-app/components/angie-app.php (modified) (4 diffs)
- modules/code-snippets/assets/css/dev-mode-border.css (deleted)
- modules/code-snippets/assets/css/list-table-toggle.css (modified) (2 diffs)
- modules/code-snippets/assets/js (deleted)
- modules/code-snippets/classes/assets-manager.php (modified) (1 diff)
- modules/code-snippets/classes/deployment-meta-box.php (modified) (3 diffs)
- modules/code-snippets/classes/dev-mode-admin-ui.php (modified) (4 diffs)
- modules/code-snippets/classes/dev-mode-manager.php (modified) (3 diffs)
- modules/code-snippets/classes/fatal-error-handler.php (modified) (4 diffs)
- modules/code-snippets/classes/file-system-handler.php (modified) (1 diff)
- modules/code-snippets/classes/files-meta-box.php (modified) (3 diffs)
- modules/code-snippets/classes/list-table-manager.php (modified) (9 diffs)
- modules/code-snippets/classes/rest-api-controller.php (modified) (14 diffs)
- modules/code-snippets/classes/snippet-repository.php (modified) (3 diffs)
- modules/code-snippets/classes/snippet-validator.php (modified) (4 diffs)
- modules/code-snippets/classes/widget-name-resolver.php (added)
- modules/code-snippets/module.php (modified) (3 diffs)
- modules/code-snippets/utils.php (added)
- modules/elementor-core/module.php (modified) (2 diffs)
- modules/sidebar/assets/sidebar.css (modified) (3 diffs)
- modules/sidebar/components/sidebar-html.php (modified) (1 diff)
- plugin.php (modified) (1 diff)
- readme.txt (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
angie/trunk/angie.php
r3443255 r3464872 4 4 * Description: Agentic AI for WordPress 5 5 * Plugin URI: https://elementor.com/pages/angie-early-access 6 * Version: 1. 0.46 * Version: 1.1.0 7 7 * Author: Elementor.com 8 8 * Author URI: https://elementor.com/ … … 21 21 } 22 22 23 define( 'ANGIE_VERSION', '1. 0.4' );23 define( 'ANGIE_VERSION', '1.1.0' ); 24 24 define( 'ANGIE_PATH', plugin_dir_path( __FILE__ ) ); 25 25 define( 'ANGIE_URL', plugins_url( '/', __FILE__ ) ); -
angie/trunk/modules/angie-app/components/angie-app.php
r3443255 r3464872 5 5 use Angie\Modules\ConsentManager\Module as ConsentManager; 6 6 use Angie\Modules\ConsentManager\Components\Consent_Page; 7 use Angie\Modules\CodeSnippets\Module as CodeSnippets;8 7 use Angie\Includes\Utils; 9 8 … … 156 155 wp_add_inline_script( 157 156 'angie-app', 158 'window.angieConfig = ' . wp_json_encode( [157 'window.angieConfig = ' . wp_json_encode( apply_filters( 'angie_config', [ 159 158 'plugins' => $plugins, 160 159 'installedPlugins' => $installed_plugins, … … 165 164 'untrusted__wpUserRole' => $wp_user_role, // Used only for analytics - Never use for auth decisions 166 165 'siteKey' => $this->get_site_key(), 167 'codeSnippetsActive' => CodeSnippets::is_active(),168 166 'isElementorOneConnected' => $this->is_elementor_one_connected(), 169 ] ) ,167 ] ) ), 170 168 'before' 171 169 ); … … 277 275 <?php $this->render_app_styles(); ?> 278 276 279 <script>280 (function() {281 // Listen for messages from iframe about authentication status282 window.addEventListener('message', function(event) {283 // Check if message is from iframe about user being already authenticated284 if (event.data && event.data.type === 'ANGIE_USER_ALREADY_AUTHENTICATED') {285 console.log('User already authenticated');286 // Open sidebar after a short delay287 setTimeout(() => {288 if (typeof window.toggleAngieSidebar === 'function') {289 window.toggleAngieSidebar(true);290 }291 }, 500);292 }293 });294 295 <?php if ( $is_in_oauth_flow ) : ?>296 const isStarting = <?php echo json_encode( $is_oauth_starting ); ?>;297 const isReturning = <?php echo json_encode( $is_oauth_return ); ?>;298 299 function ensureSidebarClosed() {300 if (typeof window.toggleAngieSidebar === 'function') {301 window.toggleAngieSidebar(false, true);302 }303 }304 305 function isOAuthComplete() {306 const urlParams = new URLSearchParams(window.location.search);307 return !urlParams.has('oauth_code') && !urlParams.has('oauth_state') && !urlParams.has('start-oauth');308 }309 310 function updateUIAfterAuth() {311 const appStart = document.getElementById('angie-app-start');312 if (appStart) {313 // Remove loading state if it exists314 const loadingState = appStart.querySelector('.angie-loading-state');315 if (loadingState) {316 loadingState.remove();317 }318 }319 }320 321 function openSidebarAfterAuth() {322 try {323 localStorage.setItem('angie_sidebar_state', 'open');324 } catch (e) {}325 if (typeof window.toggleAngieSidebar === 'function') {326 setTimeout(() => window.toggleAngieSidebar(true), 500);327 }328 }329 330 function monitorAuthCompletion() {331 let authenticationSuccessful = false;332 const checkInterval = setInterval(function() {333 const sidebar = document.getElementById('angie-sidebar-container');334 if (sidebar?.querySelector('iframe') && isOAuthComplete()) {335 updateUIAfterAuth();336 clearInterval(checkInterval);337 authenticationSuccessful = true;338 openSidebarAfterAuth();339 window.location.reload(); // Reload to update MCPs340 }341 }, 500);342 343 setTimeout(function() {344 clearInterval(checkInterval);345 if (!authenticationSuccessful) {346 console.log('OAuth authentication timed out');347 ensureSidebarClosed();348 }349 }, 30000);350 }351 352 ensureSidebarClosed();353 354 if (isStarting && !isReturning) {355 console.log('OAuth flow starting, waiting for redirect...');356 } else if (isReturning) {357 monitorAuthCompletion();358 }359 <?php endif; ?>360 })();361 </script>362 277 <?php else : ?> 363 278 <div class="wrap"> -
angie/trunk/modules/code-snippets/assets/css/list-table-toggle.css
r3443255 r3464872 1 .wrap .page-title-action { 2 display: none; 3 } 4 5 .angie-snippet-tooltip-trigger { 6 display: inline-flex; 7 align-items: center; 8 justify-content: center; 9 cursor: help; 10 } 11 12 .angie-snippet-info-icon { 13 display: inline-flex; 14 align-items: center; 15 justify-content: center; 16 width: 16px; 17 height: 16px; 18 border-radius: 50%; 19 background-color: #72777c; 20 color: #fff; 21 font-size: 11px; 22 font-weight: 600; 23 font-style: italic; 24 line-height: 1; 25 } 26 27 .angie-snippet-tooltip-trigger:hover .angie-snippet-info-icon { 28 background-color: #2271b1; 29 } 30 31 .angie-snippet-tooltip-popover { 32 position: fixed; 33 z-index: 100000; 34 display: none; 35 max-width: 260px; 36 padding: 10px 12px; 37 background-color: #1d2327; 38 color: #f0f0f1; 39 font-size: 13px; 40 line-height: 1.4; 41 border-radius: 4px; 42 box-shadow: 0 2px 12px rgba(0, 0, 0, 0.3); 43 pointer-events: none; 44 } 45 46 .angie-snippet-tooltip-popover::after { 47 content: ""; 48 position: absolute; 49 top: 100%; 50 left: 50%; 51 margin-left: -6px; 52 border: 6px solid transparent; 53 border-top-color: #1d2327; 54 } 55 1 56 .angie-snippet-toggle { 2 57 position: relative; … … 72 127 display: inline-flex; 73 128 align-items: center; 74 gap: 8px; 129 gap: 6px; 130 } 131 132 .angie-env-synced { 133 color: #00a32a; 134 } 135 136 .angie-env-not-synced { 137 color: #dba617; 138 } 139 140 .angie-env-not-deployed, 141 .angie-env-test-only { 142 color: #d63638; 75 143 } 76 144 -
angie/trunk/modules/code-snippets/classes/assets-manager.php
r3443255 r3464872 12 12 public static function init() { 13 13 add_action( 'admin_enqueue_scripts', [ __CLASS__, 'enqueue_code_editor_assets' ] ); 14 add_action( 'admin_enqueue_scripts', [ __CLASS__, 'enqueue_dev_mode_assets' ] );15 }16 17 public static function enqueue_dev_mode_assets() {18 $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;19 if ( ! $screen || 'edit-' . Module::CPT_NAME !== $screen->id ) {20 return;21 }22 23 if ( ! Module::current_user_can_manage_snippets() ) {24 return;25 }26 27 $script_url = plugins_url( 'assets/js/dev-mode.js', dirname( __FILE__ ) );28 wp_enqueue_script(29 'angie-dev-mode',30 $script_url,31 [ 'jquery' ],32 '1.0.0',33 true34 );35 36 wp_localize_script(37 'angie-dev-mode',38 'angieDevMode',39 [40 'restUrl' => esc_url( rest_url( 'angie/v1/dev-mode' ) ),41 'nonce' => wp_create_nonce( 'wp_rest' ),42 ]43 );44 14 } 45 15 -
angie/trunk/modules/code-snippets/classes/deployment-meta-box.php
r3443255 r3464872 14 14 add_action( 'save_post_' . Module::CPT_NAME, [ __CLASS__, 'save_deployment_meta' ], 5 ); 15 15 add_action( 'admin_post_angie_delete_environment', [ __CLASS__, 'handle_delete_environment' ] ); 16 add_action( 'admin_enqueue_scripts', [ __CLASS__, 'enqueue_assets' ] ); 17 add_action( 'post_submitbox_misc_actions', [ __CLASS__, 'render_publish_box_toggle' ] ); 18 add_filter( 'angie_config', [ __CLASS__, 'add_config' ] ); 19 } 20 21 public static function enqueue_assets( $hook ) { 22 if ( 'post.php' !== $hook && 'post-new.php' !== $hook ) { 23 return; 24 } 25 26 $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; 27 if ( ! $screen || Module::CPT_NAME !== $screen->post_type ) { 28 return; 29 } 30 31 wp_enqueue_style( 32 'angie-list-table-toggle', 33 plugins_url( 'assets/css/list-table-toggle.css', dirname( __FILE__ ) ), 34 [], 35 ANGIE_VERSION 36 ); 37 38 wp_add_inline_style( 39 'angie-list-table-toggle', 40 '#misc-publishing-actions .misc-pub-section:not(.angie-publish-toggle) { display: none !important; }' 41 ); 42 } 43 44 public static function add_config( $config ) { 45 $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; 46 if ( ! $screen || Module::CPT_NAME !== $screen->post_type ) { 47 return $config; 48 } 49 50 if ( 'post' !== $screen->base && 'post-new' !== $screen->base ) { 51 return $config; 52 } 53 54 $config['deploymentMetaBox'] = [ 55 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 56 'nonce' => wp_create_nonce( 'angie_toggle_snippet_status' ), 57 ]; 58 59 return $config; 60 } 61 62 public static function render_publish_box_toggle( $post ) { 63 if ( Module::CPT_NAME !== get_post_type( $post ) ) { 64 return; 65 } 66 67 $is_published = 'publish' === $post->post_status; 68 $checked = $is_published ? 'checked' : ''; 69 70 echo '<div class="misc-pub-section angie-publish-toggle" style="display: flex !important; align-items: center; gap: 8px; padding: 6px 10px;">'; 71 echo '<strong>' . esc_html__( 'Status:', 'angie' ) . '</strong>'; 72 printf( 73 '<label class="angie-snippet-toggle"> 74 <input type="checkbox" class="angie-snippet-toggle-input" data-post-id="%d" %s /> 75 <span class="angie-snippet-toggle-slider"></span> 76 </label>', 77 absint( $post->ID ), 78 esc_attr( $checked ) 79 ); 80 echo '<span class="angie-metabox-status-label">' . ( $is_published ? esc_html__( 'Active', 'angie' ) : esc_html__( 'Inactive', 'angie' ) ) . '</span>'; 81 echo '</div>'; 16 82 } 17 83 … … 33 99 $dev_time = $timestamps['dev']; 34 100 $prod_time = $timestamps['prod']; 35 $sync_status = $timestamps['status'];36 101 $delete_url_base = admin_url( 'admin-post.php' ); 37 102 … … 78 143 79 144 echo '<p><strong>' . esc_html__( 'Sync Status:', 'angie' ) . '</strong><br>'; 80 if ( Dev_Mode_Manager::SYNC_STATUS_NOT_DEPLOYED === $sync_status ) { 81 echo '<span style="color: #999;">' . esc_html__( 'Not Deployed', 'angie' ) . '</span>'; 82 } elseif ( Dev_Mode_Manager::SYNC_STATUS_CHANGES_PENDING === $sync_status ) { 83 echo '<span style="color: #d63638;">' . esc_html__( 'Test & Live not synced', 'angie' ) . '</span>'; 84 } elseif ( Dev_Mode_Manager::SYNC_STATUS_TEST_ONLY === $sync_status ) { 85 echo '<span style="color: #d63638;">' . esc_html__( 'Test Environment only', 'angie' ) . '</span>'; 86 } else { 87 echo '<span style="color: #00a32a;">' . esc_html__( 'Live & Synced', 'angie' ) . '</span>'; 88 } 145 List_Table_Manager::render_sync_status( $post->ID ); 89 146 echo '</p>'; 90 147 91 148 echo '<p>'; 92 $button_text = ( $dev_time > 0 ) ? esc_html__( 'Push to Production', 'angie' ) : esc_html__( 'Publish to Dev', 'angie' ); 93 submit_button( $button_text, 'primary', 'angie_push_to_production', false ); 149 $deploy_action = ( $dev_time > 0 ) ? 'push-to-production' : 'publish-to-dev'; 150 $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 ] ); 94 152 echo '</p>'; 95 153 echo '</div>'; -
angie/trunk/modules/code-snippets/classes/dev-mode-admin-ui.php
r3443255 r3464872 12 12 public static function init() { 13 13 add_action( 'admin_notices', [ __CLASS__, 'render_dev_mode_notice' ] ); 14 add_action( 'admin_footer', [ __CLASS__, 'render_dev_mode_exit_button' ] ); 15 16 add_action( 'elementor/editor/after_enqueue_scripts', [ __CLASS__, 'enqueue_elementor_dev_mode_assets' ] ); 17 add_action( 'elementor/editor/footer', [ __CLASS__, 'render_elementor_dev_mode_exit_button' ] ); 14 add_filter( 'angie_config', [ __CLASS__, 'add_dev_mode_state_to_angie_config' ] ); 18 15 } 19 16 … … 33 30 echo '<div class="notice notice-warning is-dismissible">'; 34 31 echo '<p>'; 35 echo '<strong>' . esc_html__( ' DevMode is Active', 'angie' ) . '</strong> - ' . esc_html__( 'Snippets are loading from the development environment.', 'angie' ) . ' ';36 echo '<button type="button" class="button button-secondary" id="angie-disable-dev-mode">' . esc_html__( 'Disable DevMode', 'angie' ) . '</button>';32 echo '<strong>' . esc_html__( 'Test Mode is Active', 'angie' ) . '</strong> - ' . esc_html__( 'Snippets are loading from the development environment.', 'angie' ) . ' '; 33 echo '<button type="button" class="button button-secondary" id="angie-disable-dev-mode">' . esc_html__( 'Disable Test Mode', 'angie' ) . '</button>'; 37 34 echo '</p>'; 38 35 echo '</div>'; … … 41 38 echo '<p>'; 42 39 echo esc_html__( 'Snippets are loading from production.', 'angie' ) . ' '; 43 echo '<button type="button" class="button button-primary" id="angie-enable-dev-mode">' . esc_html__( 'Enable DevMode', 'angie' ) . '</button>';40 echo '<button type="button" class="button button-primary" id="angie-enable-dev-mode">' . esc_html__( 'Enable Test Mode', 'angie' ) . '</button>'; 44 41 echo '</p>'; 45 42 echo '</div>'; … … 47 44 } 48 45 49 public static function render_dev_mode_exit_button() { 50 if ( ! is_admin() ) { 51 return; 52 } 53 54 if ( ! Module::current_user_can_manage_snippets() ) { 55 return; 56 } 57 58 if ( ! Dev_Mode_Manager::is_dev_mode_enabled() ) { 59 return; 60 } 61 62 self::enqueue_dev_mode_border_assets(); 63 self::render_angie_dev_mode_exit_button_html(); 64 } 65 66 private static function enqueue_dev_mode_border_assets() { 67 wp_enqueue_style( 68 'angie-dev-mode-border', 69 plugins_url( 'assets/css/dev-mode-border.css', dirname( __FILE__ ) ), 70 [], 71 ANGIE_VERSION 72 ); 73 74 wp_enqueue_script( 75 'angie-dev-mode-exit-button', 76 plugins_url( 'assets/js/dev-mode-exit-button.js', dirname( __FILE__ ) ), 77 [], 78 ANGIE_VERSION, 79 true 80 ); 81 82 wp_localize_script( 83 'angie-dev-mode-exit-button', 84 'angieDevModeExit', 85 [ 86 'restUrl' => esc_url( rest_url( 'angie/v1/dev-mode' ) ), 87 'nonce' => wp_create_nonce( 'wp_rest' ), 88 'buttonText' => esc_html__( 'Test mode', 'angie' ), 89 'exitingText' => esc_html__( 'Exiting...', 'angie' ), 90 'errorText' => esc_html__( 'An error occurred.', 'angie' ), 91 'requestFailedText' => esc_html__( 'Request failed.', 'angie' ), 92 ] 93 ); 94 } 95 96 private static function render_angie_dev_mode_exit_button_html() { 97 ?> 98 <div id="angie-dev-mode-border-overlay"></div> 99 <div id="angie-dev-mode-button-wrapper"> 100 <div id="angie-dev-mode-tooltip" class="angie-tooltip-hidden"> 101 <p class="angie-tooltip-title"><?php echo esc_html__( 'Test mode lets you try things safely.', 'angie' ); ?></p> 102 <p><?php echo esc_html__( 'Changes you make here are saved as drafts and won\'t affect your live site until you publish them.', 'angie' ); ?></p> 103 <p><?php echo esc_html__( 'You can create, edit, and preview widgets freely, then publish when you\'re ready.', 'angie' ); ?></p> 104 <div class="angie-tooltip-arrow"></div> 105 </div> 106 <button type="button" id="angie-dev-mode-exit-button"> 107 <span class="angie-exit-icon">✕</span> 108 <span><?php echo esc_html__( 'Test mode', 'angie' ); ?></span> 109 <span class="angie-info-icon">i</span> 110 </button> 111 </div> 112 <?php 113 } 114 115 public static function enqueue_elementor_dev_mode_assets() { 116 if ( ! Module::current_user_can_manage_snippets() ) { 117 return; 118 } 119 120 if ( ! Dev_Mode_Manager::is_dev_mode_enabled() ) { 121 return; 122 } 123 124 self::enqueue_dev_mode_border_assets(); 125 } 126 127 public static function render_elementor_dev_mode_exit_button() { 128 if ( ! Module::current_user_can_manage_snippets() ) { 129 return; 130 } 131 132 if ( ! Dev_Mode_Manager::is_dev_mode_enabled() ) { 133 return; 134 } 135 136 self::render_angie_dev_mode_exit_button_html(); 46 public static function add_dev_mode_state_to_angie_config( $angie_config ) { 47 $angie_config['isDevModeEnabled'] = Dev_Mode_Manager::is_dev_mode_enabled(); 48 return $angie_config; 137 49 } 138 50 } -
angie/trunk/modules/code-snippets/classes/dev-mode-manager.php
r3443255 r3464872 42 42 } 43 43 44 public static function create_dev_mode_session() { 45 $user_id = get_current_user_id(); 46 if ( ! $user_id ) { 47 return false; 48 } 49 50 $ip_address = self::get_client_ip(); 51 if ( empty( $ip_address ) ) { 52 return false; 53 } 54 44 private static function set_dev_mode_cookie( $user_id, $ip_address ) { 55 45 $token = self::generate_session_token( $user_id, $ip_address ); 56 $expiry = time() + HOUR_IN_SECONDS;46 $expiry = time() + YEAR_IN_SECONDS; 57 47 $ip_hash = hash( 'sha256', $ip_address ); 58 48 $cookie_value = $token . '|' . $expiry . '|' . $user_id . '|' . $ip_hash; … … 67 57 true 68 58 ); 59 60 return $expiry; 61 } 62 63 private static function extend_dev_mode_session( $user_id, $ip_address ) { 64 if ( headers_sent() ) { 65 return; 66 } 67 68 self::set_dev_mode_cookie( $user_id, $ip_address ); 69 } 70 71 public static function create_dev_mode_session() { 72 $user_id = get_current_user_id(); 73 if ( ! $user_id ) { 74 return false; 75 } 76 77 $ip_address = self::get_client_ip(); 78 if ( empty( $ip_address ) ) { 79 return false; 80 } 81 82 $expiry = self::set_dev_mode_cookie( $user_id, $ip_address ); 69 83 70 84 return [ … … 120 134 } 121 135 136 self::extend_dev_mode_session( $user_id, $current_ip ); 137 122 138 return true; 123 139 } -
angie/trunk/modules/code-snippets/classes/fatal-error-handler.php
r3443255 r3464872 8 8 class Fatal_Error_Handler { 9 9 10 const FATAL_ERROR_TYPES = [ E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR ];10 private const ERROR_CODE_ADDITIONAL_MESSAGE = 'angie_additional_message'; 11 11 12 12 public static function init() { … … 15 15 } 16 16 17 add_filter( 'wp_die_handler', function() { 18 return [ __CLASS__, 'handle_wp_die' ]; 19 }, 9999 ); 20 } 21 22 private static function get_additional_error_message( $message ): string { 23 24 $error_message = self::format_error_message( $message ); 25 $exit_url = self::get_exit_test_mode_url(); 26 $snippet_id = self::extract_snippet_id( $error_message ); 27 $prompt = $snippet_id 28 ? 'Please fix the error in snippet ID ' . $snippet_id . ' - Use the snippet slug: ' . $error_message 29 : 'Please fix this error: ' . $error_message; 30 $fix_with_angie_url = $exit_url . '#angie-prompt=' . rawurlencode( $prompt ); 31 17 32 ob_start(); 18 register_shutdown_function( [ __CLASS__, 'handle_shutdown' ] ); 33 ?> 34 <style> 35 body#error-page { 36 border: none !important; 37 max-width: none !important; 38 margin: 0 !important; 39 padding: 0 !important; 40 font-size: 0 !important; 41 height: 100vh !important; 42 display: flex !important; 43 justify-content: center !important; 44 align-items: center !important; 45 } 46 body#error-page .wp-die-message ul { 47 margin: 0 !important; 48 padding: 0 !important; 49 } 50 body#error-page .wp-die-message ul > li { 51 display: none !important; 52 } 53 body#error-page .wp-die-message ul > li:first-child { 54 display: block !important; 55 } 56 57 .angie-fatal-notice { 58 width: 550px; 59 max-width: 90vw; 60 padding: 24px; 61 border-radius: 16px; 62 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; 63 color: #1d2327; 64 border: 1px solid #e0e0e0; 65 } 66 .angie-fatal-notice h2 { 67 font-size: 20px; 68 margin: 0 0 12px 0; 69 } 70 #error-page .angie-fatal-notice p { 71 font-size: 16px; 72 color: #50575e; 73 margin: 0 0 4px 0; 74 } 75 .angie-fatal-notice .angie-help-link { 76 display: inline-block; 77 font-size: 16px; 78 color: #2271b1; 79 margin-top: 12px; 80 margin-bottom: 24px; 81 } 82 .angie-fatal-notice .angie-help-link:hover { 83 color: #135e96; 84 } 85 .angie-fatal-buttons { 86 display: flex; 87 justify-content: flex-end; 88 align-items: center; 89 gap: 16px; 90 } 91 .angie-btn { 92 display: inline-block; 93 padding: 8px 16px; 94 font-size: 14px; 95 font-weight: 500; 96 text-decoration: none; 97 border-radius: 10px; 98 cursor: pointer; 99 text-align: center; 100 background: transparent; 101 color: #1d2327; 102 border: none; 103 transition: all 0.2s ease; 104 } 105 .angie-btn:hover { 106 background: #f0f0f1; 107 } 108 .angie-fix-with-angie-btn { 109 background: #1d2327; 110 color: #fff; 111 border: 1px solid #1d2327; 112 } 113 .angie-fix-with-angie-btn:hover { 114 background: #3a3f44; 115 color: #fff; 116 border-color: #3a3f44; 117 } 118 </style> 119 <div class="angie-fatal-notice"> 120 <h2><?php echo esc_html__( 'Something went wrong while working on your site', 'angie' ); ?></h2> 121 <p><?php echo esc_html__( 'A change made by Angie caused an error in this preview.', 'angie' ); ?></p> 122 <p><?php echo esc_html__( "Your live site is not affected - you're viewing changes in Test Mode.", 'angie' ); ?></p> 123 <a href="https://wordpress.org/documentation/article/faq-troubleshooting/" target="_blank" rel="noopener noreferrer" class="angie-help-link"> 124 <?php echo esc_html__( 'Learn how to troubleshoot WordPress errors', 'angie' ); ?> 125 </a> 126 <div class="angie-fatal-buttons"> 127 <a href="<?php echo esc_url( $exit_url ); ?>" class="angie-btn"> 128 <?php echo esc_html__( 'Exit Test Mode', 'angie' ); ?> 129 </a> 130 <a href="<?php echo esc_url( $fix_with_angie_url ); ?>" class="angie-btn angie-fix-with-angie-btn"> 131 <?php echo esc_html__( 'Fix with Angie', 'angie' ); ?> 132 </a> 133 </div> 134 </div> 135 <?php 136 return ob_get_clean(); 137 } 138 139 public static function handle_wp_die( $message, $title = '', $args = [] ): void { 140 $additional_message = static::get_additional_error_message( $message ); 141 142 $message = is_wp_error( $message ) 143 ? self::merge_wp_errors( $message, $additional_message ) 144 : $additional_message . $message; 145 146 _default_wp_die_handler( $message, $title, $args ); 147 } 148 149 private static function merge_wp_errors( \WP_Error $original, string $additional_message ): \WP_Error { 150 $new_error = new \WP_Error( self::ERROR_CODE_ADDITIONAL_MESSAGE, $additional_message ); 151 152 foreach ( $original->get_error_codes() as $code ) { 153 foreach ( $original->get_error_messages( $code ) as $error_message ) { 154 $new_error->add( $code, $error_message ); 155 } 156 157 $error_data = $original->get_error_data( $code ); 158 159 if ( $error_data ) { 160 $new_error->add_data( $error_data, $code ); 161 } 162 } 163 164 return $new_error; 19 165 } 20 166 … … 37 183 38 184 return Dev_Mode_Manager::is_dev_mode_enabled(); 39 }40 41 public static function handle_shutdown(): void {42 $error = error_get_last();43 44 if ( ! $error || ! in_array( $error['type'], self::FATAL_ERROR_TYPES, true ) ) {45 return;46 }47 48 if ( ! Dev_Mode_Manager::is_dev_mode_enabled() ) {49 return;50 }51 52 while ( ob_get_level() > 0 ) {53 ob_end_clean();54 }55 56 self::render_fallback_notice();57 }58 59 private static function render_fallback_notice(): void {60 $exit_url = self::get_exit_test_mode_url();61 ?>62 <!DOCTYPE html>63 <html>64 <head>65 <meta charset="utf-8">66 <meta name="viewport" content="width=device-width, initial-scale=1">67 <title><?php echo esc_html__( 'Error in Test Mode', 'angie' ); ?></title>68 <style>69 .angie-fatal-notice {70 background: #fff;71 border-left: 4px solid #ED01EE;72 box-shadow: 0 1px 3px rgba(0,0,0,0.1);73 max-width: 600px;74 padding: 14px 24px;75 border-radius: 4px;76 display: block;77 }78 .angie-fatal-notice h2 {79 display: block;80 font-size: 18px;81 font-weight: 600;82 color: #1d2327;83 margin-bottom: 12px;84 }85 .angie-fatal-notice p {86 display: block;87 font-size: 14px;88 line-height: 1.6;89 color: #50575e;90 margin-bottom: 20px;91 }92 .angie-fatal-buttons {93 display: block;94 }95 .angie-btn {96 display: inline-block;97 padding: 10px 20px;98 font-size: 14px;99 font-weight: 500;100 text-decoration: none;101 border-radius: 4px;102 cursor: pointer;103 text-align: center;104 background: #f0f0f1;105 color: #1d2327;106 border: 1px solid #c3c4c7;107 }108 </style>109 </head>110 <body>111 <div class="angie-fatal-notice">112 <h2><?php echo esc_html__( 'Something went wrong', 'angie' ); ?></h2>113 <p><?php echo esc_html__( "Don't worry, the website's visitors won't see this because you are previewing unpublished changes in test mode.", 'angie' ); ?></p>114 <div class="angie-fatal-buttons">115 <a href="<?php echo esc_url( $exit_url ); ?>" class="angie-btn">116 <?php echo esc_html__( 'Exit test mode', 'angie' ); ?>117 </a>118 </div>119 </div>120 </body>121 </html>122 <?php123 exit;124 185 } 125 186 … … 128 189 return add_query_arg( 'angie-exit-test-mode', '1', $current_url ); 129 190 } 191 192 private static function extract_snippet_id( string $error_message ): ?string { 193 if ( preg_match( '/snippet-(\d+)/', $error_message, $matches ) ) { 194 return $matches[1]; 195 } 196 return null; 197 } 198 199 private static function format_error_message( $message ): string { 200 201 if ( is_wp_error( $message ) ) { 202 if( isset( $message->error_data['internal_server_error']['error']['message'] ) ) { 203 return $message->error_data['internal_server_error']['error']['message']; 204 } 205 return $message->get_error_message(); 206 } 207 208 if ( is_string( $message ) ) { 209 return $message; 210 } 211 212 return wp_json_encode( $message ) ?? ''; 213 } 130 214 } -
angie/trunk/modules/code-snippets/classes/file-system-handler.php
r3443255 r3464872 64 64 $file_path = $base_dir . '/' . $filename; 65 65 $wp_filesystem->put_contents( $file_path, $content, FS_CHMOD_FILE ); 66 67 self::maybe_invalidate_opcache( $file_path ); 66 68 } 69 } 70 71 private static function maybe_invalidate_opcache( $filepath ) { 72 if ( ! function_exists( 'wp_opcache_invalidate' ) ) { 73 return; 74 } 75 76 wp_opcache_invalidate( $filepath, true ); 67 77 } 68 78 -
angie/trunk/modules/code-snippets/classes/files-meta-box.php
r3443255 r3464872 34 34 35 35 wp_nonce_field( 'angie_snippet_files_save', 'angie_snippet_files_nonce' ); 36 37 self::enqueue_files_meta_box_script();38 36 39 37 echo '<div id="angie-snippet-files">'; … … 78 76 } 79 77 80 private static function enqueue_files_meta_box_script() {81 $script_url = plugins_url( 'assets/js/files-meta-box.js', dirname( __FILE__ ) );82 wp_enqueue_script(83 'angie-files-meta-box',84 $script_url,85 [ 'code-editor' ],86 '1.0.0',87 true88 );89 90 wp_localize_script(91 'angie-files-meta-box',92 'angieFilesMetaBox',93 [94 'placeholders' => [95 'name' => esc_attr__( 'e.g. main.php', 'angie' ),96 'content' => esc_attr__( 'File content…', 'angie' ),97 ],98 'labels' => [99 'remove' => esc_html__( 'Remove', 'angie' ),100 ],101 ]102 );103 }104 105 78 public static function save_files_meta( $post_id ) { 106 79 if ( ! isset( $_POST['angie_snippet_files_nonce'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing … … 131 104 132 105 foreach ( $raw_files as $file ) { 133 $name = isset( $file['name'] ) ? sanitize_text_field( wp_unslash( $file['name'] )) : '';134 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Unslashing to remove WP magic quotes; base64 encoded immediately.135 $content_clean = isset( $file['content'] ) ? wp_unslash( $file['content'] ): '';106 $name = isset( $file['name'] ) ? sanitize_text_field( $file['name'] ) : ''; 107 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Already unslashed via $raw_files; base64 encoded immediately. 108 $content_clean = isset( $file['content'] ) ? $file['content'] : ''; 136 109 137 110 if ( '' === trim( $name ) && '' === trim( $content_clean ) ) { -
angie/trunk/modules/code-snippets/classes/list-table-manager.php
r3443255 r3464872 14 14 add_action( 'manage_' . Module::CPT_NAME . '_posts_custom_column', [ __CLASS__, 'render_custom_columns' ], 10, 2 ); 15 15 add_action( 'admin_enqueue_scripts', [ __CLASS__, 'enqueue_assets' ] ); 16 add_filter( 'angie_config', [ __CLASS__, 'add_config' ] ); 16 17 add_action( 'wp_ajax_angie_toggle_snippet_status', [ __CLASS__, 'ajax_toggle_status' ] ); 17 18 add_action( 'wp_ajax_angie_push_to_production', [ __CLASS__, 'ajax_push_to_production' ] ); 19 add_filter( 'post_row_actions', [ __CLASS__, 'remove_quick_edit' ], 10, 2 ); 20 } 21 22 public static function remove_quick_edit( $actions, $post ) { 23 if ( Module::CPT_NAME === $post->post_type ) { 24 unset( $actions['inline hide-if-no-js'] ); 25 } 26 27 return $actions; 18 28 } 19 29 … … 26 36 'files' => esc_html__( 'Files', 'angie' ), 27 37 'actions' => esc_html__( 'Actions', 'angie' ), 28 ' date' => $columns['date'],38 'last_modified' => esc_html__( 'Last Modified', 'angie' ), 29 39 ]; 30 40 … … 53 63 } elseif ( 'actions' === $column ) { 54 64 self::render_actions_column( $post_id ); 65 } elseif ( 'last_modified' === $column ) { 66 self::render_last_modified_column( $post_id ); 55 67 } 56 68 } … … 75 87 } 76 88 89 private static function render_last_modified_column( $post_id ) { 90 $post = get_post( $post_id ); 91 if ( ! $post ) { 92 return; 93 } 94 95 if ( '0000-00-00 00:00:00' === $post->post_modified ) { 96 $t_time = esc_html__( 'Unpublished', 'angie' ); 97 } else { 98 $t_time = sprintf( 99 /* translators: 1: Post date, 2: Post time. */ 100 esc_html__( '%1$s at %2$s', 'angie' ), 101 /* translators: Post date format. See https://www.php.net/manual/datetime.format.php */ 102 get_the_modified_time( esc_html__( 'Y/m/d', 'angie' ), $post ), 103 /* translators: Post time format. See https://www.php.net/manual/datetime.format.php */ 104 get_the_modified_time( esc_html__( 'g:i a', 'angie' ), $post ) 105 ); 106 } 107 108 echo esc_html( $t_time ); 109 } 110 77 111 private static function render_files_column( $post_id ) { 78 112 $files = get_post_meta( $post_id, '_angie_snippet_files', true ); … … 85 119 86 120 private static function render_environment_column( $post_id ) { 121 self::render_sync_status( $post_id ); 122 } 123 124 public static function render_sync_status( $post_id ) { 87 125 $timestamps = Dev_Mode_Manager::get_snippet_environment_timestamps( $post_id ); 88 126 $sync_status = $timestamps['status']; 89 127 90 128 if ( Dev_Mode_Manager::SYNC_STATUS_NOT_DEPLOYED === $sync_status ) { 91 echo '<span class="angie-env-status angie-env-not-deployed">'; 92 echo '<span class="angie-env-badge angie-env-badge-gray"></span>'; 93 echo esc_html__( 'Not Deployed', 'angie' ); 94 echo '</span>'; 129 $tooltip = esc_attr__( 'Snippet has not been deployed to test or live yet', 'angie' ); 130 echo '<span class="angie-env-status angie-env-not-deployed">'; 131 echo '<span class="angie-env-badge angie-env-badge-test"></span>'; 132 echo esc_html__( 'Not Deployed', 'angie' ); 133 self::render_tooltip_icon( $tooltip ); 134 echo '</span>'; 95 135 } elseif ( Dev_Mode_Manager::SYNC_STATUS_CHANGES_PENDING === $sync_status ) { 96 echo '<span class="angie-env-status angie-env-not-synced">'; 97 echo '<span class="angie-env-badge angie-env-badge-warning"></span>'; 98 echo esc_html__( 'Test & Live not synced', 'angie' ); 99 echo '</span>'; 136 $tooltip = esc_attr__( 'Snippet is live but has unpublished changes in test', 'angie' ); 137 echo '<span class="angie-env-status angie-env-not-synced">'; 138 echo '<span class="angie-env-badge angie-env-badge-warning"></span>'; 139 echo esc_html__( 'Live (out of sync)', 'angie' ); 140 self::render_tooltip_icon( $tooltip ); 141 echo '</span>'; 100 142 } elseif ( Dev_Mode_Manager::SYNC_STATUS_TEST_ONLY === $sync_status ) { 101 echo '<span class="angie-env-status angie-env-test-only">'; 102 echo '<span class="angie-env-badge angie-env-badge-test"></span>'; 103 echo esc_html__( 'Test Environment only', 'angie' ); 104 echo '</span>'; 143 $tooltip = esc_attr__( 'Snippet is only deployed to sandbox/test, not to live', 'angie' ); 144 echo '<span class="angie-env-status angie-env-test-only">'; 145 echo '<span class="angie-env-badge angie-env-badge-test"></span>'; 146 echo esc_html__( 'Sandbox only', 'angie' ); 147 self::render_tooltip_icon( $tooltip ); 148 echo '</span>'; 105 149 } else { 106 echo '<span class="angie-env-status angie-env-synced">'; 107 echo '<span class="angie-env-badge angie-env-badge-success"></span>'; 108 echo esc_html__( 'Live & Synced', 'angie' ); 109 echo '</span>'; 110 } 150 $tooltip = esc_attr__( 'Snippet is deployed to live and matches the latest version', 'angie' ); 151 echo '<span class="angie-env-status angie-env-synced">'; 152 echo '<span class="angie-env-badge angie-env-badge-success"></span>'; 153 echo esc_html__( 'Live (synced)', 'angie' ); 154 self::render_tooltip_icon( $tooltip ); 155 echo '</span>'; 156 } 157 } 158 159 private static function render_tooltip_icon( $tooltip_text ) { 160 printf( 161 '<span class="angie-snippet-tooltip-trigger" data-tooltip="%s" aria-label="%s">', 162 esc_attr( $tooltip_text ), 163 esc_attr( $tooltip_text ) 164 ); 165 echo '<span class="angie-snippet-info-icon">i</span>'; 166 echo '</span>'; 111 167 } 112 168 … … 124 180 } 125 181 182 $post = get_post( $post_id ); 183 $snippet_slug = $post ? $post->post_name : ''; 184 126 185 $disabled_attr = $is_disabled ? ' disabled' : ''; 127 $button_text = ( $dev_time > 0 ) ? esc_html__( 'Publish to Live', 'angie' ) : esc_html__( 'Publish to Dev', 'angie' ); 186 $deploy_action = ( $dev_time > 0 ) ? 'push-to-production' : 'publish-to-dev'; 187 $button_text = ( $dev_time > 0 ) ? esc_html__( 'Push to Live', 'angie' ) : esc_html__( 'Push to Test', 'angie' ); 128 188 129 189 printf( 130 '<button type="button" class="button angie-push-to-production" data-post-id="%d" %s>%s</button>',190 '<button type="button" class="button angie-push-to-production" data-post-id="%d" data-snippet-slug="%s" data-action="%s"%s>%s</button>', 131 191 absint( $post_id ), 192 esc_attr( $snippet_slug ), 193 esc_attr( $deploy_action ), 132 194 esc_attr( $disabled_attr ), 133 195 esc_html( $button_text ) … … 144 206 return; 145 207 } 146 147 $script_url = plugins_url( 'assets/js/list-table-toggle.js', dirname( __FILE__ ) );148 wp_enqueue_script(149 'angie-list-table-toggle',150 $script_url,151 [],152 ANGIE_VERSION,153 true154 );155 156 wp_localize_script(157 'angie-list-table-toggle',158 'angieListTableToggle', [159 'ajaxUrl' => admin_url( 'admin-ajax.php' ),160 'nonce' => wp_create_nonce( 'angie_toggle_snippet_status' ),161 'pushToProductionNonce' => wp_create_nonce( 'angie_push_to_production' ),162 'i18n' => [163 'publishToDevButton' => esc_html__( 'Publish to Dev', 'angie' ),164 'confirmPublishToDev' => esc_html__( 'Are you sure you want to publish this snippet to dev?', 'angie' ),165 'confirmPushToProduction' => esc_html__( 'Are you sure you want to push this snippet to production?', 'angie' ),166 'pushing' => esc_html__( 'Pushing...', 'angie' ),167 'failedToUpdateStatus' => esc_html__( 'Failed to update status', 'angie' ),168 'errorOccurred' => esc_html__( 'An error occurred. Please try again.', 'angie' ),169 'successPushedToProduction' => esc_html__( 'Successfully pushed to production', 'angie' ),170 'failedToPushToProduction' => esc_html__( 'Failed to push to production', 'angie' ),171 ],172 ]173 );174 208 175 209 $style_url = plugins_url( 'assets/css/list-table-toggle.css', dirname( __FILE__ ) ); … … 182 216 } 183 217 218 public static function add_config( $config ) { 219 $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; 220 if ( ! $screen || 'edit-' . Module::CPT_NAME !== $screen->id ) { 221 return $config; 222 } 223 224 if ( ! Module::current_user_can_manage_snippets() ) { 225 return $config; 226 } 227 228 $config['listTable'] = [ 229 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 230 'nonce' => wp_create_nonce( 'angie_toggle_snippet_status' ), 231 'pushToProductionNonce' => wp_create_nonce( 'angie_push_to_production' ), 232 ]; 233 234 return $config; 235 } 236 184 237 public static function ajax_toggle_status() { 185 238 check_ajax_referer( 'angie_toggle_snippet_status', 'nonce' ); … … 246 299 } else { 247 300 $success = Dev_Mode_Manager::push_snippet_to_dev( $post_id ); 248 $success_message = esc_html__( 'Successfully published to dev', 'angie' );301 $success_message = esc_html__( 'Successfully published to test', 'angie' ); 249 302 } 250 303 -
angie/trunk/modules/code-snippets/classes/rest-api-controller.php
r3443255 r3464872 20 20 [ 21 21 [ 22 'methods' => \WP_REST_Server::READABLE, 23 'callback' => [ $this, 'list_snippets' ], 24 'permission_callback' => [ $this, 'check_permission' ], 22 'methods' => \WP_REST_Server::READABLE, 23 'callback' => [ $this, 'list_snippets' ], 24 'permission_callback' => [ $this, 'check_permission' ], 25 'args' => [ 26 'type' => [ 27 'required' => false, 28 'type' => 'string', 29 'sanitize_callback' => 'sanitize_text_field', 30 ], 31 'deployment_status' => [ 32 'required' => false, 33 'type' => 'string', 34 'sanitize_callback' => 'sanitize_text_field', 35 ], 36 ], 25 37 ], 26 38 ] … … 32 44 [ 33 45 [ 34 'methods' => \WP_REST_Server::READABLE,35 'callback' => [ $this, 'get_snippet' ],36 'permission_callback' => [ $this, 'check_permission' ], 37 'args' => [46 'methods' => \WP_REST_Server::READABLE, 47 'callback' => [ $this, 'get_snippet' ], 48 'permission_callback' => [ $this, 'check_permission' ], 49 'args' => [ 38 50 'slug' => [ 39 'required' => true,40 'type' => 'string',41 'sanitize_callback' => 'sanitize_text_field', 42 ], 43 ], 44 ], 45 [ 46 'methods' => \WP_REST_Server::DELETABLE,47 'callback' => [ $this, 'delete_snippet' ],48 'permission_callback' => [ $this, 'check_permission' ], 49 'args' => [51 'required' => true, 52 'type' => 'string', 53 'sanitize_callback' => 'sanitize_text_field', 54 ], 55 ], 56 ], 57 [ 58 'methods' => \WP_REST_Server::DELETABLE, 59 'callback' => [ $this, 'delete_snippet' ], 60 'permission_callback' => [ $this, 'check_permission' ], 61 'args' => [ 50 62 'slug' => [ 51 'required' => true,52 'type' => 'string',63 'required' => true, 64 'type' => 'string', 53 65 'sanitize_callback' => 'sanitize_text_field', 54 66 ], … … 63 75 [ 64 76 [ 65 'methods' => \WP_REST_Server::READABLE,66 'callback' => [ $this, 'list_snippet_files' ],67 'permission_callback' => [ $this, 'check_permission' ], 68 'args' => [77 'methods' => \WP_REST_Server::READABLE, 78 'callback' => [ $this, 'list_snippet_files' ], 79 'permission_callback' => [ $this, 'check_permission' ], 80 'args' => [ 69 81 'slug' => [ 70 'required' => true,71 'type' => 'string',72 'sanitize_callback' => 'sanitize_text_field', 73 ], 74 ], 75 ], 76 [ 77 'methods' => \WP_REST_Server::CREATABLE,78 'callback' => [ $this, 'upsert_snippet_files' ],79 'permission_callback' => [ $this, 'check_permission' ], 80 'args' => [81 'slug' => [82 'required' => true,83 'type' => 'string',84 'sanitize_callback' => 'sanitize_text_field', 85 ], 86 'files' => [87 'required' => true, 88 'type' => 'array',82 'required' => true, 83 'type' => 'string', 84 'sanitize_callback' => 'sanitize_text_field', 85 ], 86 ], 87 ], 88 [ 89 'methods' => \WP_REST_Server::CREATABLE, 90 'callback' => [ $this, 'upsert_snippet_files' ], 91 'permission_callback' => [ $this, 'check_permission' ], 92 'args' => [ 93 'slug' => [ 94 'required' => true, 95 'type' => 'string', 96 'sanitize_callback' => 'sanitize_text_field', 97 ], 98 'files' => [ 99 'required' => true, 100 'type' => 'array', 89 101 ], 90 102 'overwrite' => [ 91 103 'required' => false, 92 'type' => 'boolean',93 'default' => false,94 ], 95 'type' => [96 'required' => false,97 'type' => 'string',104 'type' => 'boolean', 105 'default' => false, 106 ], 107 'type' => [ 108 'required' => false, 109 'type' => 'string', 98 110 'sanitize_callback' => 'sanitize_text_field', 99 111 ], … … 108 120 [ 109 121 [ 110 'methods' => \WP_REST_Server::READABLE,111 'callback' => [ $this, 'get_snippet_file' ],112 'permission_callback' => [ $this, 'check_permission' ], 113 'args' => [114 'slug' => [115 'required' => true,116 'type' => 'string',122 'methods' => \WP_REST_Server::READABLE, 123 'callback' => [ $this, 'get_snippet_file' ], 124 'permission_callback' => [ $this, 'check_permission' ], 125 'args' => [ 126 'slug' => [ 127 'required' => true, 128 'type' => 'string', 117 129 'sanitize_callback' => 'sanitize_text_field', 118 130 ], 119 131 'filename' => [ 120 'required' => true,121 'type' => 'string',132 'required' => true, 133 'type' => 'string', 122 134 'sanitize_callback' => 'sanitize_text_field', 123 135 ], … … 132 144 [ 133 145 [ 134 'methods' => \WP_REST_Server::CREATABLE,135 'callback' => [ $this, 'set_dev_mode' ],136 'permission_callback' => [ $this, 'check_permission' ], 137 'args' => [146 'methods' => \WP_REST_Server::CREATABLE, 147 'callback' => [ $this, 'set_dev_mode' ], 148 'permission_callback' => [ $this, 'check_permission' ], 149 'args' => [ 138 150 'enabled' => [ 139 'required' => true,140 'type' => 'boolean',151 'required' => true, 152 'type' => 'boolean', 141 153 'sanitize_callback' => 'rest_sanitize_boolean', 142 154 ], … … 151 163 [ 152 164 [ 153 'methods' => \WP_REST_Server::CREATABLE,154 'callback' => [ $this, 'publish_snippet' ],165 'methods' => \WP_REST_Server::CREATABLE, 166 'callback' => [ $this, 'publish_snippet' ], 155 167 'permission_callback' => [ $this, 'check_permission' ], 156 'args' => [168 'args' => [ 157 169 'slug' => [ 158 'required' => true,159 'type' => 'string',170 'required' => true, 171 'type' => 'string', 160 172 'sanitize_callback' => 'sanitize_text_field', 161 173 ], … … 170 182 [ 171 183 [ 172 'methods' => \WP_REST_Server::CREATABLE,173 'callback' => [ $this, 'validate_snippet' ],174 'permission_callback' => [ $this, 'check_permission' ], 175 'args' => [184 'methods' => \WP_REST_Server::CREATABLE, 185 'callback' => [ $this, 'validate_snippet' ], 186 'permission_callback' => [ $this, 'check_permission' ], 187 'args' => [ 176 188 'files' => [ 177 189 'required' => true, 178 'type' => 'array',190 'type' => 'array', 179 191 ], 180 192 ], … … 187 199 [ 188 200 [ 189 'methods' => \WP_REST_Server::READABLE,190 'callback' => [ $this, 'is_dev_mode' ],201 'methods' => \WP_REST_Server::READABLE, 202 'callback' => [ $this, 'is_dev_mode' ], 191 203 'permission_callback' => [ $this, 'check_permission' ], 192 204 ], … … 200 212 201 213 public function list_snippets( $request ) { 202 $posts = Snippet_Repository::get_all_snippets(); 214 $type = $request->get_param( 'type' ); 215 $deployment_status_param = $request->get_param( 'deployment_status' ); 216 $deployment_statuses = $deployment_status_param ? array_map( 'trim', explode( ',', $deployment_status_param ) ) : []; 217 218 $posts = Snippet_Repository::get_all_snippets( $type ); 203 219 204 220 $snippets = []; 205 221 foreach ( $posts as $post ) { 206 $snippets[] = Snippet_Repository::get_snippet_data( $post ); 222 $snippet_data = Snippet_Repository::get_snippet_data( $post ); 223 224 if ( ! empty( $deployment_statuses ) && ! in_array( $snippet_data['deploymentStatus'], $deployment_statuses, true ) ) { 225 continue; 226 } 227 228 $snippets[] = $snippet_data; 207 229 } 208 230 … … 249 271 250 272 public function get_snippet_file( $request ) { 251 $slug = $request->get_param( 'slug' );273 $slug = $request->get_param( 'slug' ); 252 274 $filename = $request->get_param( 'filename' ); 253 $post = Snippet_Repository::find_snippet_post_by_slug( $slug );275 $post = Snippet_Repository::find_snippet_post_by_slug( $slug ); 254 276 255 277 if ( ! $post ) { … … 278 300 279 301 return rest_ensure_response( [ 280 'name' => $file['name'],302 'name' => $file['name'], 281 303 'content' => $content, 282 'size' => strlen( $content ),304 'size' => strlen( $content ), 283 305 ] ); 284 306 } 285 307 286 308 public function upsert_snippet_files( $request ) { 287 $slug = $request->get_param( 'slug' );288 $files = $request->get_param( 'files' );309 $slug = $request->get_param( 'slug' ); 310 $files = $request->get_param( 'files' ); 289 311 $overwrite = $request->get_param( 'overwrite' ); 290 $type = $request->get_param( 'type' );312 $type = $request->get_param( 'type' ); 291 313 292 314 if ( ! is_array( $files ) || empty( $files ) ) { … … 417 439 return rest_ensure_response( [ 418 440 'success' => true, 441 'created' => true, 419 442 'message' => esc_html__( 'Snippet created successfully.', 'angie' ), 420 'slug' => $slug, 421 'files' => count( $sanitized_files ), 443 'slug' => $slug, 444 'post_id' => $post_id, 445 'files' => count( $sanitized_files ), 422 446 ] ); 423 447 } … … 452 476 return rest_ensure_response( [ 453 477 'success' => true, 478 'created' => false, 454 479 'message' => esc_html__( 'Snippet files updated successfully.', 'angie' ), 455 'slug' => $slug, 456 'files' => count( $merged_files ), 480 'slug' => $slug, 481 'post_id' => $post->ID, 482 'files' => count( $merged_files ), 457 483 ] ); 458 484 } … … 470 496 } 471 497 472 $environments = [ Dev_Mode_Manager::ENV_DEV, Dev_Mode_Manager::ENV_PROD ];473 File_System_Handler::delete_snippet_files( $post->ID, $environments );474 475 $result = Snippet_Repository::delete_snippet( $post->ID );476 477 if ( ! $result ) {478 return new \WP_Error(479 'delete_failed',480 esc_html__( 'Failed to delete snippet.', 'angie' ),481 [ 'status' => 500 ]482 );483 }484 485 Cache_Manager::clear_published_snippet_cache();486 487 return rest_ensure_response( [488 'success' => true,489 'message' => esc_html__( 'Snippet deleted successfully.', 'angie' ),490 'slug'=> $slug,491 ] );492 }493 494 public function set_dev_mode( $request ) {495 $enabled = $request->get_param( 'enabled' );496 497 if ( $enabled ) {498 $session = Dev_Mode_Manager::create_dev_mode_session();499 500 if ( ! $session ) {501 return new \WP_Error( 502 'session_creation_failed',503 esc_html__( 'Failed to create dev mode session. User must be logged in.', 'angie' ), 504 [ 'status' => 403 ] 505 ); 506 } 507 508 return rest_ensure_response( [ 509 'success' => true,510 'message' => esc_html__( 'Dev mode enabled for 1 hour.', 'angie' ),511 'enabled' => true,512 'expiry' => $session['expiry'], 513 ] ); 514 } else { 515 Dev_Mode_Manager::clear_dev_mode_session(); 516 517 return rest_ensure_response( [ 518 'success' => true,519 'message' => esc_html__( 'Dev mode disabled.', 'angie' ),520 'enabled' => false, 521 ] ); 522 }523 } 524 525 public function publish_snippet( $request ) { 526 $slug = $request->get_param( 'slug');527 $post = Snippet_Repository::find_snippet_post_by_slug( $slug ); 528 529 if ( ! $post ) { 530 return new \WP_Error( 531 'snippet_not_found',532 esc_html__( 'Snippet not found.', 'angie' ), 533 [ 'status' => 404 ] 534 ); 535 } 536 537 $files = Snippet_Repository::get_snippet_files( $post->ID ); 538 539 if ( empty( $files ) ) { 540 return new \WP_Error( 541 'no_files',542 esc_html__( 'Snippet has no files to publish.', 'angie' ), 543 [ 'status' => 400 ] 544 ); 545 } 546 547 File_System_Handler::write_snippet_files_to_disk( Dev_Mode_Manager::ENV_PROD, $post->ID, $files);548 Cache_Manager::clear_published_snippet_cache(); 549 550 return rest_ensure_response( [ 551 'success' => true,552 'message' => esc_html__( 'Snippet published to production successfully.', 'angie' ),553 'slug' => $slug,554 'files'=> count( $files ),555 ] );556 }498 $environments = [ Dev_Mode_Manager::ENV_DEV, Dev_Mode_Manager::ENV_PROD ]; 499 File_System_Handler::delete_snippet_files( $post->ID, $environments ); 500 501 $result = Snippet_Repository::delete_snippet( $post->ID ); 502 503 if ( ! $result ) { 504 return new \WP_Error( 505 'delete_failed', 506 esc_html__( 'Failed to delete snippet.', 'angie' ), 507 [ 'status' => 500 ] 508 ); 509 } 510 511 Cache_Manager::clear_published_snippet_cache(); 512 513 return rest_ensure_response( [ 514 'success' => true, 515 'message' => esc_html__( 'Snippet deleted successfully.', 'angie' ), 516 'slug' => $slug, 517 ] ); 518 } 519 520 public function set_dev_mode( $request ) { 521 $enabled = $request->get_param( 'enabled' ); 522 523 if ( $enabled ) { 524 $session = Dev_Mode_Manager::create_dev_mode_session(); 525 526 if ( ! $session ) { 527 return new \WP_Error( 'session_creation_failed', 528 esc_html__( 'Failed to create test mode session. User must be logged in.', 'angie' ), 529 [ 'status' => 403 ] 530 ); 531 } 532 533 return rest_ensure_response( [ 534 'success' => true, 535 'message' => esc_html__( 'Test mode enabled.', 'angie' ), 536 'enabled' => true, 537 'expiry' => $session['expiry'], 538 ] ); 539 } else { 540 Dev_Mode_Manager::clear_dev_mode_session(); 541 542 return rest_ensure_response( [ 543 'success' => true, 544 'message' => esc_html__( 'Test mode disabled.', 'angie' ), 545 'enabled' => false, 546 ] ); 547 } 548 } 549 550 public function publish_snippet( $request ) { 551 $slug = $request->get_param( 'slug' ); 552 $post = Snippet_Repository::find_snippet_post_by_slug( $slug ); 553 554 if ( ! $post ) { 555 return new \WP_Error( 556 'snippet_not_found', 557 esc_html__( 'Snippet not found.', 'angie' ), 558 [ 'status' => 404 ] 559 ); 560 } 561 562 $files = Snippet_Repository::get_snippet_files( $post->ID ); 563 564 if ( empty( $files ) ) { 565 return new \WP_Error( 566 'no_files', 567 esc_html__( 'Snippet has no files to publish.', 'angie' ), 568 [ 'status' => 400 ] 569 ); 570 } 571 572 File_System_Handler::write_snippet_files_to_disk( Dev_Mode_Manager::ENV_PROD, $post->ID, $files ); 573 Cache_Manager::clear_published_snippet_cache(); 574 575 return rest_ensure_response( [ 576 'success' => true, 577 'message' => esc_html__( 'Snippet published to production successfully.', 'angie' ), 578 'slug' => $slug, 579 'post_id' => $post->ID, 580 'files' => count( $files ), 581 ] ); 582 } 557 583 558 584 public function validate_snippet( $request ) { -
angie/trunk/modules/code-snippets/classes/snippet-repository.php
r3443255 r3464872 70 70 } 71 71 72 public static function get_all_snippets( ) {72 public static function get_all_snippets( $type = null ) { 73 73 $args = [ 74 74 'post_type' => Module::CPT_NAME, … … 78 78 'order' => 'ASC', 79 79 ]; 80 81 if ( ! empty( $type ) && Taxonomy_Manager::is_valid_type( $type ) ) { 82 $args['tax_query'] = [ 83 [ 84 'taxonomy' => Taxonomy_Manager::TAXONOMY_NAME, 85 'field' => 'slug', 86 'terms' => $type, 87 ], 88 ]; 89 } 80 90 81 91 return get_posts( $args ); … … 95 105 public static function get_snippet_data( $post ) { 96 106 $files = self::get_snippet_files( $post->ID ); 107 $terms = wp_get_object_terms( $post->ID, Taxonomy_Manager::TAXONOMY_NAME, [ 'fields' => 'slugs' ] ); 108 $timestamps = Dev_Mode_Manager::get_snippet_environment_timestamps( $post->ID ); 109 $is_elementor_widget = ! is_wp_error( $terms ) && in_array( 'elementor-widget', $terms, true ); 97 110 98 return [ 111 $data = [ 112 'id' => $post->ID, 99 113 'slug' => self::get_snippet_slug_from_post( $post ), 100 114 'title' => $post->post_title, 101 115 'status' => $post->post_status, 102 116 'files' => self::build_file_list( $files ), 117 'type' => is_wp_error( $terms ) ? [] : $terms, 118 'deploymentStatus' => $timestamps['status'], 103 119 ]; 120 121 if ( $is_elementor_widget ) { 122 $data['widgetName'] = Widget_Name_Resolver::get_widget_name_for_snippet( $post->ID ); 123 } 124 125 return $data; 104 126 } 105 127 -
angie/trunk/modules/code-snippets/classes/snippet-validator.php
r3443255 r3464872 45 45 } 46 46 47 $validate_url = self::get_loopback_url( 'angie/v1/snippets/validate' ); 47 48 $response = wp_remote_post( 48 rest_url( 'angie/v1/snippets/validate' ),49 $validate_url, 49 50 [ 50 51 'headers' => [ … … 79 80 80 81 if ( \WP_Http::OK !== $response_code || ! isset( $validation_result['valid'] ) || ! $validation_result['valid'] ) { 81 $error_message = isset( $validation_result['message'] ) ? $validation_result['message'] : esc_html__( 'Unknown validation error.', 'angie' ); 82 $error_message = $validation_result['data']['error']['message'] 83 ?? $validation_result['data']['details']['message'] 84 ?? $validation_result['message'] 85 ?? esc_html__( 'Unknown validation error.', 'angie' ); 86 82 87 return new \WP_Error( 83 88 'validation_failed', … … 144 149 145 150 try { 151 146 152 include $file_path; 147 153 … … 183 189 } 184 190 191 // This loopback url is used to validate the snippet in the local environment (wp-env) 192 private static function get_loopback_url( $rest_route ) { 193 $url = rest_url( $rest_route ); 194 195 $host = wp_parse_url( $url, PHP_URL_HOST ); 196 197 if ( 'localhost' !== $host && '127.0.0.1' !== $host ) { 198 return $url; 199 } 200 201 return str_replace( $host, 'host.docker.internal', $url ); 202 } 203 185 204 private static function create_temp_snippet_dir() { 186 205 $temp_base = WP_CONTENT_DIR . '/angie-snippets/temp'; -
angie/trunk/modules/code-snippets/module.php
r3443255 r3464872 36 36 37 37 add_action( 'rest_api_init', [ $this, 'register_rest_routes' ] ); 38 add_action( 'wp_logout', [ Dev_Mode_Manager::class, 'clear_dev_mode_session' ] ); 38 39 39 40 if ( $this->should_load_snippets() ) { … … 79 80 80 81 private function init_components() { 82 require_once __DIR__ . '/utils.php'; 83 81 84 Post_Type_Manager::init(); 82 85 Taxonomy_Manager::init(); … … 96 99 97 100 public static function is_active(): bool { 98 return defined( 'ANGIE_CODE_SNIPPETS_ACTIVE' ) && ANGIE_CODE_SNIPPETS_ACTIVE;101 return true; 99 102 } 100 103 -
angie/trunk/modules/elementor-core/module.php
r3363303 r3464872 41 41 $this->init_rest_controllers(); 42 42 add_action( 'elementor/editor/after_enqueue_scripts', [ $this, 'enqueue_scripts' ] ); 43 add_action( 'elementor/elements/categories_registered', [ $this, 'register_widget_categories' ] ); 44 add_action( 'elementor/editor/templates/panel/category', [ $this, 'render_angie_category_generate_button' ] ); 45 add_action( 'elementor/editor/templates/panel/category/content', [ $this, 'render_angie_category_empty_state' ] ); 43 46 add_filter( 'angie_mcp_plugins', function ( $plugins ) { 44 47 $plugins['elementor'] = []; … … 47 50 } 48 51 49 /** 50 * Initialize controllers 51 */ 52 public function register_widget_categories( $elements_manager ) { 53 $elements_manager->add_category( 54 'angie-widgets', 55 [ 56 'title' => esc_html__( 'Angie Widgets', 'angie' ), 57 'icon' => 'eicon-ai', 58 'hideIfEmpty' => false, 59 'active' => true, 60 ] 61 ); 62 } 63 64 public function render_angie_category_generate_button() { 65 ?><# if ( 'angie-widgets' === name ) { #> 66 <span class="angie-category-generate" data-angie-generate-widget style="display: inline-flex; align-items: center; gap: 4px; margin-inline-start: auto; color: #C00BB9; color: light-dark(#C00BB9, #F0ABFC); font-size: 12px; font-weight: 500; cursor: pointer;"> 67 <i class="eicon-ai" aria-hidden="true" style="font-size: 14px;"></i> 68 <?php echo esc_html__( 'Generate', 'angie' ); ?> 69 </span> 70 <# } #><?php 71 } 72 73 public function render_angie_category_empty_state() { 74 if ( $this->has_angie_widgets() ) { 75 return; 76 } 77 ?><# if ( 'angie-widgets' === name ) { #> 78 <div class="angie-category-empty-state" data-angie-category-empty-state style="grid-column: 1 / -1; width: 100%; padding: 12px 20px;"> 79 <p style="color: #A4AFB7; font-size: 12px; margin: 0; line-height: 1.4;"><?php echo esc_html__( 'Your generated widgets by Angie will show up here.', 'angie' ); ?></p> 80 </div> 81 <# } #><?php 82 } 83 84 private function has_angie_widgets(): bool { 85 $widgets = \Elementor\Plugin::$instance->widgets_manager->get_widget_types(); 86 87 foreach ( $widgets as $widget ) { 88 if ( in_array( 'angie-widgets', $widget->get_categories(), true ) ) { 89 return true; 90 } 91 } 92 93 return false; 94 } 95 52 96 private function init_rest_controllers() { 53 97 $this->kit_provider = new Kit_Provider(); -
angie/trunk/modules/sidebar/assets/sidebar.css
r3379381 r3464872 18 18 body.angie-sidebar-active #angie-body-top-padding { 19 19 width: 100%; 20 height: 8px;20 height: 0; 21 21 } 22 22 … … 24 24 body.angie-sidebar-active #wpadminbar { 25 25 inset-inline-start: var(--angie-sidebar-width) !important; 26 inset-inline-end: 8px!important;27 width: calc(100% - 8px -var(--angie-sidebar-width)) !important;28 margin-top: 8px;26 inset-inline-end: 0 !important; 27 width: calc(100% - var(--angie-sidebar-width)) !important; 28 margin-top: 0; 29 29 } 30 30 31 31 body.angie-sidebar-active #wpfooter { 32 bottom: 8px!important;32 bottom: 0 !important; 33 33 } 34 34 35 35 body.angie-sidebar-active #adminmenuback { 36 top: 8px!important;36 top: 0 !important; 37 37 } 38 38 39 39 html.angie-sidebar-active { 40 margin-inline-end: 8px!important;40 margin-inline-end: 0 !important; 41 41 } 42 42 43 43 body.angie-sidebar-active #adminmenuwrap { 44 margin-bottom: 8px;44 margin-bottom: 0; 45 45 } 46 46 47 body.angie-sidebar-active #angie-wrapper {48 --angie-wrapper-border-width: 1px;49 position: fixed;50 top: 8px;51 inset-inline-start: var(--angie-sidebar-width);52 width: calc(100% - 7px - (var(--angie-wrapper-border-width) * 2) - var(--angie-sidebar-width));53 height: calc(100% - 17px);54 z-index: var(--angie-sidebar-z-index) !important;55 border: var(--angie-wrapper-border-width) solid var(--action-selected, rgba(0, 0, 0, 0.08));56 border-radius: 8px;57 box-shadow: 0 0 0 100vmax #FCFCFC;58 pointer-events: none;59 }60 61 62 47 body.angie-sidebar-active .woocommerce-layout__header{ 63 margin-top: 8px;48 margin-top: 0; 64 49 } 65 50 … … 99 84 inset-inline-start: calc(var(--angie-sidebar-width) + var(--admin-menu-width)) !important; 100 85 inset-inline-end: 0 !important; 101 top: calc(32px + 8px)!important;102 margin-bottom: 8px!important;86 top: 32px !important; 87 margin-bottom: 0 !important; 103 88 } 104 89 105 90 .block-editor-page.angie-sidebar-active.block-editor-page.is-fullscreen-mode .interface-interface-skeleton { 106 top: 8px!important;91 top: 0 !important; 107 92 } 108 93 -
angie/trunk/modules/sidebar/components/sidebar-html.php
r3385659 r3464872 91 91 </script> 92 92 93 <div id='angie-wrapper'></div>94 93 <div 95 94 id='angie-sidebar-container' -
angie/trunk/plugin.php
r3437542 r3464872 91 91 } 92 92 $this->init(); 93 $this->register_heartbeat_nonce_refresh(); 94 } 95 96 private function register_heartbeat_nonce_refresh() { 97 add_filter( 'heartbeat_received', [ $this, 'refresh_angie_nonce_on_heartbeat' ], 10, 2 ); 98 } 99 100 public function refresh_angie_nonce_on_heartbeat( array $response, array $data ): array { 101 if ( ! is_user_logged_in() ) { 102 return $response; 103 } 104 $response['angie_nonce'] = wp_create_nonce( 'wp_rest' ); 105 return $response; 93 106 } 94 107 } -
angie/trunk/readme.txt
r3443255 r3464872 5 5 Tested up to: 6.9 6 6 Requires PHP: 7.4 7 Stable tag: 1. 0.47 Stable tag: 1.1.0 8 8 License: GPLv3 9 9 License URI: https://www.gnu.org/licenses/gpl-3.0.html 10 10 11 Chat with Angie, Agentic AI for WordPress. Powered by MCP, Angie builds, manages & updates your site, boosting productivity with ease.11 Angie Code: Your expert WordPress developer, powered by AI. Build anything you can imagine without writing a single line of code. 12 12 13 13 == Description == 14 14 15 **Disclaimer:** Angie is currently in a Beta mode. While core functionality in WordPress and the Elementor Editor is stable and ready to explore, some integrations with other plugins and tools are still evolving and may not work as expected yet. We're continuously improving Angie and expanding these integrations, so you can expect broader, more refined capabilities soon. In the meantime, be sure to back up your site before using Angie. 16 17 Manage and update your website by chatting with Angie, a powerful Agentic AI for any WordPress website. With your instruction, Angie can perform simple or complex multi-step workflows and bulk actions in an instant, to multiply your productivity across any part of website creation. 18 19 Unlike traditional AI tools or AI plugins that only generate content or assist with isolated tasks, Angie acts as an intelligent AI assistant that powers your full WordPress site flow, from content and design to code, and site maintenance, without any manual tagging or training. Perfect for professional web creators, agencies, freelancers, and professional site owners who want to work smarter and achieve more. 20 21 Capture the power of MCP (Model Context Protocol) technology and autonomous AI Agents to carry out multi-step tasks like updating pages, installing plugins, bulk uploading new inventory to your WooCommerce store, or generating entire landing pages, all executed intelligently and in context. Angie frees you from repetitive admin work so you can focus on strategy and creativity, while your AI assistant runs your WordPress site seamlessly. 22 23 == Use Cases == 24 25 Angie performs hundreds of everyday WordPress tasks, spanning content, design, development, and store management. Below are just some of the operations Angie can streamline for web creators: 26 27 = Content Management = 28 29 - **Bulk content creation** - Quickly generate or duplicate multiple posts, products, or pages in batches. 30 - **Upload content** - Add images, PDFs or other media from local files or URLs, letting Angie handle the upload and attachment process. 31 - **Generate content based on web searches** - Pull in up-to-date facts and references from the web to draft posts, product descriptions, and page copy aligned to your brief. 32 - **AI image generation or editing** (using Angie's Image Generation Mini App) - Generate new images or edit existing ones (remove or replace background, resize, upscale, generative fill, isolate objects, cleanup) without leaving WordPress. 33 - **Container generation** (requires Elementor's Editor) - Optimize container layouts, automatically placing elements in the appropriate container. 34 - **Scheduling posts & applying categories and tags** - Fill in unpublished items with smart scheduling and auto-apply categories or tags based on content meaning for better organization and SEO. 35 - **Create Custom Post Types with ACF®** (Current status: experimental) - Define and register new post types tailored to specific content structures, like events, portfolios, or testimonials. 36 - **Full page content translation** - Translate individual blocks, full pages, or any piece of content across the site without relying on any external tools. 37 - **Generate full pages** - Using the Page Planner, describe the type of page needed (e.g. "About page for a bakery") and let Angie create it with the layout, content, and structure included. 38 39 = Design & Development = 40 41 - **Update global styles** - Instantly adjust typography, colors, spacing, and other theme-wide design settings with just a single prompt. 42 - **Code creation** - Write or insert code snippets (HTML, CSS, JS) for site customization. 43 - **Improve slugs** - Refine post, page, or product URLs for better SEO and readability, either individually or in bulk. 44 45 = Site Management = 46 47 - **Plugin & Theme operations** - Activate, deactivate, install, or remove plugins and themes with a single prompt. 48 - **Site configuration** - Adjust settings like site title, tagline, site language, timezone, or reading preferences. 49 - **User roles & access** - Add new users, update roles, or adjust permissions through conversational commands. 50 - **Media library control** - Enhance media assets with AI-generated alt text, captions and descriptions, or quickly search, rename, organize, and clean up files in bulk using simple prompts. 51 52 = WooCommerce Management = 53 54 - **Create and manage products** - Generate detailed WooCommerce product listings with descriptions, pricing, categories, and images. 55 - **Stock management** - Update inventory levels, set stock statuses, and adjust quantity settings across multiple products. 56 - **Pricing adjustments** - Quickly update regular and sale prices, either individually or in bulk, across multiple products. 57 - **Promotions & discounts** - Apply promotional discounts, set up coupon codes, or schedule limited-time sales with a single prompt. 58 59 = Analytics & Reporting (Coming soon) = 60 61 - **Product performance reports** - Get instant snapshots of sales volume, revenue trends, and top-selling products. 62 - **Order & product management** - Ask Angie to manage fulfillment, stock updates, order statuses, and product details. 63 64 = More ways Angie can help you = 65 66 - **Site recommendations** - Get step-by-step instructions for WordPress tasks or best-practice recommendations. 67 - **How-to assistance** - Ask Angie how to use the Editor, WordPress, or any plugin and get clear, actionable instructions. 68 - **Ideation partner** - Brainstorm ideas for new content, campaigns, or site features with Angie. 69 - **Plugin & theme advice** - Discover which plugins or themes can extend your site with the functionality you need. 70 - **Hosting & performance tips** - Ask Angie about site health, performance issues, or hosting configurations. 71 72 == Key Benefits == 73 74 - **Agentic AI - Multi-step workflow automation** - From a single prompt, Angie completes complex, multi-step tasks. Whether it's creating pages, updating inventory, or publishing bulk content, Angie handles it seamlessly within your site. 75 - **Sitewide next-generation AI engine** - Angie works across your entire WordPress site, automating content, design, code, plugin management, and maintenance workflows. It delivers a unified AI-powered engine for any WordPress website, even if you're not using Elementor's Editor. 76 - **AI for your entire website journey** - Angie supports you from planning and building to launching and maintaining your WordPress site. It helps you build, run, and grow your site with smart automation at every stage. 77 - **Deep context awareness with MCP technology** - Angie instantly understands your site's structure, including pages, posts, WooCommerce products, plugins, and templates. This deep site-level intelligence powers precise, context-aware actions without manual tagging or setup. 78 - **Intelligent AI assistance** - Angie performs complex tasks quickly and accurately. It can install plugins, summarize data, manage updates, and reduce repetitive admin work, making site management easier and more efficient. 79 - **Easy AI workflow control** - Manage complex tasks with simple commands. Angie brings content, design, code, data, and backend admin actions together in one place, so you can run your whole site without juggling tools. 80 - **Built for professionals and continuous growth** - Designed for agencies, freelancers, and professional web creators managing multiple sites. Angie streamlines workflows and supports ongoing updates like sales launches, store inventory management, and maintenance, helping your site grow with minimal effort. 81 - **Zero setup, seamless integration** - Install and go. Angie works with your existing site as-is. No training or manual configuration is needed. Start automating workflows immediately. 82 83 == Features == 84 85 - **Prompt-based task execution** - Issue a single prompt, and Angie translates it into a structured, multi-step workflow with no scripting or manual setup required. 86 - **Context-aware content mapping** - Built to understand WordPress architecture, Angie automatically determines where each action belongs across pages, templates, products, and more. 87 - **Multi-modal output generation** - Angie is capable of producing diverse outputs (including text, code, images, or layouts) within the same interface, all triggered from one command. 88 - **Flexible input options** - Angie supports multiple input sources, such as uploading files, images, or providing URLs for reference. 89 - **Data-driven actions** - Angie accepts structured files like CSVs or JSON to drive large-scale updates, content imports, or data manipulations across your site. 90 - **Plugin & theme automation** - Since plugin and theme management is built into Angie's workflow system, installation and configuration can happen automatically, without any manual steps required. 91 - **Web search integration** - With built-in web search, Angie can retrieve and process real-time information to support tasks like content creation or analysis. 92 - **Deep Elementor integration** - The integration with Elementor is handled natively, giving Angie full access to design components, layout structures, and editor actions. 93 94 == Roadmap: Expanding next-generation AI capabilities == 95 96 Angie is continuously evolving to deliver smarter, deeper AI automation for WordPress site management. Upcoming updates will include: 97 98 - **Background task scheduling** - Run complex or time-intensive tasks in the background, even when you're offline. 99 - **Custom agents** - Create and assign specialized abilities for Angie to interact with your site in new ways. 100 - **Integration hub** - Easily connect external MCP servers or built-in solutions such as the Figma connector from a dedicated screen. 101 - **Expanded plugin integration** - Expansion of integration across more plugins including WooCommerce, The Events Calendar and more. 102 103 These enhancements will empower professional web creators, agencies, and freelancers to automate complex site-wide tasks effortlessly, streamline workflows, and unlock the full potential of AI-powered WordPress site management. 104 105 == Get started today == 106 107 Experience the power of Angie, your context-aware AI assistant for WordPress! 108 109 Angie requires a connection to an active Elementor account to identify the user and provide access to the AI-powered services to their WordPress site. Make sure you are signed in with an Elementor user account. Upon activating the plugin, you will be guided to connect your site. Learn more about Angie's Terms and Conditions. 110 111 More exciting features and improvements are coming soon, so stay tuned! 15 **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. 16 17 Angie Code lets professional web creators and agencies create custom Elementor widgets, snippets for WordPress, and functionality instantly. Simply describe what you need, and watch Angie build production-ready code in seconds, zero coding knowledge required. 18 19 While standard solutions provide a vital foundation for most project requirements, Angie Code eliminates the compromise of "close enough", empowering you to build the bespoke functionalities that define the final, most impactful details of your site ensuring every element meets your exact specifications with technical precision. 20 21 == Fully configurable inside WordPress & Elementor == 22 23 Angie produces PHP, CSS, and JavaScript, whose output is editable through conversation with Angie ("make the font bigger," "add a hover animation"), or switch to the Elementor Editor's visual controls for pixel-perfect adjustments. You're never locked in - every widget is yours to own, edit, and evolve. 24 25 Your existing theme, plugins, Elementor setup, and Gutenberg editor all stay exactly as they are. Angie adds AI-powered creation capabilities on top of your current environment. There is no new platform to learn, no migration required, and no disruption to your workflow. 26 27 == What Angie can do == 28 29 Angie's expertise delivers specialized categories of production-ready components: 30 31 - **Build bespoke Elementor widgets** - Create custom widgets from scratch and refine their style using native Elementor Editor controls. 32 - **Extend Elementor widgets** - Inject custom controls and advanced capabilities into existing widgets to expand their functional range. 33 - **Customize WordPress admin & backend** - Tailor your dashboard, add custom post types, extend WooCommerce, and implement custom hooks and filters. 34 - **Architect front-end enhancements** - Integrate CSS and JavaScript for sophisticated animations, cursor effects, and immersive interactive behaviors. 35 - **Construct visual apps** - Build fully functional front-end applications like custom calculators, and quizzes. 36 - **Generate complete landing pages** - Produce entire pages with layout, content, and styling for Elementor and Gutenberg. 37 38 == Multi-modal input and collaborative building == 39 40 - **Conversational building** - Describe the functionality you need in plain language; Angie handles the engineering. 41 - **Visual references** - Upload a screenshot of a design you love, or provide a URL, and your AI developer will build it for you. 42 - **Collaborative refinement** - Not exactly what you wanted? Just tell Angie what to change. She understands your feedback and refines the result in real-time. 43 44 == Safe by design - experiment freely, deploy confidently == 45 46 Everything Angie Code creates is built and tested in a dedicated preview environment. See it on your page, tweak it, and iterate as many times as you want - nothing is added to your live site until you personally approve it. This is your safety net: the freedom to try anything without the risk of breaking anything. 47 48 == Streamline site management == 49 50 Beyond building, Angie acts as an agentic assistant to help you perform site-wide workflows, handle bulk actions, and manage content through simple conversation. 51 52 Stop compromising your vision. Start building with Angie. 53 54 == Contributors & Developers == 55 56 "Angie: Agentic AI for WordPress" is open source software. The following people have contributed to this plugin. 57 58 Contributors: Elementor 112 59 113 60 == FAQ == 114 61 115 = 1. What is Angie and how does it work? = 116 117 Angie is a next-generation powerful Agentic AI assistant for your WordPress website that enables you to manage and update your website using conversational prompts. It understands your entire site's structure, including pages, posts, templates, and the products you sell in your WooCommerce store. Angie also knows all the widgets and plugins you use and can install new plugins for you. It has deep knowledge of where everything is inside WordPress. Using simple natural language prompts, Angie performs complex multi-step tasks on your behalf, helping you automate updates, content generation, design changes, and much more so you can multiply your productivity. This saves you time and effort, while making site management easier and more efficient. 118 119 = 2. Is Angie compatible with WordPress and Elementor? = 120 121 Yes. Angie is fully compatible with WordPress and seamlessly integrates with Elementor's Editor, though it is not required. Angie is also integrated with other themes and plugins, such as Gutenberg (The WordPress Block Editor), The Events Calendar, GiveWP. It works across your entire site, delivering powerful AI-driven automation beyond just page builders. 122 123 = 3. Do I need to configure, train, or restructure my existing WordPress site for Angie to work? = 124 125 No, Angie requires no configuration, manual training, or site restructuring to get started. Once installed and connected, Angie automatically recognizes your site's layout, content types, plugins, and even your WooCommerce products. There's no tagging, manual setup, or complicated process required. Angie seamlessly adapts to your existing WordPress site as-is, allowing you to open its interface and immediately start issuing natural language commands to automate complex, multi-step tasks. 126 127 = 4. Will Angie complicate my workflow? = 128 129 Not at all. Angie simplifies your workflow by unifying AI-powered content, design, and code automation into one intelligent interface. With text commands and deep site context awareness, Angie helps you move faster from idea to execution with less friction. 130 131 = 5. Do you need to use Elementor's Editor for Angie to work? = 132 133 No, Angie works directly in WordPress and is fully compatible with various plugins. You don't need Elementor to use it. However, Angie has deep integration with Elementor's Editor, giving it extra context and control over widgets, templates, and design structure. If you're using Elementor, you'll get the most out of Angie — but it also works smoothly with Gutenberg and other WordPress environments. 134 135 = 6. Is Angie free? = 136 137 Yes, Angie has a free tier with credits renewing daily. 138 139 = 7. Is my site secure with Angie? = 140 141 Yes. Angie is built with WordPress security best practices in mind. It does not make unauthorized changes or expose your site to external threats. All operations happen within your existing WordPress environment, and Angie only acts when you instruct it to. In fact, before performing any potentially destructive actions, like deleting content or overriding settings, Angie will alert you and ask for confirmation to ensure nothing happens unintentionally. 142 143 = 8. Does Angie keep my information safe? = 144 145 Absolutely. Angie does not collect or store sensitive site data outside of your WordPress installation. Any processing involving your site's content or structure is handled securely and in compliance with standard data privacy protocols. 146 147 = 9. Can I connect my own MCP server to Angie? = 148 149 Yes. Angie is fully extensible and allows plugin developers to integrate custom capabilities through the MCP (Model Context Protocol) system. Using the official Angie MCP SDK, you can build and register your own MCP server directly in the browser, no need for an external gateway or PHP-based server. This lets Angie discover and interact with your plugin's tools, resources, and workflows in real time. Whether you're exposing backend functionality or building advanced client-side features, Angie can run your MCP server and invoke your tools securely, right from the current screen. 150 151 = 10. Is Angie in Beta? = 152 153 Yes, Angie is in Beta, which means you'll get early access to powerful features and many more integrations with other WordPress plugins are on the way. Some actions may not be perfect yet as we continue to refine and improve the experience. Sharing your experience helps us make Angie better every day. 154 155 == Screenshots == 156 157 1. Use Angie to jumpstart any project in WordPress. Write content, design sections, and manage tasks without leaving WordPress. 158 2. Prompt Angie to plan and design pages in seconds, with Angie helping to add engaging content, media, and layouts. 159 3. Edit existing images, create variations, or generate entirely new visuals, all within Angie. No third-party tools required. 160 4. Ask Angie to run instant store-wide promotions, update prices, and refresh product listings automatically. 161 162 ## Contributors & Developers 163 164 "Angie: Agentic AI for WordPress" is open source software. The following people have contributed to this plugin. 165 62 = 1. What is Angie? = 63 64 Angie is an AI layer for WordPress. Unlike standalone AI tools, Angie is purpose-built for the WordPress ecosystem with deep contextual understanding of your specific website, its architecture, installed plugins, and data. Angie acts as an AI-powered expert WordPress developer and agentic assistant designed to help you build and manage websites without writing any code. It functions like a senior engineer that turns plain-language descriptions into production-ready code instantly. 65 66 = 2. What is Angie Code? = 67 68 Angie Code is your WordPress developer on demand. Through natural conversation, you can create custom Elementor widgets, code snippets, and functionality, without requiring any code. Describe what you want, refine it through conversation, and Angie builds it for you. Every creation is fully editable and works seamlessly with your existing WordPress setup. 69 70 Here's what you can create: 71 72 - **Elementor widgets** - Create custom widgets from scratch and refine their style using native Elementor Editor controls. 73 - **Extend Elementor widgets** - Inject custom controls and advanced capabilities into existing widgets to expand their functional range. 74 - **Customize WordPress admin & backend** - Tailor your dashboard, add custom post types, extend WooCommerce, and implement custom hooks and filters. 75 - **Architect front-end enhancements** - Integrate CSS and JavaScript for sophisticated animations, cursor effects, and immersive interactive behaviors. 76 - **Construct visual apps** - Build fully functional front-end applications like custom calculators, and quizzes. 77 - **Generate complete landing pages** - Produce entire pages with layout, content, and styling for Elementor and Gutenberg. 78 79 = 3. Is Angie Code compatible with Elementor? = 80 81 Yes! Angie is deeply integrated with the Elementor Editor. You can generate custom widgets specifically for Elementor, allowing you to create unique layouts and functionalities that aren't available in standard widget packs. The widgets Angie creates work seamlessly with Elementor's visual controls, giving you the best of both worlds: AI-powered creation and manual refinement. 82 83 = 4. Is it free to use? = 84 85 Yes. Angie has a free tier with daily renewing credits, allowing you to explore its creation capabilities every day at no cost. This lets you experiment, learn, and build real projects without any financial commitment. 86 87 = 5. How does "Test Mode" protect my site? = 88 89 Test Mode is a secure staging environment where AI-generated code runs in isolation. You can preview, test, and interact with your creation without affecting your live site. Only when you're completely satisfied do you manually publish it to production. 90 91 This ensures safety and control over every AI-generated element. You can: 92 93 - See exactly what code will be added 94 - Turn widgets on and off to test behavior 95 - Review what's in production versus staging 96 - Make iterative changes in a safe space 97 - Publish to live with confidence 98 99 No artifacts are published or affect your website until you've explicitly approved it. 100 101 = 6. Can I use screenshots or URLs to build widgets? = 102 103 Absolutely. Angie Code supports multiple input types: 104 105 - **Text prompts** - Describe what you want in natural language 106 - **Screenshots** - Upload an image of a design you want to recreate 107 - **URLs** - Provide a website link and Angie analyzes the design 108 109 Angie analyzes the layout, functionality, and design patterns to create a similar, fully editable artifacts for your site. This makes reverse-engineering inspiring designs incredibly fast. 110 111 = 7. Do I need to be a developer to use Angie Code? = 112 113 Not at all. Angie is designed to break the technical ceiling through conversation. Describe what you want, then refine it through follow-up prompts, just like working with a developer. 114 115 "Make it more colorful," "Add animation," "Match my brand colors," Angie iterates with you until it's perfect. You don't need to nail the prompt on the first try. The power is in the conversation, not the single perfect instruction. 116 117 The code remains open and fully editable for those who want to customize it manually. You're never locked in. 118 119 = 8. Can I edit what Angie generates? = 120 121 Yes. This is core to the Angie philosophy. Every widget and snippet Angie creates is fully editable, both through continued conversation with Angie and through manual code editing. 122 123 You can: 124 125 - Ask Angie to make changes conversationally 126 - Switch to Elementor's visual editor for pixel-perfect adjustments 127 - Edit the underlying PHP, CSS, and JavaScript directly 128 - Mix AI creation with manual refinement 129 130 Your creations are never locked or proprietary. They're code that works with your entire ecosystem. 131 132 = 9. Is it compatible with my theme and plugins? = 133 134 Yes. Angie works across your entire WordPress environment. While it has deep integration with Elementor, Angie Code generates PHP, CSS, and JavaScript. Angie also understands your site's existing architecture and adapts automatically. Instead of replacing your tools, Angie is also an MCP that connects your ecosystem to help you get more from it. 135 136 = 10. How does Angie handle site security? = 137 138 Angie follows WordPress security best practices. It does not perform destructive actions (like deleting content or overriding core settings) without your explicit confirmation. Because Angie is context-aware, it understands your site's boundaries and works within WordPress's security model. 139 140 Additionally: 141 142 - All code runs in Test Mode before going live 143 - You manually approve every change to production 144 - Code is generated using WordPress coding standards 145 146 = 11. Can I connect my own MCP server? = 147 148 Yes. Angie is fully extensible. Developers can use Angie's official SDK on GitHub to integrate custom capabilities through the Model Context Protocol (MCP), allowing Angie to interact with your specific tools and workflows in real-time. 149 150 Resources: 151 152 - GitHub SDK 153 - NPM package 154 - Demo plugin 155 156 = 12. Do I need to speak English to use Angie? = 157 158 Not at all. Angie is built for creators worldwide. You can prompt Angie in your native language whether you speak Spanish, French, Japanese, or any other major language. Angie understands your intent and generates the correct content accordingly. 159 160 Language should never be a barrier to bringing your WordPress ideas to life. 166 161 167 162 == Changelog == 163 164 = 1.1.0 - 2026-02-19 = 165 * New: Angie Code: build custom Elementor widgets, Gutenberg blocks, code snippets, custom functionality, and interactive apps through conversation 166 * New: Preview environment for all code creations - test before going live 167 * New: Visual reference support - upload screenshots or URLs to generate matching components 168 * New: Custom post type and field generation through conversation 169 * Tweak: Added empty state and "Generate" button to Angie category 170 * Tweak: Added referrer redirect support 171 * Tweak: Removed wrapper overlay from WordPress interface when Angie is open 172 * Tweak: Removed unused Elementor One connection button 173 168 174 = 1.0.4 - 2026-01-20 = 169 175 * Tweak: Optimized token fetching and handling in Angie plugin … … 171 177 172 178 = 1.0.3 - 2026-01-12 = 173 * Tweak: Added support for “Elementor One”connection179 * Tweak: Added support for "Elementor One" connection 174 180 * Tweak: Added Canvas page template to support Landing page creation in Gutenberg 175 181 * Tweak: Added `animate.css` script for Gutenberg generated landing pages
Note: See TracChangeset for help on using the changeset viewer.