├── storage └── cache │ ├── .cache │ └── index.php ├── index.php ├── src ├── index.php ├── Admin │ ├── index.php │ ├── Dashboard.php │ └── Admin.php ├── Helpers │ ├── index.php │ └── blade.php ├── Controllers │ ├── index.php │ └── ExampleController.php ├── Desactivate.php ├── Init.php ├── Users.php ├── helpers.php ├── Start.php ├── Widgets.php ├── Uninstall.php ├── Shortcodes.php ├── Gutenberg.php ├── Language.php ├── Api.php ├── Request.php ├── Security.php ├── Hooks.php ├── Config.php ├── Install.php └── PostTypes.php ├── assets ├── js │ └── index.php ├── img │ ├── index.php │ └── antonella-icon.png └── css │ └── index.php ├── languages ├── index.php └── antonella.pot ├── resources └── views │ ├── index.php │ └── index.blade.php ├── .gitmodules ├── .gitignore ├── .gitattributes ├── docker ├── entrypoint.sh ├── Dockerfile.wordpress └── init-wordpress.sh ├── .phpcs.xml ├── antonella-framework.php ├── .dockerignore ├── LICENSE.md ├── composer.json ├── docker-compose.yaml ├── PROBLEMAS_DE_INSTALACION.md ├── readme.txt ├── SECURITY.md ├── readme.md └── antonella /storage/cache/.cache: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | es verdad 4 | @else 5 |
es mentira
6 | @endif -------------------------------------------------------------------------------- /assets/img/antonella-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cehojac/antonella-framework-for-wp/HEAD/assets/img/antonella-icon.png -------------------------------------------------------------------------------- /src/Desactivate.php: -------------------------------------------------------------------------------- 1 | user = wp_get_current_user(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "customizer-library"] 2 | path = customizer-library 3 | url = git@github.com:devinsays/customizer-library 4 | [submodule "admin/customizer-library"] 5 | path = admin/customizer-library 6 | url = git@github.com:devinsays/customizer-library 7 | -------------------------------------------------------------------------------- /src/helpers.php: -------------------------------------------------------------------------------- 1 | render($BladePage, $Attributes); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # https://git-scm.com/docs/gitignore 2 | # https://help.github.com/articles/ignoring-files 3 | # Example .gitignore files: https://github.com/github/gitignore 4 | /bower_components/ 5 | /node_modules/ 6 | vendor/* 7 | composer.lock 8 | package.lock 9 | .env 10 | wp-test/* 11 | wp-cli.phar 12 | antonella-framework-for-wp.zip 13 | db_data 14 | wordpress 15 | antonella.backup 16 | test 17 | antonella2 18 | .claude 19 | .vscode 20 | .idea 21 | *.log 22 | *.swp -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | *.sh text eol=lf -------------------------------------------------------------------------------- /src/Start.php: -------------------------------------------------------------------------------- 1 | widgets; 17 | // add_shortcode('example', array('Shortcodes','example_function')); 18 | if (is_array($filter) && count($filter) > 0) { 19 | foreach ($filter as $data) { 20 | \register_widget($data); 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Uninstall.php: -------------------------------------------------------------------------------- 1 | delete_options($config->plugin_options); 17 | } 18 | 19 | public function delete_options($options) 20 | { 21 | foreach ($options as $key => $option) { 22 | if (get_option($key) != false) { 23 | delete_option($key); 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /docker/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | echo "🚀 Iniciando contenedor de WordPress..." 5 | 6 | # Ejecutar el entrypoint original de WordPress 7 | docker-entrypoint.sh "$@" & 8 | 9 | # Obtener el PID del proceso de WordPress 10 | WORDPRESS_PID=$! 11 | 12 | echo "⏳ Esperando a que WordPress se inicie..." 13 | # Esperar más tiempo para que WordPress se inicie completamente 14 | sleep 30 15 | 16 | echo "🔧 Ejecutando script de inicialización de WordPress..." 17 | # Ejecutar la inicialización en segundo plano con logging 18 | /docker-scripts/init-wordpress.sh 2>&1 | tee /tmp/init-wordpress.log & 19 | 20 | # Esperar a que WordPress termine 21 | wait $WORDPRESS_PID 22 | -------------------------------------------------------------------------------- /.phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ./src 5 | 6 | 7 | 8 | 9 | ./src 10 | 11 | 12 | 13 | 14 | 15 | 16 | ./src 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /languages/antonella.pot: -------------------------------------------------------------------------------- 1 | #, fuzzy 2 | msgid "" 3 | msgstr "" 4 | "Project-Id-Version: Antonella Framework\n" 5 | "Report-Msgid-Bugs-To: \n" 6 | "POT-Creation-Date: 2020-08-04 15:26+0000\n" 7 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 8 | "Last-Translator: FULL NAME \n" 9 | "Language-Team: \n" 10 | "Language: \n" 11 | "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" 12 | "MIME-Version: 1.0\n" 13 | "Content-Type: text/plain; charset=UTF-8\n" 14 | "Content-Transfer-Encoding: 8bit\n" 15 | "X-Generator: Loco https://localise.biz/\n" 16 | "X-Loco-Version: 2.4.0; wp-5.4.2" 17 | 18 | #. Description of the plugin 19 | msgid "Another plugin developed on Antonella Framework for WP" 20 | msgstr "" 21 | 22 | #. Author of the plugin 23 | msgid "Carlos Herrera" 24 | msgstr "" 25 | 26 | #. Author URI of the plugin 27 | msgid "https://carlos-herrera.com" 28 | msgstr "" 29 | 30 | #. Name of the plugin 31 | msgid "Antonella Framework" 32 | msgstr "" 33 | 34 | -------------------------------------------------------------------------------- /src/Shortcodes.php: -------------------------------------------------------------------------------- 1 | shortcodes; 17 | // add_shortcode('example', array('Shortcodes','example_function')); 18 | if ($filter) { 19 | foreach ($filter as $data) { 20 | call_user_func_array('add_shortcode', [$data[0],$data[1]]); 21 | } 22 | } 23 | } 24 | /* 25 | * shortcode example 26 | * @info: https://codex.wordpress.org/Shortcode 27 | * @return string 28 | */ 29 | public function example_function($atts) 30 | { 31 | extract( 32 | shortcode_atts( 33 | array( 34 | 'data1' => 1, 35 | 'data2' => 1 36 | ), 37 | $atts 38 | ) 39 | ); 40 | return ("
$content
"); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Gutenberg.php: -------------------------------------------------------------------------------- 1 | gutenberg_blocks; 21 | 22 | foreach ($blocks as $block) { 23 | \wp_register_script( 24 | $block, 25 | \plugin_dir_url(__DIR__) . 'assets/js/' . $block . '.js', 26 | ['wp-blocks', 'wp-i18n', 'wp-element', 'wp-components', 'wp-editor' ], 27 | '1.0.0', 28 | true 29 | ); 30 | \wp_enqueue_style( 31 | $block . '-css', 32 | \plugin_dir_url(__DIR__) . 'assets/css/' . $block . '.css', 33 | [ 'wp-edit-blocks' ], 34 | '1.0.0', 35 | true 36 | ); 37 | } 38 | \wp_enqueue_script($blocks); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /antonella-framework.php: -------------------------------------------------------------------------------- 1 |

Antonella Framework: Dependencies missing. Please run composer install in the plugin directory.

