';
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 |
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 | 
4 |
5 | [](https://deepwiki.com/cehojac/antonella-framework-for-wp)
6 |
7 | [](https://packagist.org/packages/cehojac/antonella-framework-for-wp)
8 | [](https://packagist.org/packages/cehojac/antonella-framework-for-wp)
9 | [](https://packagist.org/packages/cehojac/antonella-framework-for-wp)
10 | [](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 |
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));
--------------------------------------------------------------------------------