'; 27 | }); 28 | return; 29 | } 30 | 31 | 32 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # ============================================================================= 2 | # Docker Build Context Optimization 3 | # Excluye archivos innecesarios para mejorar el rendimiento del build 4 | # ============================================================================= 5 | 6 | # Control de versiones 7 | .git/ 8 | .gitignore 9 | .gitattributes 10 | .gitmodules 11 | 12 | # Archivos de desarrollo y configuración 13 | .env* 14 | .vscode/ 15 | .idea/ 16 | .claude/ 17 | *.log 18 | 19 | # Dependencias y builds 20 | node_modules/ 21 | bower_components/ 22 | vendor/ 23 | composer.lock 24 | package-lock.json 25 | 26 | # Archivos temporales y cache 27 | storage/cache/ 28 | storage/logs/ 29 | tmp/ 30 | *.tmp 31 | *.swp 32 | *.swo 33 | *~ 34 | 35 | # Archivos de backup y tests 36 | *.backup 37 | test/ 38 | wp-test/ 39 | db_data/ 40 | wordpress/ 41 | 42 | # Archivos comprimidos y releases 43 | *.zip 44 | *.tar.gz 45 | *.rar 46 | antonella-framework-for-wp.zip 47 | 48 | # Archivos del sistema 49 | .DS_Store 50 | Thumbs.db 51 | 52 | # Documentación de desarrollo (mantener solo esenciales) 53 | PROBLEMAS_DE_INSTALACION.md 54 | 55 | # CLI tools 56 | wp-cli.phar 57 | antonella 58 | antonella2 -------------------------------------------------------------------------------- /src/Admin/Dashboard.php: -------------------------------------------------------------------------------- 1 | array_dashboard= $config->dashboard; 14 | $this->files_dashboard= $config->files_dashboard; 15 | } 16 | 17 | public static function index() 18 | { 19 | $dashboard= new Dashboard; 20 | $datas=$dashboard->array_dashboard; 21 | if (count($datas)>0&&$datas[0]['name']<>'') 22 | { 23 | foreach($datas as $data) 24 | { 25 | wp_add_dashboard_widget($data['slug'],$data['name'],$data['function'],$data['callback'],$data['args']); 26 | } 27 | } 28 | } 29 | 30 | public function scripts($hook) 31 | { 32 | 33 | if( 'index.php' != $hook ) { 34 | return; 35 | } 36 | $datas=$this->files_dashboard; 37 | foreach($datas as $data) 38 | { 39 | wp_enqueue_style( 'dashboard-widget-styles', plugins_url( '', __FILE__ ) . $data, array(), '1.0.0' ); 40 | } 41 | 42 | } 43 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 CARLOS HERRERA 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | **RELATIONSHIP WITH WORDPRESS AND GPL** 24 | Antonella Framework is licensed under the MIT license. However, any plugin 25 | created using Antonella Framework and distributed for use within WordPress 26 | **must comply with the GPL license**, in accordance with WordPress requirements 27 | and the philosophy of free software. 28 | 29 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"cehojac/antonella-framework-for-wp", 3 | "version":"1.9.0", 4 | "keywords": ["framework", "wordpress", "plugins"], 5 | "prefer-stable": true, 6 | "minimum-stability": "dev", 7 | "description":"make easy a WordPress Plugin whit a team. Use this framework for simplycity the work in develop a plugin", 8 | "type":"project", 9 | "authors": [ 10 | { 11 | "name": "Carlos Herrera", 12 | "email": "hi@carlos-herrera.com", 13 | "homepage": "https://carlos-herrera.com", 14 | "role": "Developer" 15 | } 16 | ], 17 | "support": { 18 | "email": "antonella.framework@carlos-herrera.com", 19 | "docs" : "https://antonellaframework.com/en/documentacion" 20 | }, 21 | "repositories":[ 22 | { 23 | "type":"composer", 24 | "url":"https://wpackagist.org" 25 | } 26 | ], 27 | "license": "MIT", 28 | "require":{ 29 | "php": ">=8.0.0" 30 | }, 31 | "require-dev":{ 32 | }, 33 | "autoload": { 34 | "psr-4": {"CH\\": "src/"}, 35 | "files": [ 36 | "src/helpers.php" 37 | ] 38 | }, 39 | "extra": { 40 | "installer-paths": { 41 | "vendor/{$name}/": ["type:wordpress-plugin","wordpress-muplugin"] 42 | } 43 | }, 44 | "scripts":{ 45 | "post-create-project-cmd": [ 46 | "php antonella namespace", 47 | "php antonella updateproject" 48 | ] 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Language.php: -------------------------------------------------------------------------------- 1 | config = new Config(); 16 | $this->textdomain = $this->config->language_name; 17 | $this->languages_path = dirname(plugin_basename(__DIR__)) . '/languages/'; 18 | } 19 | 20 | /** 21 | * Initialize translations using modern WordPress approach 22 | * WordPress 4.6+ automatically loads translations for plugins when available 23 | */ 24 | public static function init_translations() 25 | { 26 | // Set up locale and domain path for WordPress auto-loading 27 | // This ensures WordPress can find and load translations automatically 28 | self::setup_translation_environment(); 29 | } 30 | 31 | /** 32 | * Setup translation environment for WordPress auto-loading 33 | * This method prepares the environment without using deprecated functions 34 | */ 35 | private static function setup_translation_environment() 36 | { 37 | // Ensure the languages directory exists 38 | $full_path = plugin_dir_path(__DIR__) . 'languages'; 39 | if (!file_exists($full_path)) { 40 | wp_mkdir_p($full_path); 41 | } 42 | 43 | // WordPress will automatically load translations from: 44 | // 1. WP_LANG_DIR/plugins/{textdomain}-{locale}.mo 45 | // 2. Plugin languages directory 46 | // No manual loading needed for modern WordPress versions 47 | } 48 | 49 | /** 50 | * Get the current textdomain 51 | * @return string 52 | */ 53 | public function get_textdomain() 54 | { 55 | return $this->textdomain; 56 | } 57 | 58 | /** 59 | * Get the languages directory path 60 | * @return string 61 | */ 62 | public function get_languages_path() 63 | { 64 | return $this->languages_path; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /docker/Dockerfile.wordpress: -------------------------------------------------------------------------------- 1 | # ============================================================================= 2 | # Antonella Framework - WordPress Development Container 3 | # 4 | # Builds a WordPress environment with: 5 | # - WP-CLI for automation 6 | # - Custom initialization scripts 7 | # - Development tools and dependencies 8 | # 9 | # Compatible with: linux/amd64, linux/arm64, windows 10 | # ============================================================================= 11 | 12 | FROM wordpress:latest 13 | 14 | # ============================================================================= 15 | # Install system dependencies and development tools 16 | # ============================================================================= 17 | RUN apt-get update && apt-get install -y \ 18 | curl \ 19 | less \ 20 | default-mysql-client \ 21 | && rm -rf /var/lib/apt/lists/* 22 | 23 | # ============================================================================= 24 | # Install WP-CLI for WordPress automation 25 | # ============================================================================= 26 | RUN curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar \ 27 | && chmod +x wp-cli.phar \ 28 | && mv wp-cli.phar /usr/local/bin/wp 29 | 30 | # ============================================================================= 31 | # Setup custom scripts directory and initialization 32 | # ============================================================================= 33 | RUN mkdir -p /docker-scripts 34 | 35 | # Copiar script de inicialización 36 | COPY docker/init-wordpress.sh /docker-scripts/ 37 | RUN chmod +x /docker-scripts/init-wordpress.sh 38 | 39 | # Configurar entrypoint personalizado 40 | COPY docker/entrypoint.sh /docker-scripts/ 41 | RUN chmod +x /docker-scripts/entrypoint.sh 42 | 43 | # ============================================================================= 44 | # Configure container startup 45 | # ============================================================================= 46 | 47 | ENTRYPOINT ["/docker-scripts/entrypoint.sh"] 48 | CMD ["apache2-foreground"] 49 | -------------------------------------------------------------------------------- /src/Api.php: -------------------------------------------------------------------------------- 1 | api_endpoints_functions; 21 | 22 | if (!empty($endpoints)) { 23 | self::register_endpoints($config, $endpoints); 24 | } 25 | } 26 | 27 | /** 28 | * Register REST API endpoints 29 | * @param Config $config Framework configuration 30 | * @param array $endpoints Array of endpoint configurations 31 | */ 32 | private static function register_endpoints($config, $endpoints) 33 | { 34 | $namespace = $config->api_endpoint_name . '/v' . $config->api_endpoint_version; 35 | 36 | foreach ($endpoints as $endpoint) { 37 | if (self::is_valid_endpoint($endpoint)) { 38 | \register_rest_route( 39 | $namespace, 40 | '/' . $endpoint[0] . '/(?P\d+)', 41 | [ 42 | 'methods' => $endpoint[1], 43 | 'callback' => $endpoint[2], 44 | 'permission_callback' => '__return_true', // Override in your callbacks 45 | ] 46 | ); 47 | } 48 | } 49 | } 50 | 51 | /** 52 | * Validate endpoint configuration 53 | * @param array $endpoint Endpoint configuration array 54 | * @return bool True if valid, false otherwise 55 | */ 56 | private static function is_valid_endpoint($endpoint) 57 | { 58 | return isset($endpoint[0], $endpoint[1], $endpoint[2]) && 59 | !empty($endpoint[0]) && 60 | !empty($endpoint[1]) && 61 | is_callable($endpoint[2]); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Request.php: -------------------------------------------------------------------------------- 1 | process($config->post); 24 | $this->process($config->get); 25 | } 26 | 27 | /** 28 | * Verify nonce for security 29 | * @param $nonce_name string The name of the nonce field 30 | * @param $action string The action name for the nonce 31 | * @return void 32 | */ 33 | public function verify_nonce($nonce_name, $action) 34 | { 35 | // Sanitize and unslash POST data before verification 36 | $nonce_value = isset($_POST[$nonce_name]) ? sanitize_text_field(wp_unslash($_POST[$nonce_name])) : ''; 37 | 38 | if (empty($nonce_value) || !wp_verify_nonce($nonce_value, $action)) { 39 | die(esc_html(__('Security check failed', 'antonella-framework'))); 40 | } 41 | } 42 | 43 | /** 44 | * process function 45 | * process the request input (POST and GET) 46 | * @param [type] $datas the config array (post and get) 47 | * @return void 48 | */ 49 | public function process($datas) 50 | { 51 | require_once(ABSPATH . 'wp-includes/pluggable.php'); 52 | 53 | // Verify nonce for security when processing form data 54 | foreach ($datas as $key => $data) { 55 | // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verification handled by calling code or not required for GET requests 56 | if (isset($_REQUEST[$key])) { 57 | // Sanitize and unslash request data 58 | // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verification handled by calling code or not required for GET requests 59 | $sanitized_value = sanitize_text_field(wp_unslash($_REQUEST[$key])); 60 | call_user_func_array($data, [$sanitized_value]); 61 | } else { 62 | continue; 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Admin/Admin.php: -------------------------------------------------------------------------------- 1 | plugin_prefix = $config->plugin_prefix; 17 | $this->plugin_menu = $config->plugin_menu; 18 | } 19 | /* 20 | * Admin Menu Page 21 | * 22 | */ 23 | public static function menu() 24 | { 25 | $admin = new Admin(); 26 | $admin->menu_generator($admin->plugin_menu); 27 | } 28 | 29 | /** 30 | * Backend Menu Generator 31 | * @param array Config::config_menu 32 | * @ver 1.0 33 | */ 34 | public function menu_generator($params) 35 | { 36 | foreach ($params as $param) { 37 | if ($param['path'][0] == 'page') { 38 | $icon = $param['icon'] ? $param['icon'] : 'antonella-icon.png'; 39 | add_menu_page($param['name'], $param['name'], 'manage_options', $param['slug'], $param['function'], plugins_url('../../assets/img/' . $icon, __FILE__)); 40 | if (isset($param['subpages'])) { 41 | foreach ($param['subpages'] as $subpage) { 42 | add_submenu_page( 43 | $param['slug'], 44 | $subpage['name'], 45 | $subpage['name'], 46 | 'manage_options', 47 | $subpage['slug'], 48 | $subpage['function'] 49 | ); 50 | } 51 | } 52 | } elseif ($param['path'][0] == 'subpage') { 53 | add_submenu_page( 54 | $param['path'][1], 55 | $param['name'], 56 | $param['name'], 57 | 'manage_options', 58 | $param['slug'], 59 | $param['function'] 60 | ); 61 | } elseif ($param['path'][0] == 'option') { 62 | add_options_page($param['name'], $param['name'], 'manage_options', $param['slug'], $param['function']); 63 | } 64 | } 65 | } 66 | 67 | public function option_page() 68 | { 69 | // Check user capabilities 70 | Security::check_user_capability('manage_options'); 71 | 72 | return ('Hello World !!'); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Security.php: -------------------------------------------------------------------------------- 1 | registrer(); 20 | $this->filter($config->add_filter); 21 | $this->action($config->add_action); 22 | } 23 | 24 | /** 25 | * Register plugin lifecycle hooks (activation, deactivation, uninstall) 26 | * @see Documentation: docs/configuration/hooks-filters.md for details 27 | */ 28 | public function registrer() 29 | { 30 | $plugin_file = dirname(__DIR__) . '/antonella-framework.php'; 31 | register_activation_hook($plugin_file, array(__NAMESPACE__ . '\Install', 'index')); 32 | register_deactivation_hook($plugin_file, array(__NAMESPACE__ . '\Desactivate', 'index')); 33 | register_uninstall_hook($plugin_file, __NAMESPACE__ . '\Uninstall::index'); 34 | } 35 | /** 36 | * Register WordPress filters from configuration 37 | * @see Documentation: docs/configuration/hooks-filters.md for examples 38 | * @param array $filter Array of filter configurations from Config.php 39 | */ 40 | public function filter($filter) 41 | { 42 | if (isset($filter)) { 43 | foreach ($filter as $data) { 44 | call_user_func_array( 45 | 'add_filter', 46 | [ 47 | isset($data[0]) ? $data[0] : null, 48 | isset($data[1]) ? $data[1] : null, 49 | isset($data[2]) ? $data[2] : null, 50 | isset($data[3]) ? $data[3] : null, 51 | ] 52 | ); 53 | } 54 | } 55 | } 56 | /** 57 | * Register WordPress actions (framework core + configuration) 58 | * @see Documentation: docs/configuration/hooks-filters.md for examples 59 | * @param array $action Array of action configurations from Config.php 60 | */ 61 | public function action($action) 62 | { 63 | // Register framework core actions 64 | $this->register_core_actions(); 65 | 66 | // Register dynamic actions from configuration 67 | $this->register_dynamic_actions($action); 68 | } 69 | 70 | /** 71 | * Register core framework actions 72 | */ 73 | private function register_core_actions() 74 | { 75 | // Admin functionality 76 | \add_action('admin_menu', array(__NAMESPACE__ . '\Admin\Admin', 'menu')); 77 | \add_action('wp_dashboard_setup', array(__NAMESPACE__ . '\Admin\Dashboard', 'index')); 78 | 79 | // Core initialization 80 | \add_action('init', array(__NAMESPACE__ . '\Init', 'index'), 0); 81 | 82 | // Translations and internationalization 83 | \add_action('plugins_loaded', array(__NAMESPACE__ . '\Language', 'init_translations'), 10); 84 | 85 | // API and REST endpoints 86 | \add_action('rest_api_init', array(__NAMESPACE__ . '\Api', 'index'), 1); 87 | 88 | // Content types and features 89 | \add_action('init', array(__NAMESPACE__ . '\Shortcodes', 'index'), 1); 90 | \add_action('init', array(__NAMESPACE__ . '\PostTypes', 'index'), 1); 91 | \add_action('widgets_init', array(__NAMESPACE__ . '\Widgets', 'index'), 1); 92 | 93 | // Gutenberg blocks 94 | \add_action('enqueue_block_editor_assets', array(__NAMESPACE__ . '\Gutenberg', 'blocks'), 1, 10); 95 | } 96 | 97 | /** 98 | * Register dynamic actions from configuration 99 | * @param array $action Array of action configurations 100 | */ 101 | private function register_dynamic_actions($action) 102 | { 103 | if ($action) { 104 | foreach ($action as $data) { 105 | if (isset($data)) { 106 | call_user_func_array( 107 | 'add_action', 108 | [ 109 | isset($data[0]) ? $data[0] : null, 110 | isset($data[1]) ? $data[1] : null, 111 | isset($data[2]) ? $data[2] : null, 112 | isset($data[3]) ? $data[3] : null, 113 | ] 114 | ); 115 | } 116 | } 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/Config.php: -------------------------------------------------------------------------------- 1 | 'default value') 11 | * @example ["example_data" => 'foo',] 12 | * @return void 13 | */ 14 | public $plugin_options = []; 15 | /** 16 | * Language Option 17 | * define a unique word for translate call 18 | */ 19 | public $language_name = 'antonella'; 20 | /** 21 | * Plugin text prefix 22 | * define a unique word for this plugin 23 | */ 24 | public $plugin_prefix = 'ch_nella'; 25 | /** 26 | * POST data process 27 | * get the post data and execute the function 28 | * @example ['post_data'=>'CH::function'] 29 | */ 30 | public $post = [ 31 | 'submit_antonella_config' => __NAMESPACE__ . '\Controllers\ExampleController::process_form', 32 | ]; 33 | /** 34 | * GET data process 35 | * get the get data and execute the function 36 | * @example ['get_data'=>'CH::function'] 37 | */ 38 | public $get = [ 39 | ]; 40 | /** 41 | * add_filter data functions 42 | * @input array 43 | * @example ['body_class','CH::function',10,2] 44 | * @example ['body_class',[__NAMESPACE__.'\ExampleController,'function'],10,2] 45 | */ 46 | public $add_filter = [ 47 | ]; 48 | /** 49 | * add_action data functions 50 | * @input array 51 | * @example ['body_class','CH::function',10,2] 52 | * @example ['body_class',[__NAMESPACE__.'\ExampleController','function'],10,2] 53 | */ 54 | public $add_action = [ 55 | ]; 56 | /** 57 | * Custom shortcodes 58 | * Add custom shortcodes for your plugin 59 | * @example [['example', __NAMESPACE__.\ExampleController::example_shortcode']] 60 | */ 61 | public $shortcodes = [ 62 | // Add your custom shortcodes here 63 | ]; 64 | 65 | /** 66 | * REST API Endpoints 67 | * Add custom REST API endpoints for your plugin 68 | * @example [['name', 'GET', __NAMESPACE__.\ApiController::index']] 69 | * @example Route: /wp-json/my-plugin-endpoint/v1/name 70 | */ 71 | public $api_endpoint_name = 'my-plugin-endpoint'; 72 | public $api_endpoint_version = 1; 73 | public $api_endpoints_functions = [ 74 | // Add your custom API endpoints here 75 | ]; 76 | 77 | /** 78 | * Gutenberg Blocks 79 | * Add custom Gutenberg blocks for your plugin 80 | */ 81 | public $gutenberg_blocks = [ 82 | // Add your custom Gutenberg blocks here 83 | ]; 84 | /** 85 | * Dashboard 86 | 87 | * @reference: https://codex.wordpress.org/Function_Reference/wp_add_dashboard_widget 88 | */ 89 | public $dashboard = [ 90 | [ 91 | 'slug' => '', 92 | 'name' => '', 93 | 'function' => '', // example: __NAMESPACE__.'\Admin\PageAdmin::DashboardExample', 94 | 'callback' => '', 95 | 'args' => '', 96 | ] 97 | 98 | ]; 99 | /** 100 | * Files for use in Dashboard 101 | */ 102 | public $files_dashboard = []; 103 | 104 | /** 105 | * Plugin menu 106 | * Set your menu option here 107 | * @see Documentation: docs/configuration/plugin-menu.md for examples 108 | */ 109 | public $plugin_menu = [ 110 | [ 111 | 'path' => ['page'], 112 | 'name' => 'Hello World', 113 | 'function' => __NAMESPACE__ . "\Controllers\ExampleController::adminPage", 114 | 'icon' => 'antonella-icon.png', 115 | 'slug' => 'antonella-example', 116 | ] 117 | 118 | // Add your custom admin pages here 119 | ]; 120 | 121 | /** 122 | * Custom Post Type 123 | * For creating custom post types in WordPress 124 | * @see Documentation: docs/configuration/custom-post-types.md for examples 125 | */ 126 | public $post_types = [ 127 | // Add your custom post types here 128 | ]; 129 | 130 | /** 131 | * Taxonomies 132 | * For creating custom taxonomies for your post types 133 | * @see Documentation: docs/configuration/taxonomies.md for examples 134 | */ 135 | public $taxonomies = [ 136 | // Add your custom taxonomies here 137 | ]; 138 | 139 | /** 140 | * Widget 141 | * For register a Widget please: 142 | * Console: php antonella Widget "NAME_OF_WIDGET" 143 | * @input array 144 | * @example public $widget = [__NAMESPACE__.'\YouClassWidget'] //only the class 145 | */ 146 | public $widgets = []; 147 | } 148 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | # ============================================================================= 2 | # Antonella Framework for WordPress - Docker Compose Configuration 3 | # 4 | # Entorno de desarrollo completo con: 5 | # - MySQL 8.0 con healthchecks 6 | # - WordPress con framework preconfigurado 7 | # - phpMyAdmin para gestión de BD 8 | # - WP-CLI para automatización 9 | # 10 | # Requisitos: 11 | # - Docker Desktop 4.53.0+ (compatibilidad ARM64/Windows) 12 | # - Docker Compose v2+ 13 | # 14 | # Uso: docker compose up -d 15 | # ============================================================================= 16 | 17 | services: 18 | # ============================================================================= 19 | # Base de datos MySQL 8.0 20 | # Configurada con healthchecks para dependencias seguras 21 | # ============================================================================= 22 | mysql: 23 | container_name: mysql-antonella 24 | image: mysql:8.0 25 | restart: unless-stopped 26 | ports: 27 | - "3306:3306" 28 | environment: 29 | MYSQL_ROOT_PASSWORD: wordpress 30 | MYSQL_DATABASE: wordpress 31 | MYSQL_USER: wordpress 32 | MYSQL_PASSWORD: wordpress 33 | volumes: 34 | - mysql_data:/var/lib/mysql 35 | command: --default-authentication-plugin=mysql_native_password 36 | healthcheck: 37 | test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "wordpress", "-pwordpress"] 38 | interval: 5s 39 | timeout: 10s 40 | retries: 20 41 | start_period: 30s 42 | 43 | # ============================================================================= 44 | # phpMyAdmin - Gestión visual de base de datos 45 | # Acceso: http://localhost:9000 (cambiar puerto si está ocupado) 46 | # ============================================================================= 47 | phpmyadmin: 48 | image: phpmyadmin/phpmyadmin:latest 49 | container_name: phpmyadmin-antonella 50 | restart: unless-stopped 51 | environment: 52 | PMA_HOST: mysql 53 | PMA_USER: wordpress 54 | PMA_PASSWORD: wordpress 55 | UPLOAD_LIMIT: 1G 56 | depends_on: 57 | mysql: 58 | condition: service_healthy 59 | ports: 60 | - "9000:80" 61 | 62 | # ============================================================================= 63 | # WordPress con Antonella Framework 64 | # Configuración automática de desarrollo con inicialización completa 65 | # Acceso: http://localhost:8080 66 | # Admin: http://localhost:8080/wp-admin (test/test) 67 | # ============================================================================= 68 | wordpress: 69 | build: 70 | context: . 71 | dockerfile: docker/Dockerfile.wordpress 72 | container_name: wp-antonella 73 | hostname: antonella.test 74 | restart: unless-stopped 75 | ports: 76 | - "8080:80" 77 | depends_on: 78 | mysql: 79 | condition: service_healthy 80 | volumes: 81 | # Montar el framework como plugin 82 | - ./:/var/www/html/wp-content/plugins/antonella-framework 83 | # Persistir uploads y contenido 84 | - wordpress_data:/var/www/html 85 | # Logs de WordPress 86 | - ./storage/logs:/var/www/html/wp-content/debug.log 87 | environment: 88 | # Configuración de base de datos 89 | WORDPRESS_DB_HOST: mysql:3306 90 | WORDPRESS_DB_USER: wordpress 91 | WORDPRESS_DB_PASSWORD: wordpress 92 | WORDPRESS_DB_NAME: wordpress 93 | WORDPRESS_TABLE_PREFIX: anto_ 94 | 95 | # Configuración adicional de WordPress 96 | WORDPRESS_CONFIG_EXTRA: | 97 | // Configuración del sitio 98 | define('WP_HOME', 'http://localhost:8080'); 99 | define('WP_SITEURL', 'http://localhost:8080'); 100 | define('DOMAIN_CURRENT_SITE', 'antonella.test'); 101 | 102 | // Configuración de desarrollo 103 | define('WP_DEBUG', true); 104 | define('WP_DEBUG_LOG', true); 105 | define('WP_DEBUG_DISPLAY', false); 106 | define('SCRIPT_DEBUG', true); 107 | define('SAVEQUERIES', true); 108 | 109 | // Configuración de memoria y tiempo 110 | define('WP_MEMORY_LIMIT', '512M'); 111 | ini_set('max_execution_time', 300); 112 | 113 | // Configuración de archivos 114 | define('AUTOMATIC_UPDATER_DISABLED', true); 115 | define('WP_AUTO_UPDATE_CORE', false); 116 | define('DISALLOW_FILE_EDIT', false); 117 | define('DISALLOW_FILE_MODS', false); 118 | 119 | // Configuración de cache (deshabilitado en desarrollo) 120 | define('WP_CACHE', false); 121 | 122 | // Configuración de revisiones 123 | define('WP_POST_REVISIONS', 5); 124 | 125 | // Configuración de papelera 126 | define('EMPTY_TRASH_DAYS', 7); 127 | 128 | # ============================================================================= 129 | # WP-CLI Container - Automatización y comandos WordPress 130 | # Uso: docker compose exec wpcli wp 131 | # ============================================================================= 132 | wpcli: 133 | build: 134 | context: . 135 | dockerfile: docker/Dockerfile.wordpress 136 | container_name: wpcli-antonella 137 | volumes: 138 | - ./:/var/www/html/wp-content/plugins/antonella-framework 139 | - wordpress_data:/var/www/html 140 | depends_on: 141 | wordpress: 142 | condition: service_started 143 | environment: 144 | WORDPRESS_DB_HOST: mysql:3306 145 | WORDPRESS_DB_USER: wordpress 146 | WORDPRESS_DB_PASSWORD: wordpress 147 | WORDPRESS_DB_NAME: wordpress 148 | command: tail -f /dev/null # Mantener el contenedor activo 149 | 150 | # ============================================================================= 151 | # Volúmenes persistentes 152 | # Mantienen datos entre reinicios de contenedores 153 | # ============================================================================= 154 | volumes: 155 | mysql_data: 156 | driver: local 157 | # Datos de MySQL (base de datos, configuraciones) 158 | wordpress_data: 159 | driver: local 160 | # Instalación completa de WordPress (core, themes, uploads) 161 | 162 | # ============================================================================= 163 | # Red personalizada 164 | # Permite comunicación entre servicios con nombres de host 165 | # ============================================================================= 166 | networks: 167 | default: 168 | name: antonella-network -------------------------------------------------------------------------------- /PROBLEMAS_DE_INSTALACION.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Reporte de Error: Falla en la Instalación del Framework en Fedora con Docker" 3 | id: 20251020-01 4 | date: 2025-10-20 5 | author: Miguel BC 6 | --- 7 | 8 | # Informe técnico: problemas de instalación en Fedora (Docker) 9 | 10 | ## Resumen ejecutivo 11 | 12 | El instalador automático del framework (Composer + scripts PHP que levantan un entorno Docker y ejecutan WP-CLI) no completa la instalación en Fedora. Se identificaron varios problemas encadenados: conflictos de dependencias globales de Composer, puertos ocupados en Docker, políticas SELinux que bloquean el acceso a volúmenes montados y un bug en el script que renombra el namespace del plugin. 13 | 14 | ## Metadatos 15 | 16 | - **ID del reporte:** `20251020-01` 17 | - **Fecha:** 20 de octubre de 2025 18 | - **Autor:** Miguel BC 19 | - **Framework:** Antonella Framework v1.9.3 20 | - **Entorno:** Fedora Linux (SELinux enforcing), Docker, PHP 8+, Composer, WP-CLI 21 | 22 | ## 1. Entorno de pruebas 23 | 24 | - Sistema operativo: Fedora Linux (SELinux en modo enforcing) 25 | - Contenedores: Docker (docker-compose) 26 | - Herramientas: PHP 8+, Composer, WP-CLI 27 | 28 | ## 2. Resumen de problemas encontrados 29 | 30 | 1. Conflicto de dependencias al instalar Composer globalmente (`symfony/console`). 31 | 2. Conflicto de puertos en Docker (por ejemplo, `9000` ya ocupado). 32 | 3. El script automático se queda en "Esperando a que MySQL esté disponible..." y no completa la instalación de WordPress. 33 | 4. SELinux impide que los procesos del contenedor lean archivos montados desde el host (plugin no detectado). 34 | 5. Inconsistencia de namespace provocada por `php antonella namespace` (autoloader roto, error fatal de PHP). 35 | 36 | ## 3. Pasos para reproducir 37 | 38 | 1. Instalar prerrequisitos en Fedora: PHP, Composer, Docker. 39 | 2. Crear proyecto: 40 | 41 | ```bash 42 | composer create-project --prefer-dist cehojac/antonella-framework-for-wp:dev-master mi-plugin-de-prueba 43 | ``` 44 | 45 | 3. Entrar al directorio del proyecto y ejecutar el instalador: 46 | 47 | ```bash 48 | cd mi-plugin-de-prueba 49 | php antonella serve 50 | ``` 51 | 52 | 4. Observar los logs: el proceso se detiene en "Esperando a que MySQL esté disponible...". 53 | 5. Si se fuerza una instalación manual desde el navegador, puede que el plugin no aparezca en la lista de plugins (debido a SELinux o a errores de namespace). 54 | 55 | ## 4. Comportamiento esperado 56 | 57 | Tras `php antonella serve`: 58 | 59 | - Contenedores levantados. 60 | - WordPress instalado y configurado automáticamente. 61 | - Namespace del plugin renombrado de forma consistente en `composer.json` y `src/*.php`. 62 | - Plugin `antonella-framework` activado automáticamente. 63 | 64 | ## 5. Comportamiento observado (detallado) 65 | 66 | ### 5.1 Conflicto Composer global 67 | 68 | - Problema: `composer global require` falló por conflicto de versiones de `symfony/console`. 69 | - Solución aplicada: usar `composer create-project` para aislar dependencias al proyecto. 70 | 71 | ### 5.2 Conflicto de puertos Docker 72 | 73 | - Problema: error `Bind for 0.0.0.0:9000 failed: port is already allocated` al iniciar `docker compose up`. 74 | - Solución: reasignar el puerto del servicio problemático en `docker-compose.yaml`, por ejemplo: 75 | 76 | ```yaml 77 | services: 78 | phpmyadmin: 79 | ports: 80 | - "9002:80" # 9000 -> 9002 81 | ``` 82 | 83 | ### 5.3 Falla del script de instalación automática (WP-CLI) 84 | 85 | - Problema: el script se queda esperando a MySQL y no finaliza la instalación. 86 | - Solución: instalación manual controlada usando WP-CLI dentro del contenedor. 87 | 88 | Pasos realizados (ejemplo): 89 | 90 | ```bash 91 | # Levantar servicios en background 92 | docker compose up -d 93 | 94 | # Acceder al contenedor wpcli 95 | docker compose exec wpcli bash 96 | 97 | # Desde dentro: instalar WP y activar plugins 98 | wp core install --url="http://localhost:8080" --title="Sitio de Prueba" --admin_user=admin --admin_password=pass --admin_email=you@example.com --allow-root 99 | wp plugin activate antonella-framework --allow-root 100 | ``` 101 | 102 | ### 5.4 SELinux: plugin no detectado 103 | 104 | - Problema: el volumen montado no tenía el contexto requerido para que los procesos dentro del contenedor accedieran a los archivos. 105 | - Solución aplicada en host (Fedora): 106 | 107 | ```bash 108 | sudo chcon -R -t container_file_t /ruta/al/proyecto 109 | # o, desde la carpeta del proyecto 110 | sudo chcon -R -t container_file_t . 111 | 112 | # Reiniciar contenedores 113 | docker compose restart 114 | ``` 115 | 116 | ### 5.5 Inconsistencia de namespace (Error fatal de PHP) 117 | 118 | - Problema: `php antonella namespace` reportaba haber renombrado el namespace (por ejemplo `TWDYJC`), pero no actualizó `composer.json` ni todos los archivos `.php` en `src/`. Resultado: autoloader roto y error `Class not found`. 119 | - Solución aplicada: reemplazo masivo con `sed` y regeneración del autoloader. 120 | 121 | Ejemplo de corrección dentro del contenedor `wpcli`: 122 | 123 | ```bash 124 | sed -i 's/MIPLUGIN/TWDYJC/g' composer.json 125 | find ./src -type f -name "*.php" -exec sed -i 's/MIPLUGIN/TWDYJC/g' {} + 126 | php composer.phar dump-autoload 127 | ``` 128 | 129 | ## 6. Recomendaciones y pasos a seguir 130 | 131 | - Documentar en el README del proyecto que en sistemas con SELinux (Fedora/CentOS) es necesario aplicar `container_file_t` a los directorios montados, o usar la opción `:z`/`:Z` en los volúmenes de Docker cuando sea apropiado. 132 | - Mejorar el script `php antonella namespace` para que: 133 | - Actualice `composer.json` y todos los archivos `.php` en `src/` de forma atómica. 134 | - Valide la estructura del autoload y ejecute `composer dump-autoload` al final. 135 | - Añada tests unitarios que simulen el renombrado de namespace. 136 | - Añadir lógica de reintento/wait-for en el script que ejecuta WP-CLI para esperar a MySQL (por ejemplo con timeout y backoff exponencial) en lugar de colgar indefinidamente. 137 | - Incluir en la documentación una sección de solución de problemas con los comandos usados (`chcon`, `sed`, `dump-autoload`, `wp-cli`). 138 | 139 | ## 7. Conclusión 140 | 141 | La instalación fue posible tras aplicar varios arreglos manuales. Las causas principales fueron: aislamiento inadecuado de dependencias (Composer global), conflictos de red en Docker, políticas SELinux en el host y un bug en el script de renombrado de namespace. Con las correcciones indicadas se logra una instalación reproducible y documentada. 142 | 143 | ## Anexo: comandos útiles 144 | 145 | ```bash 146 | # Levantar contenedores en background 147 | docker compose up -d 148 | 149 | # Asignar contexto SELinux (en Fedora) 150 | sudo chcon -R -t container_file_t /ruta/al/proyecto 151 | 152 | # Entrar a wpcli y ejecutar instalación manual 153 | docker compose exec wpcli bash 154 | wp core install --allow-root ... 155 | 156 | # Reemplazar namespaces y regenerar autoloader 157 | sed -i 's/MIPLUGIN/TWDYJC/g' composer.json 158 | find ./src -type f -name "*.php" -exec sed -i 's/MIPLUGIN/TWDYJC/g' {} + 159 | php composer.phar dump-autoload 160 | 161 | # Activar plugin 162 | wp plugin activate antonella-framework --allow-root 163 | ``` 164 | 165 | Reporte generado: 20 de octubre de 2025 166 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | === Antonella Framework === 2 | Contributors: cehojac 3 | Donate link: https://antonellaframework.com/ 4 | Tags: framework, mvc, development, plugin-development, wordpress-framework 5 | Requires at least: 5.0 6 | Tested up to: 6.8 7 | Requires PHP: 8.0 8 | Stable tag: 1.9.0 9 | License: GPLv2 or later 10 | License URI: https://www.gnu.org/licenses/gpl-2.0.html 11 | 12 | A powerful WordPress framework for developers based on Model View Controller architecture. 13 | 14 | == Description == 15 | 16 | Antonella Framework is a comprehensive WordPress development framework that follows the Model-View-Controller (MVC) architectural pattern. It provides developers with a robust foundation for creating professional WordPress plugins with clean, maintainable code. 17 | 18 | **Key Features:** 19 | 20 | * **MVC Architecture**: Clean separation of concerns with Model-View-Controller pattern 21 | * **Modern PHP**: Built for PHP 8.0+ with modern programming practices 22 | * **Database Management**: Intelligent table creation and updates using WordPress dbDelta 23 | * **Security First**: Built-in security features including nonce verification, data sanitization, and SQL injection protection 24 | * **Internationalization**: Full i18n support with modern translation loading 25 | * **Gutenberg Ready**: Built-in support for custom Gutenberg blocks 26 | * **REST API**: Easy REST API endpoint creation and management 27 | * **Custom Post Types**: Simplified custom post type and taxonomy registration 28 | * **Admin Interface**: Professional admin panels and dashboard widgets 29 | * **Composer Integration**: Modern dependency management with Composer 30 | * **Developer Tools**: Comprehensive debugging and logging system 31 | 32 | **Perfect for:** 33 | 34 | * Plugin developers who want a solid foundation 35 | * Teams building complex WordPress applications 36 | * Developers who prefer structured, maintainable code 37 | * Projects requiring scalable architecture 38 | 39 | **Documentation:** 40 | 41 | Complete documentation is available at [https://antonellaframework.com/documentacion](https://antonellaframework.com/documentacion) 42 | 43 | == Installation == 44 | 45 | **Method 1: Using Antonella Installer (Recommended)** 46 | 47 | 1. Install the Antonella installer globally via Composer: 48 | `composer global require cehojac/antonella-installer` 49 | 50 | 2. Create a new project: 51 | `antonella new my-awesome-plugin` 52 | 53 | 3. Navigate to your project: 54 | `cd my-awesome-plugin` 55 | 56 | **Method 2: Manual Installation** 57 | 58 | 1. Create a new directory for your plugin 59 | 2. Run: `composer create-project --prefer-dist cehojac/antonella-framework-for-wp:dev-master my-awesome-plugin` 60 | 3. Navigate to the project directory 61 | 4. Start developing your plugin 62 | 63 | **Requirements:** 64 | 65 | * PHP 8.0 or higher 66 | * WordPress 5.0 or higher 67 | * Composer for dependency management 68 | 69 | == Frequently Asked Questions == 70 | 71 | = What is the MVC pattern? = 72 | 73 | MVC (Model-View-Controller) is an architectural pattern that separates application logic into three interconnected components: 74 | - **Model**: Handles data and business logic 75 | - **View**: Manages the presentation layer 76 | - **Controller**: Handles user input and coordinates between Model and View 77 | 78 | = Is this framework suitable for beginners? = 79 | 80 | While Antonella Framework is designed to be developer-friendly, it's best suited for developers with some PHP and WordPress experience. The framework provides structure and best practices that help developers create professional plugins. 81 | 82 | = Can I use this for commercial projects? = 83 | 84 | Yes! Antonella Framework is released under the GPL v2 license, making it suitable for both personal and commercial projects. 85 | 86 | = Does it work with the latest WordPress version? = 87 | 88 | Yes, Antonella Framework is regularly updated to ensure compatibility with the latest WordPress versions. It's currently tested up to WordPress 6.4. 89 | 90 | = Where can I get support? = 91 | 92 | - Documentation: [https://antonellaframework.com/documentacion](https://antonellaframework.com/documentacion) 93 | - Community: [Gitter Chat](https://gitter.im/Antonella-Framework/community) 94 | - Issues: [GitHub Repository](https://github.com/cehojac/antonella-framework-for-wp) 95 | 96 | == Screenshots == 97 | 98 | 1. Framework structure showing MVC organization 99 | 2. Admin interface example 100 | 3. Custom post type configuration 101 | 4. REST API endpoint setup 102 | 5. Gutenberg block integration 103 | 104 | == Changelog == 105 | 106 | = 1.9.0 = 107 | * Complete refactoring of database management system 108 | * Improved security with enhanced data sanitization 109 | * Modern translation system without deprecated functions 110 | * Centralized hooks management 111 | * Professional logging system with debug mode 112 | * Enhanced Gutenberg blocks support 113 | * Better error handling and validation 114 | * Updated for WordPress 6.4 compatibility 115 | * Improved documentation and examples 116 | 117 | = 1.8.0 = 118 | * Added REST API endpoint management 119 | * Enhanced security features 120 | * Improved admin interface 121 | * Better Gutenberg integration 122 | * Updated dependencies 123 | 124 | = 1.7.0 = 125 | * Custom post types and taxonomies support 126 | * Enhanced MVC structure 127 | * Improved database handling 128 | * Better internationalization support 129 | 130 | = 1.6.0 = 131 | * Initial stable release 132 | * Core MVC framework implementation 133 | * Basic admin interface 134 | * Security features implementation 135 | 136 | == Upgrade Notice == 137 | 138 | = 1.9.0 = 139 | Major update with improved security, modern database management, and enhanced WordPress compatibility. Recommended for all users. 140 | 141 | == Development == 142 | 143 | **Contributing:** 144 | 145 | Contributions are welcome! Please visit our [GitHub repository](https://github.com/cehojac/antonella-framework-for-wp) to: 146 | - Report bugs 147 | - Request features 148 | - Submit pull requests 149 | - Review code 150 | 151 | **Testing Environment:** 152 | 153 | The framework includes a complete Docker development environment with: 154 | - WordPress installation 155 | - Plugin development tools 156 | - Database management 157 | - Debugging utilities 158 | 159 | **Architecture:** 160 | 161 | ``` 162 | my-plugin/ 163 | ├── src/ # Core framework files 164 | │ ├── Controllers/ # Application controllers 165 | │ ├── Admin/ # WordPress admin functionality 166 | │ ├── helpers/ # Utility functions 167 | │ ├── Config.php # Central configuration 168 | │ ├── Security.php # Security utilities 169 | │ └── Api.php # REST API management 170 | ├── resources/ # Views and templates 171 | ├── Assets/ # CSS, JS, images 172 | ├── languages/ # Translation files 173 | └── antonella-framework.php # Main plugin file 174 | ``` 175 | 176 | == Privacy Policy == 177 | 178 | Antonella Framework does not collect, store, or transmit any personal data. It's a development framework that provides tools for building WordPress plugins. Any data handling depends on how developers implement their plugins using the framework. 179 | 180 | == Support == 181 | 182 | For support, documentation, and community discussions: 183 | 184 | * **Documentation**: [https://antonellaframework.com/documentacion](https://antonellaframework.com/documentacion) 185 | * **Community Chat**: [https://gitter.im/Antonella-Framework/community](https://gitter.im/Antonella-Framework/community) 186 | * **GitHub Issues**: [https://github.com/cehojac/antonella-framework-for-wp/issues](https://github.com/cehojac/antonella-framework-for-wp/issues) 187 | * **Website**: [https://antonellaframework.com](https://antonellaframework.com) 188 | -------------------------------------------------------------------------------- /docker/init-wordpress.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Script para inicializar WordPress automáticamente 4 | # NO usar 'set -e' para manejar errores manualmente 5 | set +e 6 | 7 | echo "🚀 Iniciando configuración automática de WordPress..." 8 | echo "📅 $(date)" 9 | 10 | # Esperar a que MySQL esté listo con timeout 11 | echo "⏳ Esperando a que MySQL esté disponible..." 12 | MYSQL_TIMEOUT=120 # 2 minutos 13 | MYSQL_COUNTER=0 14 | MYSQL_READY=false 15 | 16 | while [ $MYSQL_COUNTER -lt $MYSQL_TIMEOUT ]; do 17 | if mysqladmin ping -h"mysql" -u"wordpress" -p"wordpress" --skip-ssl --silent 2>/dev/null; then 18 | MYSQL_READY=true 19 | echo "✅ MySQL está listo (después de ${MYSQL_COUNTER} segundos)" 20 | break 21 | fi 22 | 23 | if [ $((MYSQL_COUNTER % 10)) -eq 0 ]; then 24 | echo " ⏱️ Esperando MySQL... (${MYSQL_COUNTER}/${MYSQL_TIMEOUT}s)" 25 | fi 26 | 27 | sleep 2 28 | MYSQL_COUNTER=$((MYSQL_COUNTER + 2)) 29 | done 30 | 31 | if [ "$MYSQL_READY" = false ]; then 32 | echo "❌ ERROR: MySQL no está disponible después de ${MYSQL_TIMEOUT} segundos" 33 | echo "❌ Verifica que el contenedor de MySQL esté corriendo correctamente" 34 | echo "💡 Ejecuta: docker compose logs mysql" 35 | exit 1 36 | fi 37 | 38 | # Esperar a que WordPress esté disponible con timeout 39 | echo "⏳ Esperando a que WordPress esté disponible..." 40 | WP_TIMEOUT=60 # 1 minuto 41 | WP_COUNTER=0 42 | WP_READY=false 43 | 44 | while [ $WP_COUNTER -lt $WP_TIMEOUT ]; do 45 | if curl -s http://localhost > /dev/null 2>&1; then 46 | WP_READY=true 47 | echo "✅ WordPress está disponible (después de ${WP_COUNTER} segundos)" 48 | break 49 | fi 50 | 51 | if [ $((WP_COUNTER % 10)) -eq 0 ]; then 52 | echo " ⏱️ Esperando WordPress... (${WP_COUNTER}/${WP_TIMEOUT}s)" 53 | fi 54 | 55 | sleep 2 56 | WP_COUNTER=$((WP_COUNTER + 2)) 57 | done 58 | 59 | if [ "$WP_READY" = false ]; then 60 | echo "❌ ERROR: WordPress no está disponible después de ${WP_TIMEOUT} segundos" 61 | echo "❌ Verifica que Apache esté corriendo correctamente" 62 | exit 1 63 | fi 64 | 65 | # Verificar si WordPress ya está instalado 66 | if wp core is-installed --allow-root --path=/var/www/html --quiet 2>/dev/null; then 67 | echo "✅ WordPress ya está instalado" 68 | 69 | # Actualizar WordPress a la última versión 70 | echo "🔄 Verificando actualizaciones de WordPress..." 71 | if wp core check-update --allow-root --path=/var/www/html --format=count --quiet 2>/dev/null | grep -q "^[1-9]"; then 72 | echo "📥 Actualizando WordPress a la última versión..." 73 | wp core update --allow-root --path=/var/www/html --quiet 2>/dev/null 74 | echo "✅ WordPress actualizado correctamente" 75 | else 76 | echo "✅ WordPress ya está en la última versión" 77 | fi 78 | else 79 | echo "📦 Instalando WordPress..." 80 | 81 | # Instalar WordPress 82 | wp core install \ 83 | --url="http://localhost:8080" \ 84 | --title="Antonella Framework Test" \ 85 | --admin_user="test" \ 86 | --admin_password="test" \ 87 | --admin_email="test@antonella.test" \ 88 | --allow-root \ 89 | --path=/var/www/html \ 90 | --quiet 2>/dev/null 91 | 92 | echo "✅ WordPress instalado correctamente" 93 | 94 | # Actualizar WordPress a la última versión después de la instalación 95 | echo "🔄 Actualizando WordPress a la última versión..." 96 | wp core update --allow-root --path=/var/www/html --quiet 2>/dev/null 97 | echo "✅ WordPress actualizado a la última versión" 98 | fi 99 | 100 | # Desinstalar plugins por defecto de WordPress 101 | echo "🗑️ Desinstalando plugins por defecto..." 102 | wp plugin delete hello-dolly --allow-root --path=/var/www/html --quiet 2>/dev/null || true 103 | wp plugin delete akismet --allow-root --path=/var/www/html --quiet 2>/dev/null || true 104 | echo "✅ Plugins por defecto eliminados" 105 | 106 | # Activar el framework Antonella (si existe) 107 | echo "🔌 Verificando Antonella Framework..." 108 | if wp plugin list --name=antonella-framework --allow-root --path=/var/www/html --format=count 2>/dev/null | grep -q "1"; then 109 | wp plugin activate antonella-framework --allow-root --path=/var/www/html 2>/dev/null && echo "✅ Antonella Framework activado" || echo "⚠️ No se pudo activar antonella-framework" 110 | else 111 | echo "ℹ️ Plugin antonella-framework no encontrado (se activará cuando esté disponible)" 112 | fi 113 | 114 | # Instalar y activar Plugin Check 115 | echo "📥 Instalando plugins de desarrollo..." 116 | wp plugin install plugin-check --activate --allow-root --path=/var/www/html --quiet 2>/dev/null || true 117 | 118 | # Query Monitor - Para debugging 119 | wp plugin install query-monitor --activate --allow-root --path=/var/www/html --quiet 2>/dev/null || true 120 | 121 | # Debug Bar - Para debugging adicional 122 | wp plugin install debug-bar --activate --allow-root --path=/var/www/html --quiet 2>/dev/null || true 123 | 124 | # Theme Check - Para verificar temas 125 | wp plugin install theme-check --activate --allow-root --path=/var/www/html --quiet 2>/dev/null || true 126 | 127 | echo "✅ Plugins de desarrollo instalados" 128 | 129 | # Configurar tema por defecto 130 | echo "🎨 Configurando tema..." 131 | wp theme activate twentytwentyfour --allow-root --path=/var/www/html --quiet 2>/dev/null || true 132 | echo "✅ Tema configurado" 133 | 134 | # Configurar permalinks 135 | echo "🔗 Configurando permalinks..." 136 | wp rewrite structure '/%postname%/' --allow-root --path=/var/www/html --quiet 2>/dev/null 137 | wp rewrite flush --allow-root --path=/var/www/html --quiet 2>/dev/null 138 | echo "✅ Permalinks configurados" 139 | 140 | # Corregir permisos de WordPress para actualizaciones 141 | echo "🔧 Corrigiendo permisos de WordPress..." 142 | # Excluir directorios montados del host para evitar errores de permisos en Linux 143 | find /var/www/html/wp-content/ -maxdepth 1 -type d ! -name 'plugins' ! -name 'debug.log' -exec chown -R www-data:www-data {} + 2>/dev/null || true 144 | find /var/www/html/wp-content/ -maxdepth 1 -type d ! -name 'plugins' ! -name 'debug.log' -exec chmod -R 755 {} + 2>/dev/null || true 145 | 146 | # Crear y dar permisos a directorios necesarios 147 | mkdir -p /var/www/html/wp-content/uploads /var/www/html/wp-content/upgrade 2>/dev/null || true 148 | chown -R www-data:www-data /var/www/html/wp-content/uploads /var/www/html/wp-content/upgrade 2>/dev/null || true 149 | chmod -R 775 /var/www/html/wp-content/uploads /var/www/html/wp-content/upgrade 2>/dev/null || true 150 | echo "✅ Permisos de WordPress configurados" 151 | 152 | # Configurar opciones de desarrollo 153 | echo "⚙️ Configurando opciones de desarrollo..." 154 | wp option update blog_public 0 --allow-root --path=/var/www/html --quiet 2>/dev/null 155 | wp option update users_can_register 1 --allow-root --path=/var/www/html --quiet 2>/dev/null 156 | echo "✅ Opciones de desarrollo configuradas" 157 | 158 | # Crear contenido de ejemplo 159 | echo "📝 Creando contenido de ejemplo..." 160 | wp post create --post_type=page --post_title="Página de Prueba Antonella" --post_content="Esta es una página de prueba para el framework Antonella." --post_status=publish --allow-root --path=/var/www/html --quiet 2>/dev/null || true 161 | wp post create --post_title="Post de Prueba Antonella" --post_content="Este es un post de prueba para demostrar las funcionalidades del framework Antonella." --post_status=publish --allow-root --path=/var/www/html --quiet 2>/dev/null || true 162 | echo "✅ Contenido de ejemplo creado" 163 | 164 | echo "🎉 ¡Configuración completada!" 165 | echo "📍 Accede a tu sitio en: http://localhost:8080" 166 | echo "🔐 Admin: http://localhost:8080/wp-admin" 167 | echo "👤 Usuario: test" 168 | echo "🔑 Contraseña: test" 169 | echo "🗄️ phpMyAdmin: http://localhost:9000" 170 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # 🔒 Antonella Framework Security Guide 2 | 3 | ## Overview 4 | 5 | Antonella Framework 1.9.0 now includes comprehensive security features to ensure your WordPress plugins follow best practices for security. 6 | 7 | ## Security Features 8 | 9 | ### 1. 🛡️ User Capability Checks 10 | 11 | #### Basic Usage: 12 | ```php 13 | use CH\Security; 14 | 15 | // Check if user has specific capability 16 | Security::check_user_capability('manage_options'); 17 | 18 | // Check if user is admin 19 | if (Security::is_admin_user()) { 20 | // Admin-only code 21 | } 22 | 23 | // Check if user can edit posts 24 | if (Security::can_edit_posts()) { 25 | // Editor-level code 26 | } 27 | ``` 28 | 29 | #### Common WordPress Capabilities: 30 | - `manage_options` - Administrator level 31 | - `edit_posts` - Editor level 32 | - `edit_published_posts` - Author level 33 | - `edit_others_posts` - Editor level 34 | - `publish_posts` - Author level 35 | - `activate_plugins` - Administrator level 36 | 37 | ### 2. 🔐 Nonce Security 38 | 39 | #### Creating Nonce Fields: 40 | ```php 41 | // In your admin form 42 | echo Security::create_nonce_field('my_action', 'my_nonce'); 43 | 44 | // This generates: 45 | // 46 | ``` 47 | 48 | #### Verifying Nonces: 49 | ```php 50 | // When processing form data 51 | Security::verify_nonce('my_nonce', 'my_action'); 52 | ``` 53 | 54 | ### 3. 🧹 Input Sanitization 55 | 56 | #### Sanitize Different Data Types: 57 | ```php 58 | // Text fields 59 | $name = Security::sanitize_input($_POST['name'], 'text'); 60 | 61 | // Email addresses 62 | $email = Security::sanitize_input($_POST['email'], 'email'); 63 | 64 | // URLs 65 | $website = Security::sanitize_input($_POST['website'], 'url'); 66 | 67 | // Textarea content 68 | $message = Security::sanitize_input($_POST['message'], 'textarea'); 69 | 70 | // HTML content (allows safe HTML) 71 | $content = Security::sanitize_input($_POST['content'], 'html'); 72 | ``` 73 | 74 | ### 4. 🔒 Output Escaping 75 | 76 | #### Escape Data for Different Contexts: 77 | ```php 78 | // HTML content 79 | echo Security::escape_output($data, 'html'); 80 | 81 | // HTML attributes 82 | echo '
'; 83 | 84 | // URLs 85 | echo ''; 86 | 87 | // JavaScript 88 | echo ''; 89 | ``` 90 | 91 | ## Complete Example 92 | 93 | ### Admin Page with Security: 94 | ```php 95 | namespace CH\Controllers; 96 | 97 | use CH\Security; 98 | 99 | class MyController 100 | { 101 | public static function admin_page() 102 | { 103 | // Check user capabilities 104 | Security::check_user_capability('manage_options'); 105 | 106 | // Process form if submitted 107 | if (isset($_POST['submit'])) { 108 | // Verify nonce 109 | Security::verify_nonce('my_nonce', 'my_action'); 110 | 111 | // Sanitize input 112 | $option_value = Security::sanitize_input($_POST['option_value'], 'text'); 113 | 114 | // Save option 115 | update_option('my_option', $option_value); 116 | 117 | // Show success message 118 | echo '

' . 119 | Security::escape_output(__('Settings saved!', 'antonella')) . 120 | '

'; 121 | } 122 | 123 | // Get current option value 124 | $current_value = get_option('my_option', ''); 125 | ?> 126 |
127 |

128 | 129 |
130 | 131 | 132 | 133 | 134 | 139 | 146 | 147 |
135 | 138 | 140 | 145 |
148 | 149 | 150 |
151 |
152 | __('Success!', 'antonella'), 178 | 'data' => $result 179 | ]); 180 | } 181 | ``` 182 | 183 | ### API Endpoint with Security: 184 | ```php 185 | public static function api_endpoint(\WP_REST_Request $request) 186 | { 187 | // Check if user is logged in 188 | if (!is_user_logged_in()) { 189 | return new \WP_Error('unauthorized', __('You must be logged in', 'antonella'), ['status' => 401]); 190 | } 191 | 192 | // Check capabilities 193 | if (!Security::can_edit_posts()) { 194 | return new \WP_Error('forbidden', __('Insufficient permissions', 'antonella'), ['status' => 403]); 195 | } 196 | 197 | // Sanitize input 198 | $data = Security::sanitize_input($request->get_param('data'), 'text'); 199 | 200 | // Process and return response 201 | return rest_ensure_response([ 202 | 'success' => true, 203 | 'data' => $data 204 | ]); 205 | } 206 | ``` 207 | 208 | ## Best Practices 209 | 210 | 1. **Always check capabilities** before allowing access to admin functions 211 | 2. **Use nonces** for all forms and AJAX requests 212 | 3. **Sanitize all input** data before processing 213 | 4. **Escape all output** data before displaying 214 | 5. **Use specific capabilities** rather than generic ones when possible 215 | 6. **Validate data types** and ranges where appropriate 216 | 7. **Log security events** for monitoring purposes 217 | 218 | ## Common Security Mistakes to Avoid 219 | 220 | ❌ **Don't do this:** 221 | ```php 222 | // No capability check 223 | function admin_function() { 224 | update_option('my_option', $_POST['value']); // No sanitization 225 | } 226 | 227 | // No nonce verification 228 | if (isset($_POST['submit'])) { 229 | process_form(); // Vulnerable to CSRF 230 | } 231 | 232 | // No output escaping 233 | echo '
' . $user_input . '
'; // XSS vulnerability 234 | ``` 235 | 236 | ✅ **Do this instead:** 237 | ```php 238 | // Proper security implementation 239 | function admin_function() { 240 | Security::check_user_capability('manage_options'); 241 | Security::verify_nonce('my_nonce', 'my_action'); 242 | 243 | $value = Security::sanitize_input($_POST['value'], 'text'); 244 | update_option('my_option', $value); 245 | } 246 | 247 | // In template 248 | echo '
' . Security::escape_output($user_input) . '
'; 249 | ``` 250 | 251 | 252 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # 🚀 Antonella Framework for WordPress 2 | 3 | ![Antonella Framework](https://legacy.antonellaframework.com/wp-content/uploads/2018/06/anonella-repositorio.png) 4 | 5 | [![DeepWiki](https://img.shields.io/badge/DeepWiki-Antonella_Framework-blue?logo=wikipedia)](https://deepwiki.com/cehojac/antonella-framework-for-wp) 6 | 7 | [![Total Downloads](https://poser.pugx.org/cehojac/antonella-framework-for-wp/downloads)](https://packagist.org/packages/cehojac/antonella-framework-for-wp) 8 | [![Latest Version](https://poser.pugx.org/cehojac/antonella-framework-for-wp/v/stable)](https://packagist.org/packages/cehojac/antonella-framework-for-wp) 9 | [![License](https://poser.pugx.org/cehojac/antonella-framework-for-wp/license)](https://packagist.org/packages/cehojac/antonella-framework-for-wp) 10 | [![Gitter](https://badges.gitter.im/Antonella-Framework/community.svg)](https://gitter.im/Antonella-Framework/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) 11 | 12 | **Framework for developing WordPress plugins based on Model View Controller with enterprise-level security** 13 | 14 | 📖 **Full Documentation**: [https://antonellaframework.com](https://antonellaframework.com) 15 | 🎥 **Video Tutorial**: [https://tipeos.com/anto](https://tipeos.com/anto) 16 | 17 | --- 18 | 19 | ## ✨ What's New in Version 1.9.0 20 | 21 | ### 🔒 **Enterprise-Level Security** 22 | - **CSRF Protection**: Automatic nonce verification 23 | - **Permission Control**: Granular user capability checks 24 | - **Input Sanitization**: Automatic data cleaning 25 | - **Output Escaping**: XSS attack prevention 26 | - **Security Class**: Centralized API for all security functions 27 | 28 | ### 🛠️ **Technical Improvements** 29 | - **PHP 8.2 Compatible**: Full compatibility with latest PHP 30 | - **Enhanced Headers**: Complete plugin metadata 31 | - **Docker Integration**: Improved development environment 32 | - **Auto Root File Change**: Automatic plugin file renaming 33 | 34 | --- 35 | 36 | ## 📋 Requirements 37 | 38 | ### **Core Requirements** 39 | - **PHP**: 8.0 or higher 40 | - **Composer**: Latest version 41 | - **Git**: For version control 42 | - **WordPress**: 5.0 or higher 43 | 44 | ### **Docker Development Environment** 45 | - **Docker Desktop**: 4.53.0+ (⚠️ **Required for ARM64/Windows compatibility**) 46 | - **Docker Compose**: v2.0+ 47 | - **Available Ports**: 8080 (WordPress), 3306 (MySQL), 9000 (phpMyAdmin) 48 | 49 | > **💡 Note**: For optimal ARM64 compatibility on Windows/Mac, ensure Docker Desktop is updated to version 4.53.0 or higher. Earlier versions may experience container startup issues. 50 | 51 | --- 52 | 53 | ## 🚀 Quick Installation 54 | 55 | ### 1. Create Your Plugin Project 56 | Via Antonella installer 57 | 58 | ```bash 59 | composer global require cehojac/antonella-installer 60 | antonella new my-awesome-plugin 61 | cd my-awesome-plugin 62 | ``` 63 | 64 | or via composer CLI 65 | 66 | ```bash 67 | composer create-project --prefer-dist cehojac/antonella-framework-for-wp my-awesome-plugin 68 | cd my-awesome-plugin 69 | ``` 70 | 71 | ### 2. Initialize Your Project 72 | ```bash 73 | php antonella namespace MyPlugin 74 | php antonella updateproject 75 | ``` 76 | 77 | ### 3. Start Development 78 | 79 | #### **Option A: Traditional WordPress Development** 80 | Your plugin is now ready! Upload to WordPress and start developing. 81 | 82 | #### **Option B: Docker Development Environment** 83 | For a complete development setup with database and admin interface: 84 | 85 | ```bash 86 | # Start the development environment 87 | php antonella serve 88 | # or manually with Docker Compose 89 | docker compose up -d 90 | 91 | # Access your development site 92 | # WordPress: http://localhost:8080 93 | # Admin Panel: http://localhost:8080/wp-admin (test/test) 94 | # phpMyAdmin: http://localhost:9000 95 | ``` 96 | 97 | **🐳 Docker Environment Includes:** 98 | - WordPress with automatic framework activation 99 | - MySQL 8.0 with persistent data 100 | - phpMyAdmin for database management 101 | - WP-CLI for command automation 102 | - Development plugins (Query Monitor, Debug Bar) 103 | 104 | **📋 Default Credentials:** 105 | - **WordPress Admin**: `test` / `test` 106 | - **MySQL**: `wordpress` / `wordpress` 107 | 108 | > **🔧 Troubleshooting**: If containers fail to start, ensure Docker Desktop is updated to 4.53.0+ and required ports (8080, 3306, 9000) are available. 109 | 110 | --- 111 | 112 | ## 🎯 Core Features 113 | 114 | ### **Console Commands** 115 | | Command | Description | 116 | |---------|-------------| 117 | | `php antonella namespace FOO` | Rename namespace across all files | 118 | | `php antonella make MyController` | Create controller class | 119 | | `php antonella widget MyWidget` | Create widget class | 120 | | `php antonella helper myFunction` | Create helper function | 121 | | `php antonella cpt MyPostType` | Create custom post type | 122 | | `php antonella block MyBlock` | Create Gutenberg block | 123 | | `php antonella makeup` | Generate ZIP for distribution | 124 | | `php antonella serve` | Start development server | 125 | 126 | ### **Security API** 127 | ```php 128 | use CH\Security; 129 | 130 | // Verify user permissions 131 | Security::check_user_capability('manage_options'); 132 | 133 | // Create secure forms 134 | echo Security::create_nonce_field('my_action'); 135 | Security::verify_nonce('my_nonce', 'my_action'); 136 | 137 | // Sanitize input data 138 | $data = Security::sanitize_input($_POST['data'], 'text'); 139 | 140 | // Escape output data 141 | echo Security::escape_output($data); 142 | ``` 143 | 144 | ### **Built-in Capabilities** 145 | - ✅ **MVC Architecture**: Clean separation of concerns 146 | - ✅ **Security First**: Enterprise-level protection 147 | - ✅ **Auto-loading**: PSR-4 compliant 148 | - ✅ **Blade Templates**: Optional template engine 149 | - ✅ **Custom Post Types**: Easy CPT creation 150 | - ✅ **Gutenberg Blocks**: Block development tools 151 | - ✅ **Docker Support**: Containerized development 152 | - ✅ **Testing Framework**: Built-in testing tools 153 | 154 | --- 155 | 156 | ## 🛡️ Security Features 157 | 158 | ### **CSRF Protection** 159 | ```php 160 | // In your form 161 | echo Security::create_nonce_field('update_settings'); 162 | 163 | // In your controller 164 | Security::verify_nonce('settings_nonce', 'update_settings'); 165 | ``` 166 | 167 | ### **Data Sanitization** 168 | ```php 169 | $text = Security::sanitize_input($_POST['text'], 'text'); 170 | $email = Security::sanitize_input($_POST['email'], 'email'); 171 | $url = Security::sanitize_input($_POST['url'], 'url'); 172 | $html = Security::sanitize_input($_POST['content'], 'html'); 173 | ``` 174 | 175 | ### **Output Escaping** 176 | ```php 177 | echo Security::escape_output($user_data, 'html'); 178 | echo ''; 179 | echo ''; 180 | ``` 181 | 182 | --- 183 | 184 | ## 🐳 Development with Docker 185 | 186 | ### Start Development Environment 187 | ```bash 188 | php antonella serve 189 | # or 190 | php antonella serve -d # detached mode 191 | ``` 192 | 193 | ### Features Include: 194 | - WordPress latest version 195 | - PHP 8.2 196 | - MySQL 8.0 197 | - Automatic plugin installation 198 | - Hot reloading 199 | 200 | --- 201 | 202 | ## 📦 Plugin Distribution 203 | 204 | ### Create Production ZIP 205 | ```bash 206 | php antonella makeup 207 | ``` 208 | 209 | This command: 210 | - ✅ Excludes development files 211 | - ✅ Includes only production dependencies 212 | - ✅ Creates optimized ZIP file 213 | - ✅ Maintains proper file structure 214 | 215 | --- 216 | 217 | ## 🔧 Migration from 1.8.x 218 | 219 | ### Update Your Controllers 220 | **Before (1.8.x):** 221 | ```php 222 | public function process_form() { 223 | $data = $_POST['data']; 224 | update_option('my_option', $data); 225 | } 226 | ``` 227 | 228 | **After (1.9.0):** 229 | ```php 230 | public function process_form() { 231 | Security::check_user_capability('manage_options'); 232 | Security::verify_nonce('my_nonce', 'my_action'); 233 | 234 | $data = Security::sanitize_input($_POST['data'], 'text'); 235 | update_option('my_option', $data); 236 | } 237 | ``` 238 | 239 | --- 240 | 241 | ## 🤝 Contributing 242 | 243 | 1. Fork the repository 244 | 2. Create your feature branch (`git checkout -b feature/amazing-feature`) 245 | 3. Commit your changes (`git commit -m 'Add amazing feature'`) 246 | 4. Push to the branch (`git push origin feature/amazing-feature`) 247 | 5. Open a Pull Request 248 | 249 | --- 250 | 251 | ## 📞 Support 252 | 253 | - **Documentation**: [antonellaframework.com/documentacion](https://antonellaframework.com/documentacion) 254 | - **Community Chat**: [Gitter](https://gitter.im/Antonella-Framework/community) 255 | - **Issues**: [GitHub Issues](https://github.com/cehojac/antonella-framework-for-wp/issues) 256 | - **Email**: antonella.framework@carlos-herrera.com 257 | 258 | --- 259 | 260 | ## 📄 License 261 | 262 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 263 | 264 | --- 265 | 266 | ## 🎉 Made with ❤️ by Carlos Herrera 267 | 268 | **Antonella Framework** - Making WordPress plugin development secure, fast, and enjoyable! 269 | 270 | 271 | -------------------------------------------------------------------------------- /src/Controllers/ExampleController.php: -------------------------------------------------------------------------------- 1 |

Configuración guardada correctamente'; 36 | 37 | } catch (\Exception $e) { 38 | log_error('Error: ' . Security::escape_output($e->getMessage())); 39 | } 40 | } 41 | 42 | /** 43 | * Example admin page with nonce 44 | */ 45 | public static function adminPage() 46 | { 47 | 48 | $site_name = get_option('antonella_site_name', 'Mi Proyecto Increíble'); 49 | $debug_mode = get_option('antonella_debug_mode', false); 50 | $api_key = get_option('antonella_api_key', ''); 51 | 52 | ?> 53 |

54 |
55 |

🚀 Antonella configuration panel

56 |

This is an example panel, it is not a real panel.

57 |

Location of this function: /Controllers/ExampleController.php adminPage()

58 | 59 |

✅ Settings stored correctly

60 | 61 |
62 | 63 | 64 | 65 | 66 | 69 | 79 | 80 | 81 | 82 | 83 | 96 | 97 | 98 | 99 | 102 | 111 | 112 |
67 | 68 | 70 | 77 |

Personalized name for your project.

78 |
Debug mode 84 |
85 | 93 |

Shows additional information from Debugging.

94 |
95 |
100 | 101 | 103 | 109 |

Key for external integrations.

110 |
113 | 114 |

115 | 119 |

120 |
121 | 122 |
123 | 124 |
125 |
126 | 📊 Posted Posts
127 | publish); ?> 128 |
129 |
130 | 👥 Users
131 | 132 |
133 |
134 | 🔧 Plugins
135 | 136 |
137 |
138 |
139 |
140 | 141 | 153 | 401]); 164 | } 165 | 166 | // Check capabilities 167 | if (!Security::can_edit_posts()) { 168 | return new \WP_Error('forbidden', __('Insufficient permissions', 'antonella-framework'), ['status' => 403]); 169 | } 170 | 171 | // Sanitize input 172 | $data = Security::sanitize_input($_REQUEST['data'], 'text'); 173 | 174 | // Process and return response 175 | return rest_ensure_response([ 176 | 'success' => true, 177 | 'data' => $data 178 | ]); 179 | } 180 | 181 | /** 182 | * Example AJAX handler 183 | */ 184 | public static function ajax_handler() 185 | { 186 | // Check nonce 187 | if (!wp_verify_nonce($_POST['nonce'], 'example_ajax_nonce')) { 188 | wp_die(esc_html(__('Security check failed', 'antonella-framework'))); 189 | } 190 | 191 | // Check capabilities 192 | Security::check_user_capability('edit_posts'); 193 | 194 | // Process AJAX request 195 | $response = [ 196 | 'success' => true, 197 | 'message' => __('AJAX request processed successfully', 'antonella-framework') 198 | ]; 199 | 200 | wp_send_json($response); 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/Install.php: -------------------------------------------------------------------------------- 1 | get_charset_collate(); 48 | } 49 | 50 | /** 51 | * Main installation function - Entry point for plugin activation 52 | * 53 | * @since 1.0.0 54 | * @return void 55 | */ 56 | public static function index() 57 | { 58 | try { 59 | $config = new Config(); 60 | $install = new Install(); 61 | 62 | // Log installation start 63 | 64 | 65 | // Create/update database tables 66 | if (isset($config->database_tables) && !empty($config->database_tables)) { 67 | $install->create_tables($config->database_tables); 68 | } 69 | 70 | // Add new columns to existing tables 71 | if (isset($config->table_updates) && !empty($config->table_updates)) { 72 | $install->update_table_columns($config->table_updates); 73 | } 74 | 75 | // Setup plugin options 76 | if (isset($config->plugin_options) && !empty($config->plugin_options)) { 77 | $install->setup_plugin_options($config->plugin_options); 78 | } 79 | 80 | // Update plugin version 81 | $install->update_plugin_version(); 82 | 83 | } catch (Exception $e) { 84 | self::$errors[] = 'Installation failed: ' . $e->getMessage(); 85 | 86 | 87 | // Show admin notice on error 88 | add_action('admin_notices', array(__CLASS__, 'show_installation_errors')); 89 | } 90 | } 91 | 92 | /** 93 | * Create database tables using WordPress dbDelta function 94 | * 95 | * @since 1.0.0 96 | * @param array $tables Array of table definitions 97 | * @return bool Success status 98 | */ 99 | public function create_tables($tables) 100 | { 101 | require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); 102 | 103 | $success = true; 104 | 105 | foreach ($tables as $table_config) { 106 | try { 107 | $table_name = self::$wpdb->prefix . $table_config['name']; 108 | 109 | // Build complete SQL with proper formatting for dbDelta 110 | $sql = "CREATE TABLE {$table_name} (\n"; 111 | $sql .= $table_config['columns'] . "\n"; 112 | $sql .= ") " . self::$charset_collate . ";"; 113 | 114 | // Use dbDelta for intelligent table creation/updates 115 | $result = dbDelta($sql); 116 | 117 | // Check if table creation was successful 118 | if (empty($result)) { 119 | throw new \Exception(esc_html("Failed to create table: {$table_name}")); 120 | } 121 | 122 | } catch (Exception $e) { 123 | self::$errors[] = "Table creation error: " . $e->getMessage(); 124 | $success = false; 125 | } 126 | } 127 | 128 | return $success; 129 | } 130 | 131 | /** 132 | * Add columns to existing tables with proper validation 133 | * 134 | * @since 1.0.0 135 | * @param array $updates Array of table update configurations 136 | * @return bool Success status 137 | */ 138 | public function update_table_columns($updates) 139 | { 140 | $success = true; 141 | 142 | foreach ($updates as $table_name => $columns) { 143 | $full_table_name = self::$wpdb->prefix . $table_name; 144 | 145 | // Verify table exists first 146 | if (!$this->table_exists($full_table_name)) { 147 | self::$errors[] = "Cannot update columns: Table '{$full_table_name}' does not exist"; 148 | $success = false; 149 | continue; 150 | } 151 | 152 | foreach ($columns as $column) { 153 | try { 154 | $this->add_column_if_not_exists($full_table_name, $column); 155 | } catch (Exception $e) { 156 | self::$errors[] = "Column addition error: " . $e->getMessage(); 157 | $success = false; 158 | } 159 | } 160 | } 161 | 162 | return $success; 163 | } 164 | 165 | /** 166 | * Add column to table if it doesn't exist 167 | * 168 | * @since 1.0.0 169 | * @param string $table_name Full table name with prefix 170 | * @param array $column Column configuration 171 | * @return bool True if column was added, false if already exists 172 | */ 173 | private function add_column_if_not_exists($table_name, $column) 174 | { 175 | // Check if column already exists 176 | // Use information_schema for better prepared statement compatibility 177 | global $wpdb; 178 | $column_exists = $wpdb->get_results( 179 | $wpdb->prepare( 180 | "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s AND COLUMN_NAME = %s", 181 | $table_name, 182 | $column['name'] 183 | ) 184 | ); 185 | 186 | if (!empty($column_exists)) { 187 | return false; // Column already exists 188 | } 189 | 190 | // Add the column with manual escaping for identifiers 191 | $safe_column_name = esc_sql($column['name']); 192 | $safe_definition = esc_sql($column['definition']); 193 | 194 | // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared -- Direct query needed for table structure modification, identifiers escaped manually for DDL operation 195 | $result = self::$wpdb->query("ALTER TABLE `" . $safe_table_name . "` ADD COLUMN `" . $safe_column_name . "` " . $safe_definition); 196 | 197 | if ($result === false) { 198 | throw new \Exception(esc_html("Failed to add column '{$column['name']}' to table '{$table_name}'")); 199 | } 200 | 201 | return true; 202 | } 203 | 204 | /** 205 | * Check if a table exists in the database 206 | * 207 | * @since 1.0.0 208 | * @param string $table_name Full table name with prefix 209 | * @return bool True if table exists 210 | */ 211 | private function table_exists($table_name) 212 | { 213 | // Use information_schema for better prepared statement compatibility 214 | global $wpdb; 215 | $table_exists = $wpdb->get_var( 216 | $wpdb->prepare( 217 | "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s", 218 | $table_name 219 | ) 220 | ); 221 | 222 | return $table_exists === $table_name; 223 | } 224 | 225 | /** 226 | * Setup plugin options with proper handling 227 | * 228 | * @since 1.0.0 229 | * @param array $options Plugin options to set 230 | * @return void 231 | */ 232 | public function setup_plugin_options($options) 233 | { 234 | foreach ($options as $key => $option) { 235 | // Use add_option which won't overwrite existing values 236 | $result = add_option($key, $option); 237 | 238 | 239 | } 240 | } 241 | 242 | /** 243 | * Update plugin version in database 244 | * 245 | * @since 1.0.0 246 | * @return void 247 | */ 248 | private function update_plugin_version() 249 | { 250 | $current_version = get_option('antonella_framework_version', '0.0.0'); 251 | $new_version = '1.9.0'; // Should match plugin header 252 | 253 | if (version_compare($current_version, $new_version, '<')) { 254 | update_option('antonella_framework_version', $new_version); 255 | 256 | } 257 | } 258 | 259 | /** 260 | * Show installation errors in admin notices 261 | * 262 | * @since 1.0.0 263 | * @return void 264 | */ 265 | public static function show_installation_errors() 266 | { 267 | if (!empty(self::$errors)) { 268 | echo '

'; 269 | echo esc_html(__('Antonella Framework installation encountered errors. Check error logs for details.', 'antonella-framework')); 270 | echo '

'; 271 | } 272 | } 273 | 274 | /** 275 | * Get installation errors (for debugging) 276 | * 277 | * @since 1.0.0 278 | * @return array Array of error messages 279 | */ 280 | public static function get_errors() 281 | { 282 | return self::$errors; 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /src/PostTypes.php: -------------------------------------------------------------------------------- 1 | post_types; 14 | $taxonomy_array = $config->taxonomies; 15 | if (is_array($post_type_array)) { 16 | foreach ($post_type_array as $pt) { 17 | if ($pt['singular']) { 18 | $post_type->register_post_type($pt); 19 | } 20 | } 21 | } 22 | if (is_array($taxonomy_array)) { 23 | foreach ($taxonomy_array as $tx) { 24 | if ($tx['singular']) { 25 | $post_type->add_taxonomy($tx); 26 | } 27 | } 28 | } 29 | } 30 | /** 31 | * Easy add new post_type and taxonomies 32 | * Add variables for create a new Post-Type 33 | * @version 3 34 | * @param array $pt From Config $config->post_types 35 | * @return void 36 | */ 37 | public function register_post_type($pt) 38 | { 39 | $config = new Config(); 40 | $img = explode('.', $pt['image']); 41 | $image = (isset($img[1]) && strlen($img[1]) <= 4) ? plugins_url('assets/img/' . $pt['image'], dirname(__FILE__)) : $pt['image']; 42 | $translate = $config->language_name; 43 | $singular = $pt['singular']; 44 | $plural = $pt['plural']; 45 | $slug = $pt['slug']; 46 | $taxonomy = $pt['taxonomy']; 47 | 48 | // Definición de variables de traducción 49 | $name = $pt['labels']['name'] ?? $plural; 50 | $singular_name = $pt['labels']['singular_name'] ?? $singular; 51 | $menu_name = $pt['labels']['menu_name'] ?? $singular; 52 | $all_items = $pt['labels']['all_items'] ?? $plural; 53 | // translators: %s is the singular name of the post type 54 | $view_item = sprintf(esc_html__('See %s', 'antonella-framework'), $singular); 55 | // translators: %s is the singular name of the post type 56 | $add_new_item = sprintf(esc_html__('Add %s', 'antonella-framework'), $singular); 57 | // translators: %s is the singular name of the post type 58 | $add_new = sprintf(esc_html__('Add %s', 'antonella-framework'), $singular); 59 | // translators: %s is the singular name of the post type 60 | $edit_item = sprintf(esc_html__('Edit %s', 'antonella-framework'), $singular); 61 | // translators: %s is the singular name of the post type 62 | $update_item = sprintf(esc_html__('Update %s', 'antonella-framework'), $singular); 63 | // translators: %s is the singular name of the post type 64 | $search_items = sprintf(esc_html__('Search %s', 'antonella-framework'), $singular); 65 | // translators: %s is the singular name of the post type 66 | $not_found = sprintf(esc_html__('%s not found', 'antonella-framework'), $singular); 67 | // translators: %s is the singular name of the post type 68 | $not_found_in_trash = sprintf(esc_html__('%s not found in trash', 'antonella-framework'), $singular); 69 | 70 | $labels = [ 71 | 'name' => $name, 72 | 'singular_name' => $singular_name, 73 | 'menu_name' => $menu_name, 74 | 'all_items' => $all_items, 75 | 'view_item' => $view_item, 76 | 'add_new_item' => $add_new_item, 77 | 'add_new' => $add_new, 78 | 'edit_item' => $edit_item, 79 | 'update_item' => $update_item, 80 | 'search_items' => $search_items, 81 | 'not_found' => $not_found, 82 | 'not_found_in_trash' => $not_found_in_trash, 83 | ]; 84 | 85 | $rewrite = [ 86 | 'slug' => $slug, 87 | 'with_front' => $pt['rewrite']['with_front'] ?? true, 88 | 'pages' => $pt['rewrite']['pages'] ?? true, 89 | 'feeds' => $pt['rewrite']['feeds'] ?? false, 90 | ]; 91 | 92 | // translators: %s is the singular name of the post type 93 | $description = sprintf(esc_html__('Info about %s', 'antonella-framework'), $singular); 94 | 95 | $args = [ 96 | 'label' => $pt['args']['label'] ?? $plural, 97 | 'labels' => $labels, 98 | 'description' => $description, 99 | 'supports' => $pt['args']['supports'] ?? ['title', 'editor', 'comments', 'thumbnail'], 100 | 'public' => $pt['args']['public'] ?? true, 101 | 'publicly_queryable' => $pt['args']['publicly_queryable'] ?? true, 102 | 'show_ui' => $pt['args']['show_ui'] ?? true, 103 | 'delete_with_user' => $pt['args']['delete_with_user'] ?? null, 104 | 'show_in_rest' => $pt['args']['show_in_rest'] ?? ($pt['gutemberg'] == false ? false : true), 105 | 'rest_base' => $pt['args']['rest_base'] ?? $slug, 106 | 'rest_controller_class' => $pt['args']['rest_controller_class'] ?? 'WP_REST_Posts_Controller', 107 | 'has_archive' => $pt['args']['has_archive'] ?? $slug, 108 | 'show_in_menu' => $pt['args']['show_in_menu'] ?? true, 109 | 'show_in_nav_menus' => $pt['args']['show_in_nav_menus'] ?? true, 110 | 'exclude_from_search' => $pt['args']['exclude_from_search'] ?? false, 111 | 'capability_type' => $pt['args']['capability_type'] ?? 'post', 112 | 'map_meta_cap' => $pt['args']['map_meta_cap'] ?? true, 113 | 'hierarchical' => $pt['args']['hierarchical'] ?? false, 114 | 'rewrite' => $pt['args']['rewrite'] ?? ['slug' => $slug, 'with_front' => true], 115 | 'query_var' => $pt['args']['query_var'] ?? true, 116 | 'menu_position' => $pt['args']['position'] ?? ($pt['position'] ?? 4), 117 | 'menu_icon' => $image, 118 | ]; 119 | 120 | register_post_type($slug, $args); 121 | 122 | // Registrar taxonomías 123 | if (is_array($taxonomy) && count($taxonomy) > 0) { 124 | foreach ($taxonomy as $tx) { 125 | register_taxonomy( 126 | $tx, 127 | [$slug], 128 | [ 129 | 'label' => $tx, 130 | 'show_in_rest' => true, 131 | 'show_ui' => true, 132 | 'show_admin_column' => true, 133 | 'query_var' => true 134 | ] 135 | ); 136 | } 137 | } 138 | } 139 | 140 | 141 | /** 142 | * Add taxonomies 143 | * Add variables for create a new Taxonomy 144 | * @version 1.0 145 | * @param array $tx From Config $config->post_types 146 | * @return void 147 | */ 148 | 149 | public function add_taxonomy($tx) 150 | { 151 | $config = new Config(); 152 | $labels = []; 153 | $args = []; 154 | $capabilities = []; 155 | $rewrite = []; 156 | $post_type = $tx['post_type']; 157 | $singular = $tx['singular']; 158 | $plural = $tx['plural']; 159 | $slug = $tx['slug']; 160 | $translate = $config->language_name; 161 | 162 | // Definición de variables de traducción 163 | $name = $tx['labels']['name'] ?? $plural; 164 | $singular_name = $tx['labels']['singular_name'] ?? $singular; 165 | // translators: %s is the singular name of the taxonomy 166 | $search_items = sprintf(esc_html__('Search %s', 'antonella-framework'), $singular); 167 | // translators: %s is the singular name of the taxonomy 168 | $all_items = sprintf(esc_html__('All %s', 'antonella-framework'), $singular); 169 | // translators: %s is the singular name of the taxonomy 170 | $parent_item = sprintf(esc_html__('Parent %s', 'antonella-framework'), $singular); 171 | // translators: %s is the singular name of the taxonomy 172 | $parent_item_colon = sprintf(esc_html__('Parent %s:', 'antonella-framework'), $singular); 173 | // translators: %s is the singular name of the taxonomy 174 | $edit_item = sprintf(esc_html__('Edit %s', 'antonella-framework'), $singular); 175 | // translators: %s is the singular name of the taxonomy 176 | $view_item = sprintf(esc_html__('View %s', 'antonella-framework'), $singular); 177 | // translators: %s is the singular name of the taxonomy 178 | $update_item = sprintf(esc_html__('Update %s', 'antonella-framework'), $singular); 179 | // translators: %s is the singular name of the taxonomy 180 | $add_new_item = sprintf(esc_html__('Add new %s', 'antonella-framework'), $singular); 181 | // translators: %s is the singular name of the taxonomy 182 | $new_item_name = sprintf(esc_html__('New %s', 'antonella-framework'), $singular); 183 | $menu_name = $tx['labels']['menu_name'] ?? $plural; 184 | // translators: %s is the plural name of the taxonomy 185 | $popular_items = sprintf(esc_html__('Popular %s', 'antonella-framework'), $plural); 186 | $labels = [ 187 | 'name' => $name, 188 | 'singular_name' => $singular_name, 189 | 'search_items' => $search_items, 190 | 'all_items' => $all_items, 191 | 'parent_item' => $parent_item, 192 | 'parent_item_colon' => $parent_item_colon, 193 | 'edit_item' => $edit_item, 194 | 'view_item' => $view_item, 195 | 'update_item' => $update_item, 196 | 'add_new_item' => $add_new_item, 197 | 'new_item_name' => $new_item_name, 198 | 'menu_name' => $menu_name, 199 | 'popular_items' => $popular_items, 200 | ]; 201 | 202 | $rewrite = [ 203 | 'slug' => $slug, 204 | 'with_front' => $tx['args']['rewrite']['with_front'] ?? $tx['rewrite']['with_front'] ?? true, 205 | 'hierarchical' => $tx['args']['rewrite']['hierarchical'] ?? $tx['rewrite']['hierarchical'] ?? false, 206 | 'ep_mask' => $tx['args']['rewrite']['ep_mask'] ?? $tx['rewrite']['ep_mask'] ?? EP_NONE, 207 | ]; 208 | $args = [ 209 | 'hierarchical' => $tx['args']['hierarchical'] ?? true, 210 | 'labels' => $labels, 211 | 'show_ui' => $tx['args']['show_ui'] ?? true, 212 | 'public' => $tx['args']['public'] ?? true, 213 | 'publicly_queryable' => $tx['args']['publicly_queryable'] ?? true, 214 | 'show_admin_column' => $tx['args']['show_admin_column'] ?? true, 215 | 'show_in_menu' => $tx['args']['show_in_menu'] ?? true, 216 | 'show_in_rest' => $tx['args']['show_in_rest'] ?? ($tx['gutemberg'] == false ? false : true), 217 | 'query_var' => $slug, 218 | 'rest_base' => $tx['args']['rest_base'] ?? $plural, 219 | 'rest_controller_class' => $tx['args']['rest_controller_class'] ?? 'WP_REST_Terms_Controller', 220 | 'show_tagcloud' => $tx['args']['show_tagcloud'] ?? $tx['args']['show_ui'] ?? true, 221 | 'show_in_quick_edit' => $tx['args']['show_in_quick_edit'] ?? $tx['args']['show_ui'] ?? true, 222 | 'meta_box_cb' => $tx['args']['meta_box_cb'] ?? null, 223 | 'show_in_nav_menus' => $tx['args']['show_in_nav_menus'] ?? true, 224 | 'rewrite' => $rewrite, 225 | 'capabilities' => $capabilities, 226 | 'description' => $tx['args']['description'] ?? '', 227 | ]; 228 | 229 | $capabilities = [ 230 | 'manage_terms' => $tx['args']['capabilities']['manage_terms'] ?? $tx['capabilities']['manage_terms'] ?? 'manage_' . $slug, 231 | 'edit_terms' => $tx['args']['capabilities']['edit_terms'] ?? $tx['capabilities']['edit_terms'] ?? 'manage_' . $slug, 232 | 'delete_terms' => $tx['args']['capabilities']['delete_terms'] ?? $tx['capabilities']['delete_terms'] ?? 'manage_' . $slug, 233 | 'assign_terms' => $tx['args']['capabilities']['assign_terms'] ?? $tx['capabilities']['assign_terms'] ?? 'edit_' . $slug, 234 | ]; 235 | 236 | register_taxonomy($plural, [$post_type], $args); 237 | } 238 | 239 | } -------------------------------------------------------------------------------- /antonella: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 'createZip', 74 | 'namespace' => 'changeNamespace', 75 | 'make' => 'makeController', 76 | 'helper' => 'makeHelper', 77 | 'widget' => 'makeWidget', 78 | 'remove' => 'removeModule', 79 | 'add' => 'addModule', 80 | 'help' => 'showHelp', 81 | 'serve' => 'serveDevelopment', 82 | 'test' => 'runTest', 83 | 'cpt' => 'makeCustomPostType', 84 | 'block' => 'makeGutenbergBlock', 85 | 'docker' => 'runDocker', 86 | 'updateproject' => 'updateProjectFiles' 87 | ]; 88 | 89 | if (isset($commands[$command]) && method_exists($this, $commands[$command])) { 90 | return $this->{$commands[$command]}($data); 91 | } 92 | 93 | $this->showError($this->error_message); 94 | } 95 | 96 | /** 97 | * Utility methods for better UX 98 | */ 99 | private function showError($message) 100 | { 101 | echo "\033[31m$message\033[0m\n"; 102 | exit(1); 103 | } 104 | 105 | private function showSuccess($message) 106 | { 107 | echo "\033[32m$message\033[0m\n"; 108 | } 109 | 110 | private function showInfo($message) 111 | { 112 | echo "\033[33m$message\033[0m\n"; 113 | } 114 | 115 | private function validateInput($data, $index, $errorMsg) 116 | { 117 | if (!isset($data[$index]) || trim($data[$index]) === '') { 118 | $this->showError($errorMsg); 119 | } 120 | return trim($data[$index]); 121 | } 122 | 123 | private function createDirectoryIfNotExists($path) 124 | { 125 | if (!is_dir($path)) { 126 | if (!mkdir($path, 0755, true)) { 127 | $this->showError("Could not create directory: $path"); 128 | } 129 | } 130 | } 131 | 132 | /** 133 | * Get unified exclusion directories for current OS 134 | */ 135 | private function getExcludedDirectories() 136 | { 137 | $excluded = $this->base_dirs_to_exclude; 138 | 139 | // Add OS-specific directories 140 | if (PHP_OS_FAMILY === 'Windows') { 141 | $excluded = array_merge($excluded, ['.', '..']); 142 | } 143 | 144 | // Add vendor directories with proper separator 145 | foreach ($this->vendor_dirs_to_exclude as $vendor) { 146 | // Replace forward slashes with OS-specific directory separator 147 | $vendor = str_replace('/', DIRECTORY_SEPARATOR, $vendor); 148 | $excluded[] = 'vendor' . DIRECTORY_SEPARATOR . $vendor; 149 | } 150 | 151 | return $excluded; 152 | } 153 | 154 | /** 155 | * Read current namespace from composer.json 156 | */ 157 | public function readNamespace() 158 | { 159 | $composerPath = $this->dir . "/composer.json"; 160 | 161 | if (!file_exists($composerPath)) { 162 | $this->showError("composer.json not found"); 163 | } 164 | 165 | $composer = json_decode(file_get_contents($composerPath), true); 166 | 167 | if (!isset($composer['autoload']['psr-4'])) { 168 | $this->showError("PSR-4 autoload not found in composer.json"); 169 | } 170 | 171 | $psr4 = $composer['autoload']['psr-4']; 172 | $namespace = rtrim(key($psr4), '\\'); 173 | 174 | return $namespace; 175 | } 176 | 177 | /** 178 | * Update project files after installation 179 | */ 180 | public function updateProjectFiles() 181 | { 182 | $oldFile = "antonella-framework.php"; 183 | $newFile = basename($this->dir) . '.php'; 184 | 185 | if (file_exists($oldFile)) { 186 | if (rename($oldFile, $newFile)) { 187 | $this->showSuccess("Renamed $oldFile to $newFile"); 188 | } else { 189 | $this->showError("Failed to rename $oldFile"); 190 | } 191 | } 192 | } 193 | 194 | /** 195 | * Optimized ZIP creation - unified method 196 | */ 197 | public function createZip() 198 | { 199 | $this->showInfo("Antonella is packing the plugin..."); 200 | 201 | $zipName = basename($this->dir) . '.zip'; 202 | $zipPath = $this->dir . DIRECTORY_SEPARATOR . $zipName; 203 | 204 | // Remove existing zip 205 | if (file_exists($zipPath)) { 206 | unlink($zipPath); 207 | } 208 | 209 | $zip = new ZipArchive(); 210 | if ($zip->open($zipName, ZipArchive::CREATE) !== TRUE) { 211 | $this->showError("Cannot create zip file: $zipName"); 212 | } 213 | 214 | // Use appropriate method based on OS 215 | if (PHP_OS_FAMILY === 'Windows') { 216 | $this->addFilesRecursive($zip); 217 | } else { 218 | $this->addFilesWithIterator($zip); 219 | } 220 | 221 | $zip->close(); 222 | $this->showSuccess("Plugin packed successfully: $zipName"); 223 | } 224 | 225 | /** 226 | * Add files recursively (Windows method) 227 | */ 228 | private function addFilesRecursive($zip) 229 | { 230 | $dirName = realpath($this->dir); 231 | $excludedDirs = $this->getExcludedDirectories(); 232 | 233 | if (!is_dir($dirName)) { 234 | $this->showError("Directory $dirName does not exist"); 235 | } 236 | 237 | $dirName = rtrim($dirName, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; 238 | $dirStack = [$dirName]; 239 | $cutFrom = strlen($dirName); 240 | 241 | while (!empty($dirStack)) { 242 | $currentDir = array_pop($dirStack); 243 | $filesToAdd = []; 244 | 245 | $dir = dir($currentDir); 246 | while (false !== ($node = $dir->read())) { 247 | if ($node === '.' || $node === '..' || in_array($node, $this->files_to_exclude)) { 248 | continue; 249 | } 250 | 251 | $fullPath = $currentDir . $node; 252 | $relativePath = substr($fullPath, $cutFrom); 253 | 254 | // Normalize path separators for comparison 255 | $relativePath = str_replace(DIRECTORY_SEPARATOR, '/', $relativePath); 256 | 257 | // Check if this path should be excluded based on relative path 258 | $shouldExclude = false; 259 | foreach ($excludedDirs as $excludeDir) { 260 | // Normalize exclude dir separators too 261 | $normalizedExcludeDir = str_replace(DIRECTORY_SEPARATOR, '/', $excludeDir); 262 | if (strpos($relativePath, $normalizedExcludeDir) === 0) { 263 | $shouldExclude = true; 264 | break; 265 | } 266 | } 267 | 268 | if ($shouldExclude) { 269 | continue; 270 | } 271 | 272 | if (is_dir($fullPath)) { 273 | array_push($dirStack, $fullPath . DIRECTORY_SEPARATOR); 274 | } elseif (is_file($fullPath)) { 275 | $filesToAdd[] = $node; 276 | } 277 | } 278 | $dir->close(); 279 | 280 | $localDir = substr($currentDir, $cutFrom); 281 | // Normalize directory separators for ZIP format (always use forward slashes) 282 | $localDir = str_replace(DIRECTORY_SEPARATOR, '/', $localDir); 283 | 284 | if (!empty($localDir) && $localDir !== '/') { 285 | $zip->addEmptyDir($localDir); 286 | } 287 | 288 | foreach ($filesToAdd as $file) { 289 | $localFile = $localDir . $file; 290 | // Ensure consistent path separators in ZIP 291 | $localFile = str_replace(DIRECTORY_SEPARATOR, '/', $localFile); 292 | $zip->addFile($currentDir . $file, $localFile); 293 | } 294 | } 295 | } 296 | 297 | /** 298 | * Add files with iterator (Linux method) 299 | */ 300 | private function addFilesWithIterator($zip) 301 | { 302 | $dirName = realpath($this->dir); 303 | $excludedDirs = $this->getExcludedDirectories(); 304 | 305 | $files = new RecursiveIteratorIterator( 306 | new RecursiveDirectoryIterator($dirName), 307 | RecursiveIteratorIterator::LEAVES_ONLY 308 | ); 309 | 310 | foreach ($files as $name => $file) { 311 | if (!$file->isDir() && !in_array($file->getFilename(), $this->files_to_exclude)) { 312 | $filePath = $file->getRealPath(); 313 | $relativePath = substr($filePath, strlen($dirName) + 1); 314 | $zip->addFile($filePath, $relativePath); 315 | } 316 | } 317 | 318 | // Remove excluded directories 319 | for ($i = 0; $i < $zip->numFiles; $i++) { 320 | $entry = $zip->statIndex($i); 321 | foreach ($excludedDirs as $excludeDir) { 322 | if (strpos($entry['name'], $excludeDir) === 0) { 323 | $zip->deleteIndex($i); 324 | break; 325 | } 326 | } 327 | } 328 | } 329 | 330 | /** 331 | * Change namespace across all project files 332 | */ 333 | public function changeNamespace($data) 334 | { 335 | $this->showInfo("Renaming the namespace..."); 336 | 337 | $newNamespace = isset($data[2]) && trim($data[2]) !== '' 338 | ? strtoupper(trim($data[2])) 339 | : $this->generateRandomString(6); 340 | 341 | $currentNamespace = $this->readNamespace(); 342 | 343 | // Update composer.json 344 | $this->updateFileContent( 345 | $this->dir . DIRECTORY_SEPARATOR . "composer.json", 346 | $currentNamespace, 347 | $newNamespace 348 | ); 349 | 350 | // Update main plugin file 351 | $this->updateFileContent( 352 | $this->dir . DIRECTORY_SEPARATOR . "antonella-framework.php", 353 | $currentNamespace, 354 | $newNamespace 355 | ); 356 | 357 | // Update all PHP files in src directory 358 | $this->updateDirectoryFiles( 359 | $this->dir . DIRECTORY_SEPARATOR . "src", 360 | $currentNamespace, 361 | $newNamespace 362 | ); 363 | 364 | // Regenerate autoload 365 | exec("composer dump-autoload"); 366 | 367 | $this->showSuccess("Namespace changed to: $newNamespace"); 368 | exit(0); 369 | } 370 | 371 | /** 372 | * Update file content with namespace replacement 373 | */ 374 | private function updateFileContent($filePath, $oldNamespace, $newNamespace) 375 | { 376 | if (!file_exists($filePath)) { 377 | return; 378 | } 379 | 380 | $content = file_get_contents($filePath); 381 | $content = str_replace($oldNamespace, $newNamespace, $content); 382 | file_put_contents($filePath, $content); 383 | } 384 | 385 | /** 386 | * Update all PHP files in directory recursively 387 | */ 388 | private function updateDirectoryFiles($dirPath, $oldNamespace, $newNamespace) 389 | { 390 | if (!is_dir($dirPath)) { 391 | return; 392 | } 393 | 394 | $iterator = new RecursiveIteratorIterator( 395 | new RecursiveDirectoryIterator($dirPath) 396 | ); 397 | 398 | foreach ($iterator as $file) { 399 | if ($file->isFile() && $file->getExtension() === 'php') { 400 | $this->updateFileContent($file->getPathname(), $oldNamespace, $newNamespace); 401 | } 402 | } 403 | } 404 | 405 | /** 406 | * Template generation system 407 | */ 408 | private function getTemplate($type, $className, $namespace) 409 | { 410 | $templates = [ 411 | 'controller' => $this->getControllerTemplate($className, $namespace), 412 | 'helper' => $this->getHelperTemplate($className), 413 | 'widget' => $this->getWidgetTemplate($className, $namespace) 414 | ]; 415 | 416 | return $templates[$type] ?? ''; 417 | } 418 | 419 | private function getControllerTemplate($className, $namespace) 420 | { 421 | return " strtolower('$className'), 465 | 'description' => '$className widget description' 466 | ]; 467 | 468 | public \$form_values = [ 469 | 'title' => 'Widget Title', 470 | // Add more default values as needed 471 | ]; 472 | 473 | public function __construct() 474 | { 475 | parent::__construct('$className', \$this->name_widget, \$this->options); 476 | } 477 | 478 | public function form(\$instance) 479 | { 480 | \$instance = wp_parse_args((array) \$instance, \$this->form_values); 481 | \$html = ''; 482 | 483 | foreach (\$instance as \$key => \$value) { 484 | \$field_id = \$this->get_field_id(\$key); 485 | \$field_name = \$this->get_field_name(\$key); 486 | \$html .= \"

\"; 487 | \$html .= \"

\"; 488 | } 489 | 490 | echo \$html; 491 | } 492 | 493 | public function update(\$new_instance, \$old_instance) 494 | { 495 | \$instance = \$old_instance; 496 | foreach (\$new_instance as \$key => \$value) { 497 | \$instance[\$key] = sanitize_text_field(\$value); 498 | } 499 | return \$instance; 500 | } 501 | 502 | public function widget(\$args, \$instance) 503 | { 504 | extract(\$args); 505 | 506 | echo \$before_widget; 507 | 508 | if (!empty(\$instance['title'])) { 509 | echo \$before_title . apply_filters('widget_title', \$instance['title']) . \$after_title; 510 | } 511 | 512 | // Widget output logic here 513 | 514 | echo \$after_widget; 515 | } 516 | }"; 517 | } 518 | 519 | /** 520 | * Generate controller file 521 | */ 522 | public function makeController($data) 523 | { 524 | $className = $this->validateInput($data, 2, "Controller name is required"); 525 | $namespace = $this->readNamespace(); 526 | 527 | $controllerDir = $this->dir . DIRECTORY_SEPARATOR . "src" . DIRECTORY_SEPARATOR . "Controllers"; 528 | $this->createDirectoryIfNotExists($controllerDir); 529 | 530 | $filePath = $controllerDir . DIRECTORY_SEPARATOR . "$className.php"; 531 | $template = $this->getTemplate('controller', $className, $namespace); 532 | 533 | if (file_put_contents($filePath, $template)) { 534 | $this->showSuccess("Controller $className.php created in src/Controllers/"); 535 | } else { 536 | $this->showError("Failed to create controller file"); 537 | } 538 | 539 | exit(0); 540 | } 541 | 542 | /** 543 | * Generate widget file 544 | */ 545 | public function makeWidget($data) 546 | { 547 | $className = $this->validateInput($data, 2, "Widget name is required"); 548 | $namespace = $this->readNamespace(); 549 | 550 | $filePath = $this->dir . DIRECTORY_SEPARATOR . "src" . DIRECTORY_SEPARATOR . "$className.php"; 551 | $template = $this->getTemplate('widget', $className, $namespace); 552 | 553 | if (file_put_contents($filePath, $template)) { 554 | $this->showSuccess("Widget $className.php created in src/"); 555 | } else { 556 | $this->showError("Failed to create widget file"); 557 | } 558 | 559 | exit(0); 560 | } 561 | 562 | /** 563 | * Generate helper file 564 | */ 565 | public function makeHelper($data) 566 | { 567 | $functionName = $this->validateInput($data, 2, "Helper function name is required"); 568 | 569 | $helperDir = $this->dir . DIRECTORY_SEPARATOR . "src" . DIRECTORY_SEPARATOR . "Helpers"; 570 | $this->createDirectoryIfNotExists($helperDir); 571 | 572 | $filePath = $helperDir . DIRECTORY_SEPARATOR . "$functionName.php"; 573 | $template = $this->getTemplate('helper', $functionName, ''); 574 | 575 | if (file_put_contents($filePath, $template)) { 576 | $this->showSuccess("Helper $functionName.php created in src/Helpers/"); 577 | } else { 578 | $this->showError("Failed to create helper file"); 579 | } 580 | 581 | exit(0); 582 | } 583 | 584 | /** 585 | * Docker environment management 586 | */ 587 | public function runDocker($data) 588 | { 589 | $this->showInfo("Starting Docker environment..."); 590 | $command = 'docker-compose up' . (isset($data[2]) && $data[2] === '-d' ? ' -d' : ''); 591 | exec($command); 592 | $this->showSuccess("Docker environment started!"); 593 | } 594 | 595 | /** 596 | * Module management 597 | */ 598 | public function removeModule($data) 599 | { 600 | $module = $data[2] ?? ''; 601 | 602 | switch ($module) { 603 | case 'blade': 604 | return $this->removeBlade(); 605 | case 'dd': 606 | return $this->removeDD(); 607 | case 'model': 608 | return $this->removeModel(); 609 | default: 610 | $this->showError("Module '$module' not supported for removal. Available: blade, dd, model"); 611 | } 612 | } 613 | 614 | public function addModule($data) 615 | { 616 | $module = $data[2] ?? ''; 617 | 618 | switch ($module) { 619 | case 'blade': 620 | return $this->addBlade(); 621 | case 'dd': 622 | return $this->addDD(); 623 | case 'model': 624 | return $this->addModel(); 625 | case 'action': 626 | return $this->showActions($data); 627 | default: 628 | $this->showError("Module '$module' not supported. Available: blade, dd, model, action"); 629 | } 630 | } 631 | 632 | private function removeBlade() 633 | { 634 | $this->showInfo("Removing Blade template engine..."); 635 | exec('composer remove jenssegers/blade'); 636 | $this->showSuccess("Blade removed successfully!"); 637 | } 638 | 639 | private function addBlade() 640 | { 641 | echo "Add Blade template engine? Type 'yes' or 'y' to continue: "; 642 | $handle = fopen("php://stdin", "r"); 643 | $response = trim(fgets($handle)); 644 | fclose($handle); 645 | 646 | if (in_array(strtolower($response), ['yes', 'y'])) { 647 | $this->showInfo("Adding Blade template engine..."); 648 | exec('composer require jenssegers/blade'); 649 | $this->showSuccess("Blade added successfully!"); 650 | } else { 651 | $this->showInfo("Operation cancelled"); 652 | } 653 | } 654 | 655 | /** 656 | * Add Symfony Var-Dumper (dd function) for debugging 657 | */ 658 | private function addDD() 659 | { 660 | $this->showInfo("🐛 Symfony Var-Dumper Installation"); 661 | $this->showInfo("Adding powerful debugging tools (dd() function)..."); 662 | echo "\n"; 663 | 664 | $this->showInfo("📦 Installing symfony/var-dumper as dev dependency..."); 665 | 666 | // Simple progress indicator 667 | echo "Installing"; 668 | for ($i = 0; $i < 3; $i++) { 669 | sleep(1); 670 | echo "."; 671 | } 672 | echo "\n"; 673 | 674 | exec('composer require symfony/var-dumper --dev 2>&1', $composerOutput, $returnCode); 675 | 676 | if ($returnCode === 0) { 677 | $this->showSuccess("✅ Symfony Var-Dumper successfully installed!"); 678 | $this->showInfo("🎯 You can now use dd() and dump() functions for debugging"); 679 | $this->showInfo("💡 Example: dd(\$variable); // Dies and dumps the variable"); 680 | } else { 681 | $this->showError("❌ Installation failed. Please check your composer configuration."); 682 | $this->showInfo("Composer output:"); 683 | foreach ($composerOutput as $line) { 684 | echo " $line\n"; 685 | } 686 | } 687 | } 688 | 689 | /** 690 | * Add WordPress Eloquent Models 691 | */ 692 | private function addModel() 693 | { 694 | $this->showInfo("🗃️ WordPress Eloquent Models Installation"); 695 | $this->showInfo("Adding powerful Eloquent ORM models for WordPress..."); 696 | echo "\n"; 697 | 698 | echo "Add WordPress Eloquent Models? Type 'yes' or 'y' to continue: "; 699 | $handle = fopen("php://stdin", "r"); 700 | $response = trim(fgets($handle)); 701 | fclose($handle); 702 | 703 | if (!in_array(strtolower($response), ['yes', 'y'])) { 704 | $this->showInfo("⚠️ Installation cancelled by user"); 705 | $this->showInfo("💡 Tip: Run 'php antonella add model' anytime to install WordPress Eloquent Models"); 706 | return; 707 | } 708 | 709 | echo "\n"; 710 | $this->showInfo("📦 Installing antonella-framework/wordpress-eloquent-models via Composer..."); 711 | 712 | // Simple progress indicator 713 | echo "Installing"; 714 | for ($i = 0; $i < 3; $i++) { 715 | sleep(1); 716 | echo "."; 717 | } 718 | echo "\n"; 719 | 720 | exec('composer require antonella-framework/wordpress-eloquent-models 2>&1', $composerOutput, $returnCode); 721 | 722 | if ($returnCode === 0) { 723 | $this->showSuccess("✅ WordPress Eloquent Models successfully installed!"); 724 | $this->showInfo("📚 You can now use Eloquent ORM models for WordPress data"); 725 | $this->showInfo("💡 Tip: Use models like User, Post, Comment, etc. with Eloquent syntax"); 726 | } else { 727 | $this->showError("❌ Installation failed. Please check your composer configuration."); 728 | $this->showInfo("💡 Make sure the package antonella-framework/wordpress-eloquent-models exists"); 729 | $this->showInfo("Composer output:"); 730 | foreach ($composerOutput as $line) { 731 | echo " $line\n"; 732 | } 733 | } 734 | } 735 | 736 | private function showActions($data) 737 | { 738 | if (!isset($data[3])) { 739 | $this->showError("Additional parameter required"); 740 | } 741 | 742 | $configPath = $this->dir . DIRECTORY_SEPARATOR . "src" . DIRECTORY_SEPARATOR . "Config.php"; 743 | if (!file_exists($configPath)) { 744 | $this->showError("Config.php not found"); 745 | } 746 | 747 | include($configPath); 748 | $namespace = $this->readNamespace(); 749 | $configClass = $namespace . "\\Config"; 750 | 751 | if (class_exists($configClass)) { 752 | $config = new $configClass; 753 | print_r($config->add_action); 754 | } 755 | } 756 | 757 | /** 758 | * Development server 759 | */ 760 | public function serveDevelopment($data) 761 | { 762 | $this->showInfo("Checking Docker installation..."); 763 | 764 | $dockerCheck = shell_exec('docker --version 2>&1'); 765 | if (empty($dockerCheck) || strpos($dockerCheck, 'not recognized') !== false) { 766 | $this->showError("Docker is not installed. Please install from https://www.docker.com/products/docker-desktop"); 767 | } 768 | 769 | $dockerPs = shell_exec('docker ps 2>&1'); 770 | if ( 771 | strpos($dockerPs, 'error during connect') !== false || 772 | strpos($dockerPs, 'Cannot connect to the Docker daemon') !== false 773 | ) { 774 | $this->showError("Docker is not running. Please start Docker Desktop and try again."); 775 | } 776 | 777 | $this->showSuccess("Docker is ready!"); 778 | $this->showInfo("Starting development environment..."); 779 | 780 | $command = 'docker-compose up' . (isset($data[2]) && $data[2] === '-d' ? ' -d' : ''); 781 | system($command); 782 | 783 | $this->showSuccess("Development environment is ready!"); 784 | } 785 | 786 | /** 787 | * Test management 788 | */ 789 | public function runTest($data) 790 | { 791 | $subCommand = $data[2] ?? ''; 792 | 793 | switch ($subCommand) { 794 | case 'refresh': 795 | return $this->refreshTestEnvironment($data); 796 | default: 797 | $this->showError("Test subcommand '$subCommand' not recognized"); 798 | } 799 | } 800 | 801 | private function refreshTestEnvironment($data) 802 | { 803 | // Test environment refresh logic would go here 804 | // This is a complex method that would need the original implementation 805 | $this->showInfo("Test environment refresh functionality needs implementation"); 806 | } 807 | 808 | /** 809 | * Custom Post Type creation 810 | */ 811 | public function makeCustomPostType($data) 812 | { 813 | $postTypeName = $this->validateInput($data, 2, "Custom Post Type name is required"); 814 | 815 | $configPath = $this->dir . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'Config.php'; 816 | 817 | if (!file_exists($configPath)) { 818 | $this->showError("Config.php not found"); 819 | } 820 | 821 | $content = file_get_contents($configPath); 822 | $lines = explode("\n", $content); 823 | 824 | foreach ($lines as $i => $line) { 825 | if (strpos($line, 'public $post_types = [') !== false) { 826 | $lines[$i] = ' public $post_types = [ 827 | [ 828 | "singular" => "' . $postTypeName . '", 829 | "plural" => "' . $postTypeName . 's", 830 | "slug" => "' . strtolower($postTypeName) . '", 831 | "position" => 99, 832 | "taxonomy" => [], 833 | "image" => "antonella-icon.png", 834 | "gutenberg" => true 835 | ],'; 836 | break; 837 | } 838 | } 839 | 840 | file_put_contents($configPath, implode("\n", $lines)); 841 | $this->showSuccess("Custom Post Type '$postTypeName' added to Config.php"); 842 | } 843 | 844 | /** 845 | * Gutenberg block creation 846 | */ 847 | public function makeGutenbergBlock($data) 848 | { 849 | $blockName = $this->validateInput($data, 2, "Gutenberg block name is required"); 850 | 851 | $jsContent = $this->getGutenbergBlockJS($blockName); 852 | $cssContent = $this->getGutenbergBlockCSS($blockName); 853 | 854 | $jsPath = $this->dir . DIRECTORY_SEPARATOR . "assets" . DIRECTORY_SEPARATOR . "js" . DIRECTORY_SEPARATOR . "$blockName.js"; 855 | $cssPath = $this->dir . DIRECTORY_SEPARATOR . "assets" . DIRECTORY_SEPARATOR . "css" . DIRECTORY_SEPARATOR . "$blockName.css"; 856 | 857 | file_put_contents($jsPath, $jsContent); 858 | file_put_contents($cssPath, $cssContent); 859 | 860 | $this->addBlockToConfig($blockName); 861 | 862 | $this->showSuccess("Gutenberg block '$blockName' created successfully"); 863 | } 864 | 865 | private function getGutenbergBlockJS($blockName) 866 | { 867 | return "/* Gutenberg Block: $blockName */ 868 | wp.blocks.registerBlockType('antonella/$blockName', { 869 | title: '$blockName', 870 | icon: 'smiley', 871 | category: 'common', 872 | attributes: { 873 | content: {type: 'string'}, 874 | color: {type: 'string'} 875 | }, 876 | 877 | edit: function(props) { 878 | function updateContent(event) { 879 | props.setAttributes({content: event.target.value}); 880 | } 881 | 882 | function updateColor(value) { 883 | props.setAttributes({color: value.hex}); 884 | } 885 | 886 | return React.createElement( 887 | 'div', 888 | null, 889 | React.createElement('h3', null, '$blockName Block'), 890 | React.createElement('input', { 891 | type: 'text', 892 | value: props.attributes.content, 893 | onChange: updateContent 894 | }), 895 | React.createElement(wp.components.ColorPicker, { 896 | color: props.attributes.color, 897 | onChangeComplete: updateColor 898 | }) 899 | ); 900 | }, 901 | 902 | save: function(props) { 903 | return wp.element.createElement( 904 | 'h3', 905 | {style: {border: '3px solid ' + props.attributes.color}}, 906 | props.attributes.content 907 | ); 908 | } 909 | });"; 910 | } 911 | 912 | private function getGutenbergBlockCSS($blockName) 913 | { 914 | return "/* CSS for $blockName Gutenberg Block */ 915 | .wp-block-antonella-" . strtolower($blockName) . " { 916 | /* Add your custom styles here */ 917 | }"; 918 | } 919 | 920 | private function addBlockToConfig($blockName) 921 | { 922 | $configPath = $this->dir . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'Config.php'; 923 | 924 | if (!file_exists($configPath)) { 925 | return; 926 | } 927 | 928 | $content = file_get_contents($configPath); 929 | $lines = explode("\n", $content); 930 | 931 | foreach ($lines as $i => $line) { 932 | if (strpos($line, 'public $gutenberg_blocks = [') !== false) { 933 | $lines[$i] = ' public $gutenberg_blocks = [ 934 | "' . $blockName . '",'; 935 | break; 936 | } 937 | } 938 | 939 | file_put_contents($configPath, implode("\n", $lines)); 940 | } 941 | 942 | /** 943 | * Generate random string for namespace 944 | */ 945 | private function generateRandomString($length) 946 | { 947 | $characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; 948 | $result = ''; 949 | 950 | for ($i = 0; $i < $length; $i++) { 951 | $result .= $characters[mt_rand(0, strlen($characters) - 1)]; 952 | } 953 | 954 | return $result; 955 | } 956 | 957 | /** 958 | * Display help information 959 | */ 960 | public function showHelp() 961 | { 962 | $this->displayLogo(); 963 | echo "\n"; 964 | $this->showInfo("Usage:"); 965 | echo "\033[37m php antonella [option] [name or value]\033[0m\n\n"; 966 | 967 | echo "\033[33mAvailable Commands:\033[0m\n"; 968 | echo "\033[32m namespace [name]\033[0m Generate or regenerate namespace\n"; 969 | echo "\033[32m makeup\033[0m Create plugin ZIP file\n"; 970 | echo "\033[32m make [name]\033[0m Generate controller class\n"; 971 | echo "\033[32m helper [name]\033[0m Generate helper function\n"; 972 | echo "\033[32m widget [name]\033[0m Generate widget class\n"; 973 | echo "\033[32m cpt [name]\033[0m Generate custom post type\n"; 974 | echo "\033[32m block [name]\033[0m Generate Gutenberg block\n"; 975 | echo "\033[32m serve [-d]\033[0m Start development server\n"; 976 | echo "\033[32m add [module]\033[0m Add framework modules (blade, dd, model)\n"; 977 | echo "\033[32m remove [module]\033[0m Remove framework modules\n"; 978 | echo "\033[32m help\033[0m Show this help message\n\n"; 979 | 980 | echo "\033[37mDocumentation: \033[33mhttps://antonellaframework.com\033[0m\n"; 981 | echo "\033[37mVideo Tutorial: \033[33mhttps://tipeos.com/anto\033[0m\n"; 982 | } 983 | 984 | private function displayLogo() 985 | { 986 | echo "\033[33m*******************************************************************\033[0m\n"; 987 | echo "\033[33m*******************************************************************\033[0m\n"; 988 | echo "\033[33m*** _ _ _ ***\033[0m\n"; 989 | echo "\033[33m*** /\\ | | | | | ***\033[0m\n"; 990 | echo "\033[33m*** / \\ _ __ | |_ ___ _ __ ___| | | __ _ ***\033[0m\n"; 991 | echo "\033[33m*** / /\\ \\ | '_ \\| __/ _ \\| '_ \\ / _ \\ | |/ _` | ***\033[0m\n"; 992 | echo "\033[33m*** / ____ \\| | | | || (_) | | | | __/ | | (_| | ***\033[0m\n"; 993 | echo "\033[33m*** /_/____\\_\\_| |_|\\__\\___/|_| |_|\\___|_|_|\\__,_| _ ***\033[0m\n"; 994 | echo "\033[33m*** | ____| | | ***\033[0m\n"; 995 | echo "\033[33m*** | |__ _ __ __ _ _ __ ___ _____ _____ _ __| | __ ***\033[0m\n"; 996 | echo "\033[33m*** | __| '__/ _` | '_ ` _ \\ / _ \\ \\ /\\ / / _ \\| '__| |/ / ***\033[0m\n"; 997 | echo "\033[33m*** | | | | | (_| | | | | | | __/\\ V V / (_) | | | < ***\033[0m\n"; 998 | echo "\033[33m*** |_| |_| \\__,_|_| |_| |_|\\___| \\_/\\_/ \\___/|_| |_|\\_\\ ***\033[0m\n"; 999 | echo "\033[33m*** ***\033[0m\n"; 1000 | echo "\033[33m*******************************************************************\033[0m\n"; 1001 | echo "\033[33m*******************************************************************\033[0m\n"; 1002 | } 1003 | } 1004 | 1005 | // Execute Antonella CLI 1006 | $antonella = new Antonella(); 1007 | exit($antonella->process($argv)); --------------------------------------------------------------------------------