From 6eeb521427b7b41610e60bec2fe5e6e8d89be5d4 Mon Sep 17 00:00:00 2001 From: Brede Basualdo Date: Tue, 25 Mar 2025 13:45:42 -0300 Subject: [PATCH] - updated bones to use current - add support for get textdomain from config/options.php - add transversal support for WPBONES_TEXTDOMAIN --- bones | 1631 +++++++++++++++++++++++----------------- bootstrap/autoload.php | 6 + config/options.php | 1 + 3 files changed, 966 insertions(+), 672 deletions(-) diff --git a/bones b/bones index 588db73..2827670 100755 --- a/bones +++ b/bones @@ -445,6 +445,181 @@ namespace Bones\SemVer { } } +namespace Bones\Traits { + + // Standard Color Definitions + define('WPBONES_COLOR_BLACK', "\033[0;30m"); + define('WPBONES_COLOR_RED', "\033[0;31m"); + define('WPBONES_COLOR_GREEN', "\033[0;32m"); + define('WPBONES_COLOR_YELLOW', "\033[0;33m"); + define('WPBONES_COLOR_BLUE', "\033[0;34m"); + define('WPBONES_COLOR_MAGENTA', "\033[0;35m"); + define('WPBONES_COLOR_CYAN', "\033[0;36m"); + define('WPBONES_COLOR_WHITE', "\033[0;37m"); + + // Definition of bold colors + define('WPBONES_COLOR_BOLD_BLACK', "\033[1;30m"); + define('WPBONES_COLOR_BOLD_RED', "\033[1;31m"); + define('WPBONES_COLOR_BOLD_GREEN', "\033[1;32m"); + define('WPBONES_COLOR_BOLD_YELLOW', "\033[1;33m"); + define('WPBONES_COLOR_BOLD_BLUE', "\033[1;34m"); + define('WPBONES_COLOR_BOLD_MAGENTA', "\033[1;35m"); + define('WPBONES_COLOR_BOLD_CYAN', "\033[1;36m"); + define('WPBONES_COLOR_BOLD_WHITE', "\033[1;37m"); + + // Definition of Light Colors + define('WPBONES_COLOR_LIGHT_BLACK', "\033[0;38;5;240m"); + define('WPBONES_COLOR_LIGHT_RED', "\033[0;38;5;203m"); + define('WPBONES_COLOR_LIGHT_GREEN', "\033[0;38;5;82m"); + define('WPBONES_COLOR_LIGHT_YELLOW', "\033[0;38;5;227m"); + define('WPBONES_COLOR_LIGHT_BLUE', "\033[0;38;5;117m"); + define('WPBONES_COLOR_LIGHT_MAGENTA', "\033[0;38;5;213m"); + define('WPBONES_COLOR_LIGHT_CYAN', "\033[0;38;5;159m"); + define('WPBONES_COLOR_LIGHT_WHITE', "\033[0;38;5;15m"); + + // Definition for color reset + define('WPBONES_COLOR_RESET', "\033[0m"); + + // Definition for cursor up + define('WPBONES_CURSOR_UP', "\033[1A"); + + trait Console + { + /** + * Commodity to display a message in the console with color. + * + * @param string $str The message to display. + * @param string $color The color to use. Default is 'white'. + */ + protected function color($str, $color = WPBONES_COLOR_YELLOW) + { + echo $color . $str . WPBONES_COLOR_RESET; + } + + /** + * Commodity to display a message in the console. + * + * @param string $str The message to display. + * @param bool $newLine Optional. Whether to add a new line at the end. + */ + protected function info(string $str, $newLine = true) + { + $this->color($str, WPBONES_COLOR_LIGHT_MAGENTA); + echo $newLine ? "\n" : ''; + } + + /** + * Commodity to display a message in the console. + * + * @param string $str The message to display. + * @param bool $newLine Optional. Whether to add a new line at the end. + */ + protected function line(string $str, $newLine = true) + { + $this->color($str, WPBONES_COLOR_LIGHT_GREEN); + echo $newLine ? "\n" : ''; + } + + /* Commodity to display an error message in the console. */ + protected function error(string $str, $newLine = true) + { + echo "❌ "; + $this->color($str, WPBONES_COLOR_BOLD_RED); + echo $newLine ? "\n" : ''; + } + + /* Commodity to display an info message in the console. */ + protected function warning(string $str, $newLine = true) + { + echo "❗ "; + $this->color($str, WPBONES_COLOR_BOLD_YELLOW); + echo $newLine ? "\n" : ''; + } + + /* Commodity to display a success message in the console. */ + protected function success($str) + { + $this->color("✅ " . $str, WPBONES_COLOR_GREEN); + } + + protected function startCommand($str) + { + $this->color("------------------------------------------------" . "\n", WPBONES_COLOR_BLUE); + $this->color("🙌 " . $str . " " . $this->getPluginName() . "\n", WPBONES_COLOR_BLUE); + $this->color("------------------------------------------------" . "\n", WPBONES_COLOR_BLUE); + } + + /* Commodity to display a start progress message in the console. */ + protected function startProgress($str) + { + $this->color("🚧 " . $str . "\n", WPBONES_COLOR_GREEN); + } + + /* Commodity to replace a previous progress message in the console with cursor up. */ + protected function endProgress() + { + echo WPBONES_CURSOR_UP . WPBONES_COLOR_GREEN . "✅" . WPBONES_COLOR_RESET . "\n"; + } + + /* Commodity to display a completed message in the console. */ + protected function processCompleted($str) + { + $this->color("✅ " . $str . "\n", WPBONES_COLOR_GREEN); + } + + /** + * Get input from console + * + * @param string $str The question to ask + * @param string|null $default The default value + */ + protected function ask(string $str, ?string $default = '',?string $example = ''): string + { + + $str = WPBONES_COLOR_GREEN . + "❓ $str" . + (empty($example) ? '' : " ie: {$example}") . + (empty($default) ? ': ' : " (default: {$default}): ") . + WPBONES_COLOR_RESET; + + // Use readline to get the user input + $line = readline($str); + + // Trim the input to remove extra spaces or newlines + $line = trim($line); + + return $line ?: $default; + } + /** + * Get input from console + * + * @param string $str The question to ask + * @param string|null $default The default value + */ + protected function askYesNo(string $str, bool $default = true): string + { + + $str = WPBONES_COLOR_GREEN . + "❓ $str (".($default?"Y/n":"y/N").")" . + " (default: ".($default?"Yes":"No")."): " . + WPBONES_COLOR_RESET; + + // Use readline to get the user input + $line = readline($str); + + // Trim the input to remove extra spaces or newlines + $line = strtoupper(trim($line)); + if($line=="Y"||$line=="YES"){ + return "true"; + }else if($line=="N"||$line=="NO"){ + return "false"; + }else{ + return $default?"true":"false"; + } + } + } +} + /** * Bones * @@ -456,7 +631,7 @@ namespace Bones { define('WPBONES_MINIMAL_PHP_VERSION', '7.4'); /* MARK: The WP Bones command line version. */ - define('WPBONES_COMMAND_LINE_VERSION', '1.8.0'); + define('WPBONES_COMMAND_LINE_VERSION', '1.9.1'); use Bones\SemVer\Exceptions\InvalidVersionException; use Bones\SemVer\Version; @@ -475,12 +650,9 @@ namespace Bones { } if (version_compare(PHP_VERSION, WPBONES_MINIMAL_PHP_VERSION) < 0) { - echo "\n\033[33;5;82mWarning!!\n"; - echo "\n\033[38;5;82m\t" . - 'You must run with PHP version ' . - WPBONES_MINIMAL_PHP_VERSION . - ' or greater'; - echo "\033[0m\n\n"; + echo WPBONES_COLOR_BOLD_RED . + "❌ Error! You must run with PHP version " . WPBONES_MINIMAL_PHP_VERSION . " or greater\n" . + WPBONES_COLOR_RESET; exit(); } @@ -489,6 +661,8 @@ namespace Bones { */ class BonesCommandLine { + use Traits\Console; + /** * WP Bones version */ @@ -554,10 +728,10 @@ namespace Bones { $this->help(); } // rename elseif ($this->isCommand('rename')) { - $this->rename($this->commandParams()); + $this->rename($this->getCommandParams()); } // install elseif ($this->isCommand('install')) { - $this->install($this->commandParams()); + $this->install($this->getCommandParams()); } // Update elseif ($this->isCommand('update')) { $this->update(); @@ -576,7 +750,7 @@ namespace Bones { * * @return mixed|array */ - protected function arguments(int $index = null): ?array + protected function arguments($index = null) { // Check if 'argv' is set in the server variables if (!isset($_SERVER['argv'])) { @@ -606,26 +780,18 @@ namespace Bones { { try { - /** - * MARK: Load WordPress - * We have to load the WordPress environment. - */ + // We have to load the WordPress environment. $currentDir = $_SERVER['PWD'] ?? __DIR__; $wpLoadPath = dirname(dirname(dirname($currentDir))) . '/wp-load.php'; if (!file_exists($wpLoadPath)) { - echo "\n\033[33;5;82mWarning!!\n"; - echo "\n\033[38;5;82m\t" . - 'You must be inside "wp-content/plugins/" folders'; - echo "\033[0m\n\n"; + $this->error("Error! You need to be inside the wp-content/plugins/ folder"); exit(); } require $wpLoadPath; } catch (Exception $e) { - echo "\n\033[33;5;82mWarning!!\n"; - echo "\n\033[38;5;82m\t" . 'Can\'t load WordPress'; - echo "\033[0m\n\n"; + $this->error("Error! Can't load WordPress (" . $e->getMessage() . ")"); } try { @@ -641,8 +807,8 @@ namespace Bones { require __DIR__ . '/vendor/autoload.php'; } } catch (Exception $e) { - echo "\n\033[33;5;82mWarning!!\n"; - echo "\n\033[38;5;82m\t" . 'Can\'t load Composer'; + $this->error("Error! Can't load Composer autoload (" . $e->getMessage() . ")"); + exit(); } try { @@ -655,8 +821,8 @@ namespace Bones { require_once __DIR__ . '/bootstrap/plugin.php'; } } catch (Exception $e) { - echo "\n\033[33;5;82mWarning!!\n"; - echo "\n\033[38;5;82m\t" . 'Can\'t load this plugin env'; + $this->error("Error! Can't load the plugin env (" . $e->getMessage() . ")"); + exit(); } } @@ -674,11 +840,89 @@ namespace Bones { $this->kernel = new $kernelClass(); } } catch (Exception $e) { - echo "\n\033[33;5;82mWarning!!\n"; - echo "\n\033[38;5;82m\t" . 'Can\'t load console kernel'; + $this->error("Error! Can't load kernel console (" . $e->getMessage() . ")"); + exit(); } } + /** + * Commodity function to check if ClassName has been requested. + * + * @param string|null $className Optional. Command to check. + * + * @return string + */ + protected function askClassNameIfEmpty($className = ''): string + { + if (empty($className)) { + $className = $this->ask('ClassName'); + if (empty($className)) { + $this->error('ClassName is required'); + exit(0); + } + } + + return $className; + } + + /** + * Ask the user which package manager to use + * + * @since 1.9.0 + * @return string|null The name of the package manager to use + */ + protected function askPackageManager() + { + $packageManager = null; + + // get the available package manager + $listPackageManagers = $this->getListAvailablePackageManagers(); + + if ($listPackageManagers) { + // if we found just one package manager, we use it + if (count($listPackageManagers) === 1) { + return $listPackageManagers[0]; + } + + $availablePackageManagerStr = implode(', ', $listPackageManagers); + $this->info("🔍 Found package managers: " . $availablePackageManagerStr); + + // check if "npm" is available and use it by default + if (in_array('npm', $listPackageManagers)) { + $packageManager = 'npm'; + } + + // ask which package manager to use + $packageManager = $this->ask('Which package manager do you want to use (' . $availablePackageManagerStr . ')', $packageManager); + } + + return $packageManager; + } + + /** + * Return the params after "php bones [command]". + * + * @param int|null $index Optional. Index of param. + * If NULL will be returned the whole array. + * + * @return array|string + */ + protected function getCommandParams($index = null) + { + $params = $this->arguments(); + + // strip the command name + array_shift($params); + + return !is_null($index) ? $params[$index] ?? null : $params; + } + + /* Return the default plugin name and namespace. */ + protected function getDefaultPluginNameAndNamespace(): array + { + return ['WP Kirk', 'WPKirk']; + } + /** * Return the current Plugin namespace defined in the namespace file. * @@ -691,6 +935,70 @@ namespace Bones { return $namespace; } + /** + * Return the default Plugin filename as snake case from the plugin name. + * + * @param string|null $pluginName + * @return string + */ + public function getMainPluginFile($pluginName = ''): string + { + if (empty($pluginName)) { + $pluginName = $this->getPluginName(); + } + return strtolower(str_replace(' ', '-', $pluginName)) . '.php'; + } + + /** + * Return the current Plugin name defined in the namespace file. + * + * @return string + */ + public function getPluginName(): string + { + [$plugin_name] = $this->getPluginNameAndNamespace(); + + return $plugin_name; + } + + /** + * Return the plugin slug. + * + * @param string|null $str + */ + public function getPluginSlug($str = null): string + { + $str = $this->snakeCasePluginName($str); + + return $str . '_slug'; + } + + /** + * Return the plugin vars. + * + * @param string|null $str + * @return string + */ + public function getPluginVars($str = null): string + { + $str = $this->snakeCasePluginName($str); + + return $str . '_vars'; + } + + /** + * Return the plugin id used for css, js, less and files. + * Currently, it's the sanitized plugin name. + * + * @param string|null $str + * + * @return string + */ + public function getPluginId($str = null): string + { + return $this->sanitizePluginName($str); + } + /** * Return the current Plugin name and namespace defined in the namespace file. * @@ -702,248 +1010,335 @@ namespace Bones { } /** - * Return TRUE if the command is in console argument. + * Help method to get the Domain Path from the plugin header * - * @param string $command Bones command to check. - * @return bool + * @return string|null */ - protected function isCommand(string $command): bool + protected function getDomainPath() { - $arguments = $this->arguments(); - - return $command === ($arguments[0] ?? ''); + $header = $this->extractPluginHeaderInfo('Domain Path'); + if (isset($header['Domain Path'])) { + return $header['Domain Path']; + } + return null; } - /** Display the WP-CLI version in the bones console. */ - protected function wpCliInfoHelp() + /** + * Returns the first package manager available. + * + * @return string|null The name of the available package manager (yarn, npm, pnpm, bun) or null if none is available. + */ + protected function getAvailablePackageManager(): ?string { - if ($this->wpCliVersion) { - $this->info("→ WP-CLI version: {$this->wpCliVersion}"); - } else { - $this->warning("⚠️ WP-CLI not found: we recommend to install it globally - https://wp-cli.org/#installing\n"); + $packageManagers = $this->getListAvailablePackageManagers(); + + if (empty($packageManagers)) { + return null; } + + return $packageManagers[0]; } - /** Display the full help. */ - protected function help() + /** + * Returns the list of effective package managers. + * We will check for ['yarn', 'npm', 'pnpm', 'bun'] and return the available ones. + * + * @since 1.9.0 + * @return array The list of available package managers + */ + protected function getListAvailablePackageManagers(): array { - $colorCodes = [ - [51, 45, 39, 33, 27, 21], - [249, 248, 247, 246, 245, 244], - [88, 89, 90, 91, 92, 93], - [76, 82, 46, 40, 34, 28], - [201, 200, 199, 198, 197, 196], - [243, 242, 241, 240, 239, 238] - ]; - $colorCode = $colorCodes[rand(0, count($colorCodes) - 1)]; - - echo "\n\033[38;5;{$colorCode[0]}m██╗ ██╗██████╗ ██████╗ ██████╗ ███╗ ██╗███████╗███████╗\033[0m\n"; - echo "\033[38;5;{$colorCode[1]}m██║ ██║██╔══██╗ ██╔══██╗██╔═══██╗████╗ ██║██╔════╝██╔════╝\033[0m\n"; - echo "\033[38;5;{$colorCode[2]}m██║ █╗ ██║██████╔╝ ██████╔╝██║ ██║██╔██╗ ██║█████╗ ███████╗\033[0m\n"; - echo "\033[38;5;{$colorCode[3]}m██║███╗██║██╔═══╝ ██╔══██╗██║ ██║██║╚██╗██║██╔══╝ ╚════██║\033[0m\n"; - echo "\033[38;5;{$colorCode[4]}m╚███╔███╔╝██║ ██████╔╝╚██████╔╝██║ ╚████║███████╗███████║\033[0m\n"; - echo "\033[38;5;{$colorCode[5]}m ╚══╝╚══╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝╚══════╝\033[0m\n"; - - $this->line(" Bones Version " . self::VERSION . "\n"); - $this->wpCliInfoHelp(); - $this->info('→ Current Plugin Filename, Name, Namespace:', false); - $this->line(" '{$this->getMainPluginFile()}', '{$this->getPluginName()}', '{$this->getNamespace()}'\n"); - $this->info('Usage:'); - $this->line(" command [options] [arguments]\n"); - $this->info('Available commands:'); - $this->line(' deploy Create a deploy version'); - $this->line(' install Install a new WP Bones plugin'); - $this->line(' optimize Run composer dump-autoload with -o option'); - $this->line(' plugin Perform plugin operations'); - $this->line(' rename Rename the plugin name and the namespace'); - $this->line(' require Install a WP Bones package'); - $this->line(' tinker Interact with your application'); - $this->line(' update Update the Framework'); - $this->line(' version Update the Plugin version'); - $this->info('migrate'); - $this->line(' migrate:create Create a new Migration'); - $this->info('make'); - $this->line(' make:ajax Create a new Ajax service provider class'); - $this->line(' make:api Create a new API controller class'); - $this->line(' make:console Create a new Bones command'); - $this->line(' make:controller Create a new controller class'); - $this->line(' make:cpt Create a new Custom Post Type service provider class'); - $this->line(' make:ctt Create a new Custom Taxonomy Type service provider class'); - $this->line(' make:eloquent-model Create a new Eloquent database model class'); - $this->line(' make:model Create a new database model class'); - $this->line(' make:schedule Create a new schedule (cron) service provider class'); - $this->line(' make:shortcode Create a new Shortcode service provider class'); - $this->line(' make:provider Create a new service provider class'); - $this->line(' make:widget Create a new Widget service provider class'); + $checkIdAvailable = ['yarn', 'npm', 'pnpm', 'bun']; + $available = []; - if ($this->kernel && $this->kernel->hasCommands()) { - $this->info('Extensions'); - $this->kernel->displayHelp(); + foreach ($checkIdAvailable as $id) { + if ($this->isCommandAvailable($id)) { + $available[] = $id; + } } - echo "\n\n"; + return $available; } /** - * Commodity to display a message in the console. + * Return the content of a stub file. * - * @param string $str The message to display. - * @param bool $newLine Optional. Whether to add a new line at the end. + * @param string $filename + * @return string */ - protected function info(string $str, $newLine = true) + public function getStubContent(string $filename): string { - echo "\033[38;5;213m" . $str; - echo "\033[0m"; - echo $newLine ? "\n" : ''; + return file_get_contents( + "vendor/wpbones/wpbones/src/Console/stubs/{$filename}.stub" + ); } /** - * Commodity to display a message in the console. + * Return true if the command is available in the system. * - * @param string $str The message to display. - * @param bool $newLine Optional. Whether to add a new line at the end. + * @param string $command + * @return bool */ - protected function line(string $str, $newLine = true) + protected function isCommandAvailable(string $command): bool { - echo "\033[38;5;82m" . $str; - echo "\033[0m"; - echo $newLine ? "\n" : ''; + $whereCommand = (PHP_OS_FAMILY === 'Windows') ? 'where' : 'command -v'; + $output = shell_exec("$whereCommand $command"); + return !empty($output); } - /* Commodity to display an error message in the console. */ - protected function error(string $str, $newLine = true) + /** + * Return TRUE if the command is in console argument. + * + * @param string $command Bones command to check. + * @return bool + */ + protected function isCommand(string $command): bool { - echo "\033[41m"; - echo $newLine ? "\n" : ''; - echo "\033[41;255m" . $str; - echo $newLine ? "\n" : ''; - echo "\033[0m"; - echo $newLine ? "\n" : ''; + $arguments = $this->arguments(); + + return $command === ($arguments[0] ?? ''); } - /* Commodity to display an info message in the console. */ - protected function warning(string $str, $newLine = true) + /** + * Commodity function to check if help has been requested. + * + * @param string|null $str Optional. Command to check. + * + * @return bool + */ + protected function isHelp($str = null): bool { - echo "\e[31m"; - echo $newLine ? "\n" : ''; - echo $str; - echo $newLine ? "\n" : ''; - echo "\e[0m"; - echo $newLine ? "\n" : ''; + if (!is_null($str)) { + return empty($str) || $str === '--help'; + } + + $param = $this->getCommandParams()[0] ?? null; + + return !empty($param) && $param === '--help'; } /** - * Return the default Plugin filename as snake case from the plugin name. + * Return the snake case plugin name. * - * @param string|null $pluginName + * @param string|null $str * @return string */ - public function getMainPluginFile(?string $pluginName = ''): string + public function snakeCasePluginName($str = null): string { - if (empty($pluginName)) { - $pluginName = $this->getPluginName(); - } - return strtolower(str_replace(' ', '-', $pluginName)) . '.php'; + $str = $this->sanitizePluginName($str); + + return str_replace('-', '_', $str); } /** - * Return the current Plugin name defined in the namespace file. + * Return the sanitized plugin name. * + * @param string|null $str * @return string */ - public function getPluginName(): string + public function sanitizePluginName($str = null): string { - [$plugin_name] = $this->getPluginNameAndNamespace(); + if (is_null($str)) { + $str = $this->getPluginName(); + } - return $plugin_name; + return $this->sanitize($str); } /** - * This is the most important function of WP Bones. - * Here we will rename all occurrences of the plugin name and namespace. - * The default plugin name is 'WP Kirk' and the default namespace is 'WPKirk'. - * Here we will create also the slug used in the plugin. - * - * For example, if the plugin name is 'My WP Plugin' - * - * My WP Plugin Name of plugin - * MyWPPlugin Namespace, see [PSR-4 autoload standard](http://www.php-fig.org/psr/psr-4/) - * my_wp_plugin_slug Plugin slug - * my_wp_plugin_vars Plugin vars used for CPT, taxonomy, etc. - * my-wp-plugin Internal id used for css, js and less files - * - * As you can see we're going to create all namespace/id from the plugin name. + * Return the CamelCase plugin name. + * Used for Namespace * - * @brief Rename the plugin + * @param string $input + * @return string + */ + public function sanitizeToCamelCase(string $input): string + { + // Remove special characters except letters and numbers + $sanitized = preg_replace('/[^a-zA-Z0-9\s]/', '', $input); + + // Split the string into words (using spaces as delimiter) + $words = explode(' ', $sanitized); + + // Convert the first letter of each word to uppercase + $camelCasedWords = array_map('ucfirst', $words); + + // Join the words without spaces + $camelCasedString = implode('', $camelCasedWords); + + // Return the CamelCase string + return $camelCasedString; + } + + /** + * Return a kebalized version of the string * + * @param string $title */ - protected function rename($args) + protected function sanitize(string $title): string { - if ($this->isHelp()) { - $this->info('Usage:'); - $this->line(' php bones rename [options] '); - $this->info('Available options:'); - $this->line(' --reset Reset the plugin name and namespace'); - $this->line(' --update Rename after an update. For example after install a new package'); - exit(); - } + $title = strip_tags($title); + // Preserve escaped octets. + $title = preg_replace('|%([a-fA-F0-9][a-fA-F0-9])|', '---$1---', $title); + // Remove percent signs that are not part of an octet. + $title = str_replace('%', '', $title); + // Restore octets. + $title = preg_replace('|---([a-fA-F0-9][a-fA-F0-9])---|', '%$1', $title); - $arg_option_plugin_name = $args[0] ?? null; + $title = strtolower($title); - switch ($arg_option_plugin_name) { - case '--reset': - $this->resetPluginNameAndNamespace(); - break; - case '--update': - $this->updatePluginNameAndNamespace(); - break; - default: - [$search_plugin_name, $search_namespace, $plugin_name, $namespace, $mainPluginFile] = $this->getAskPluginNameAndNamespace($args); - $this->info("\nThe new plugin filename, name and namespace will be '{$mainPluginFile}', '{$plugin_name}', '{$namespace}'"); - $yesno = $this->ask('Continue (y/n)', 'n'); - if (strtolower($yesno) != 'y') { - return; - } - $this->setPluginNameAndNamespace($search_plugin_name, $search_namespace, $plugin_name, $namespace); - $this->optimize(); - break; + $title = preg_replace('/&.+?;/', '', $title); // kill entities + $title = str_replace('.', '-', $title); + + $title = preg_replace('/[^%a-z0-9 _-]/', '', $title); + $title = preg_replace('/\s+/', '-', $title); + $title = preg_replace('|-+|', '-', $title); + + return trim($title, '-'); + } + + /** Display the WP-CLI version in the bones console. */ + protected function wpCliInfoHelp() + { + if ($this->wpCliVersion) { + $this->info("→ WP-CLI version: {$this->wpCliVersion}"); + } else { + $this->warning("WP-CLI not found: we recommend to install it globally - https://wp-cli.org/#installing\n"); } } - /** - * Commodity function to check if help has been requested. - * - * @param string|null $str Optional. Command to check. - * - * @return bool - */ - protected function isHelp(string $str = null): bool - { - if (!is_null($str)) { - return empty($str) || $str === '--help'; + /** Display the full help. */ + protected function help() + { + $colorCodes = [ + [51, 45, 39, 33, 27, 21], + [249, 248, 247, 246, 245, 244], + [88, 89, 90, 91, 92, 93], + [76, 82, 46, 40, 34, 28], + [201, 200, 199, 198, 197, 196], + [243, 242, 241, 240, 239, 238] + ]; + $colorCode = $colorCodes[rand(0, count($colorCodes) - 1)]; + + echo "\n\033[38;5;{$colorCode[0]}m██╗ ██╗██████╗ ██████╗ ██████╗ ███╗ ██╗███████╗███████╗\033[0m\n"; + echo "\033[38;5;{$colorCode[1]}m██║ ██║██╔══██╗ ██╔══██╗██╔═══██╗████╗ ██║██╔════╝██╔════╝\033[0m\n"; + echo "\033[38;5;{$colorCode[2]}m██║ █╗ ██║██████╔╝ ██████╔╝██║ ██║██╔██╗ ██║█████╗ ███████╗\033[0m\n"; + echo "\033[38;5;{$colorCode[3]}m██║███╗██║██╔═══╝ ██╔══██╗██║ ██║██║╚██╗██║██╔══╝ ╚════██║\033[0m\n"; + echo "\033[38;5;{$colorCode[4]}m╚███╔███╔╝██║ ██████╔╝╚██████╔╝██║ ╚████║███████╗███████║\033[0m\n"; + echo "\033[38;5;{$colorCode[5]}m ╚══╝╚══╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝╚══════╝\033[0m\n"; + + $this->line(" Bones Version " . self::VERSION . "\n"); + $this->wpCliInfoHelp(); + $this->info('→ Current Plugin Filename, Name, Namespace:', false); + $this->line(" '{$this->getMainPluginFile()}', '{$this->getPluginName()}', '{$this->getNamespace()}'\n"); + $this->info('Usage:'); + $this->line(" command [options] [arguments]\n"); + $this->info('Available commands:'); + $this->line(' deploy Create a deploy version'); + $this->line(' install Install a new WP Bones plugin'); + $this->line(' optimize Run composer dump-autoload with -o option'); + $this->line(' plugin Perform plugin operations'); + $this->line(' rename Rename the plugin name and the namespace'); + $this->line(' require Install a WP Bones package'); + $this->line(' tinker Interact with your application'); + $this->line(' update Update the Framework'); + $this->line(' version Update the Plugin version'); + $this->info('migrate'); + $this->line(' migrate:create Create a new Migration'); + $this->info('make'); + $this->line(' make:ajax Create a new Ajax service provider class'); + $this->line(' make:api Create a new API controller class'); + $this->line(' make:console Create a new Bones command'); + $this->line(' make:controller Create a new controller class'); + $this->line(' make:cpt Create a new Custom Post Type service provider class'); + $this->line(' make:ctt Create a new Custom Taxonomy Type service provider class'); + $this->line(' make:eloquent-model Create a new Eloquent database model class'); + $this->line(' make:model Create a new database model class'); + $this->line(' make:schedule Create a new schedule (cron) service provider class'); + $this->line(' make:shortcode Create a new Shortcode service provider class'); + $this->line(' make:provider Create a new service provider class'); + $this->line(' make:widget Create a new Widget service provider class'); + + if ($this->kernel && $this->kernel->hasCommands()) { + $this->info('Extensions'); + $this->kernel->displayHelp(); } - $param = $this->commandParams()[0] ?? null; - - return !empty($param) && $param === '--help'; + echo "\n\n"; } - /** - * Return the params after "php bones [command]". - * - * @param int|null $index Optional. Index of param. - * If NULL will be returned the whole array. - * - * @return array|string - */ - protected function commandParams(int $index = null) + /* Run subtask. Handle the php bones commands */ + protected function handle() { - $params = $this->arguments(); + // deploy + if ($this->isCommand('deploy')) { + $this->deploy($this->getCommandParams()); + } // Optimize + elseif ($this->isCommand('optimize')) { + $this->optimize(); + } // Tinker + elseif ($this->isCommand('tinker')) { + $this->tinker(); + } // Require + elseif ($this->isCommand('require')) { + $this->requirePackage($this->getCommandParams(0)); + } // Version + elseif ($this->isCommand('version')) { + $this->version($this->getCommandParams()); + } // migrate:create {table_name} + elseif ($this->isCommand('plugin')) { + $this->plugin($this->getCommandParams()); + } // migrate:create {table_name} + elseif ($this->isCommand('migrate:create')) { + $this->createMigrate($this->getCommandParams(0)); + } // make:controller {controller_name} + elseif ($this->isCommand('make:controller')) { + $this->createController($this->getCommandParams(0)); + } // make:console {command_name} + elseif ($this->isCommand('make:console')) { + $this->createCommand($this->getCommandParams(0)); + } // make:cpt {className} + elseif ($this->isCommand('make:cpt')) { + $this->createCustomPostType($this->getCommandParams(0)); + } // make:shortcode {className} + elseif ($this->isCommand('make:shortcode')) { + $this->createShortcode($this->getCommandParams(0)); + } // make:schedule {className} + elseif ($this->isCommand('make:schedule')) { + $this->createSchedule($this->getCommandParams(0)); + } // make:provider {className} + elseif ($this->isCommand('make:provider')) { + $this->createProvider($this->getCommandParams(0)); + } // make:ajax {className} + elseif ($this->isCommand('make:ajax')) { + $this->createAjax($this->getCommandParams(0)); + } // make:ctt {className} + elseif ($this->isCommand('make:ctt')) { + $this->createCustomTaxonomyType($this->getCommandParams(0)); + } // make:widget {className} + elseif ($this->isCommand('make:widget')) { + $this->createWidget($this->getCommandParams(0)); + } // make:model {className} + elseif ($this->isCommand('make:model')) { + $this->createModel($this->getCommandParams(0)); + } // make:eloquent-model {className} + elseif ($this->isCommand('make:eloquent-model')) { + $this->createEloquentModel($this->getCommandParams(0)); + } // make:api {className} + elseif ($this->isCommand('make:api')) { + $this->createAPIController($this->getCommandParams(0)); + } // else... + else { + $extended = false; - // strip the command name - array_shift($params); + if ($this->kernel) { + $extended = $this->kernel->handle($this->arguments()); + } - return !is_null($index) ? $params[$index] ?? null : $params; + if (!$extended) { + $this->info("\nUnknown command! Use --help for commands list\n"); + } + } } /* Reset the plugin name and namespace to the original values */ @@ -956,12 +1351,6 @@ namespace Bones { $this->setPluginNameAndNamespace($search_plugin_name, $search_namespace, $plugin_name, $namespace); } - /* Return the default plugin name and namespace. */ - protected function getDefaultPluginNameAndNamespace(): array - { - return ['WP Kirk', 'WPKirk']; - } - /** * Update the plugin name and namespace * @@ -1006,7 +1395,7 @@ namespace Bones { ]); // change namespace - $this->line("🚧 Loading and process files..."); + $this->startProgress("Processing files"); foreach ($files as $file) { $content = file_get_contents($file); @@ -1042,7 +1431,7 @@ namespace Bones { file_put_contents($file, $content); } - $this->info("✅ Content renamed completed!"); + $this->endProgress(); $folder = $this->getDomainPath(); @@ -1084,7 +1473,7 @@ namespace Bones { // save new plugin name and namespace file_put_contents('namespace', "{$plugin_name},{$namespace}"); - $this->info("👏 Rename process completed!"); + $this->processCompleted("Rename process completed!"); } /** @@ -1153,99 +1542,6 @@ namespace Bones { return _rglob($path, $match, $result); } - /** - * Return the plugin slug. - * - * @param string|null $str - */ - public function getPluginSlug(string $str = null): string - { - $str = $this->snakeCasePluginName($str); - - return $str . '_slug'; - } - - /** - * Return the snake case plugin name. - * - * @param string|null $str - * @return string - */ - public function snakeCasePluginName(string $str = null): string - { - $str = $this->sanitizePluginName($str); - - return str_replace('-', '_', $str); - } - - /** - * Return the sanitized plugin name. - * - * @param string|null $str - * @return string - */ - public function sanitizePluginName(string $str = null): string - { - if (is_null($str)) { - $str = $this->getPluginName(); - } - - return $this->sanitize($str); - } - - /** - * Return a kebalized version of the string - * - * @param string $title - */ - protected function sanitize(string $title): string - { - $title = strip_tags($title); - // Preserve escaped octets. - $title = preg_replace('|%([a-fA-F0-9][a-fA-F0-9])|', '---$1---', $title); - // Remove percent signs that are not part of an octet. - $title = str_replace('%', '', $title); - // Restore octets. - $title = preg_replace('|---([a-fA-F0-9][a-fA-F0-9])---|', '%$1', $title); - - $title = strtolower($title); - - $title = preg_replace('/&.+?;/', '', $title); // kill entities - $title = str_replace('.', '-', $title); - - $title = preg_replace('/[^%a-z0-9 _-]/', '', $title); - $title = preg_replace('/\s+/', '-', $title); - $title = preg_replace('|-+|', '-', $title); - - return trim($title, '-'); - } - - /** - * Return the plugin vars. - * - * @param string|null $str - * @return string - */ - public function getPluginVars(string $str = null): string - { - $str = $this->snakeCasePluginName($str); - - return $str . '_vars'; - } - - /** - * Return the plugin id used for css, js, less and files. - * Currently, it's the sanitized plugin name. - * - * @param string|null $str - * - * @return string - */ - public function getPluginId(string $str = null): string - { - return $this->sanitizePluginName($str); - } - /* Update the plugin name and namespace after a install new package */ protected function updatePluginNameAndNamespace() { @@ -1262,14 +1558,14 @@ namespace Bones { * @param array $args The arguments from the console. The first argument is the plugin name and the second is the namespace * */ - protected function getAskPluginNameAndNamespace(array $args): array + protected function askPluginNameAndNamespace(array $args): array { // Get the current plugin name and namespace $search_plugin_name = $this->getPluginName(); $search_namespace = $this->getNamespace(); if ($search_plugin_name === 'WP Kirk' && $search_namespace === 'WPKirk') { - $this->line("ℹ️ You are renaming your plugin for the first time.\n"); + $this->line("→ You are renaming your plugin for the first time.\n"); } $plugin_name = $args[0] ?? ''; @@ -1298,18 +1594,18 @@ namespace Bones { $namespace = ''; while (empty($plugin_name)) { - $plugin_name = $this->ask('Plugin name:', $plugin_name); - $namespace = $this->ask('Namespace:', $namespace); + $plugin_name = $this->ask('Plugin name', $plugin_name); + $namespace = $this->ask('Namespace', $namespace); // both plugin name and namespace don't have to contains 'WP Kirk' or 'WPKirk' if (strpos($plugin_name, 'WP Kirk') !== false || strpos($plugin_name, 'WPKirk') !== false) { - $this->error('🛑 Plugin name cannot contain "WP Kirk" or "WPKirk"'); + $this->warning('Plugin name cannot contain "WP Kirk" or "WPKirk"'); $plugin_name = ''; continue; } if (strpos($namespace, 'WP Kirk') !== false || strpos($namespace, 'WPKirk') !== false) { - $this->error('🛑 Namespace cannot contain "WP Kirk" or "WPKirk"'); + $this->warning('Namespace cannot contain "WP Kirk" or "WPKirk"'); $plugin_name = ''; $namespace = ''; continue; @@ -1392,78 +1688,103 @@ namespace Bones { return $result; } - /** - * Return the CamelCase plugin name. - * Used for Namespace - * - * @param string $input - * @return string - */ - public function sanitizeToCamelCase(string $input): string + /* Execute composer install */ + protected function install() { - // Remove special characters except letters and numbers - $sanitized = preg_replace('/[^a-zA-Z0-9\s]/', '', $input); - - // Split the string into words (using spaces as delimiter) - $words = explode(' ', $sanitized); + if ($this->isHelp()) { + $this->line("Will run the composer install\n"); + $this->info('Usage:'); + $this->line(' php bones install'); + exit(); + } - // Convert the first letter of each word to uppercase - $camelCasedWords = array_map('ucfirst', $words); + $this->startCommand("Install"); - // Join the words without spaces - $camelCasedString = implode('', $camelCasedWords); + // check if the "node_modules" folder exists + if (!is_dir('node_modules')) { + $this->warning("Node modules not found"); + $this->installPackages(); + } - // Return the CamelCase string - return $camelCasedString; + $this->line(`composer install`); } /** - * -------------------------------------------------------------------------- - * Internal useful function - * -------------------------------------------------------------------------- - * Contains all internal methods. - */ - - - /** - * Get input from console + * Install node modules * - * @param string $str The question to ask - * @param string|null $default The default value + * @since 1.9.0 */ - protected function ask(string $str, ?string $default = ''): string + protected function installPackages() { - $str = "\n\e[38;5;33m$str" . - (empty($default) ? '' : " (default: {$default})") . - "\e[0m "; - - // Use readline to get the user input - $line = readline($str); - - // Trim the input to remove extra spaces or newlines - $line = trim($line); - - return $line ?: $default; + // ask to install node modules + $yesno = $this->ask('Do you want to install node modules (y/n)', 'n'); + if (strtolower($yesno) === 'y') { + $packageManager = $this->askPackageManager(); + shell_exec("{$packageManager} install"); + $this->processCompleted("Node modules created and packages installed successfully\n"); + } } - - /* Alias composer dump-autoload */ protected function optimize() { + $this->startCommand("Optimize"); $this->line(`composer dump-autoload -o`); + $this->processCompleted("Optimize process completed!"); } - /* Execute composer install */ - protected function install() + /** + * This is the most important function of WP Bones. + * Here we will rename all occurrences of the plugin name and namespace. + * The default plugin name is 'WP Kirk' and the default namespace is 'WPKirk'. + * Here we will create also the slug used in the plugin. + * + * For example, if the plugin name is 'My WP Plugin' + * + * My WP Plugin Name of plugin + * MyWPPlugin Namespace, see [PSR-4 autoload standard](http://www.php-fig.org/psr/psr-4/) + * my_wp_plugin_slug Plugin slug + * my_wp_plugin_vars Plugin vars used for CPT, taxonomy, etc. + * my-wp-plugin Internal id used for css, js and less files + * + * As you can see we're going to create all namespace/id from the plugin name. + * + * @brief Rename the plugin + * + */ + protected function rename($args) { if ($this->isHelp()) { - $this->line("Will run the composer install\n"); $this->info('Usage:'); - $this->line(' php bones install'); + $this->line(' php bones rename [options] '); + $this->info('Available options:'); + $this->line(' --reset Reset the plugin name and namespace'); + $this->line(' --update Rename after an update. For example after install a new package'); exit(); } - $this->line(`composer install`); + + $this->startCommand("Rename"); + + $arg_option_plugin_name = $args[0] ?? null; + + switch ($arg_option_plugin_name) { + case '--reset': + $this->resetPluginNameAndNamespace(); + break; + case '--update': + $this->updatePluginNameAndNamespace(); + break; + default: + [$search_plugin_name, $search_namespace, $plugin_name, $namespace, $mainPluginFile] = $this->askPluginNameAndNamespace($args); + $this->line("\n→ The new plugin filename, name and namespace will be '{$mainPluginFile}', '{$plugin_name}', '{$namespace}'"); + $yesno = $this->ask('Continue (y/n)', 'n'); + if (strtolower($yesno) != 'y') { + return; + } + $this->setPluginNameAndNamespace($search_plugin_name, $search_namespace, $plugin_name, $namespace); + $this->optimize(); + break; + } } /* Execute composer update */ @@ -1504,87 +1825,6 @@ namespace Bones { @rmdir("{$path}"); } - /** - * -------------------------------------------------------------------------- - * Public task - * -------------------------------------------------------------------------- - * Contains tasks a user can run from the console. - */ - - /* Run subtask. Handle the php bones commands */ - protected function handle() - { - // deploy - if ($this->isCommand('deploy')) { - $this->deploy($this->commandParams()); - } // Optimize - elseif ($this->isCommand('optimize')) { - $this->optimize(); - } // Tinker - elseif ($this->isCommand('tinker')) { - $this->tinker(); - } // Require - elseif ($this->isCommand('require')) { - $this->requirePackage($this->commandParams(0)); - } // Version - elseif ($this->isCommand('version')) { - $this->version($this->commandParams()); - } // migrate:create {table_name} - elseif ($this->isCommand('plugin')) { - $this->plugin($this->commandParams()); - } // migrate:create {table_name} - elseif ($this->isCommand('migrate:create')) { - $this->createMigrate($this->commandParams(0)); - } // make:controller {controller_name} - elseif ($this->isCommand('make:controller')) { - $this->createController($this->commandParams(0)); - } // make:console {command_name} - elseif ($this->isCommand('make:console')) { - $this->createCommand($this->commandParams(0)); - } // make:cpt {className} - elseif ($this->isCommand('make:cpt')) { - $this->createCustomPostType($this->commandParams(0)); - } // make:shortcode {className} - elseif ($this->isCommand('make:shortcode')) { - $this->createShortcode($this->commandParams(0)); - } // make:schedule {className} - elseif ($this->isCommand('make:schedule')) { - $this->createSchedule($this->commandParams(0)); - } // make:provider {className} - elseif ($this->isCommand('make:provider')) { - $this->createProvider($this->commandParams(0)); - } // make:ajax {className} - elseif ($this->isCommand('make:ajax')) { - $this->createAjax($this->commandParams(0)); - } // make:ctt {className} - elseif ($this->isCommand('make:ctt')) { - $this->createCustomTaxonomyType($this->commandParams(0)); - } // make:widget {className} - elseif ($this->isCommand('make:widget')) { - $this->createWidget($this->commandParams(0)); - } // make:model {className} - elseif ($this->isCommand('make:model')) { - $this->createModel($this->commandParams(0)); - } // make:eloquent-model {className} - elseif ($this->isCommand('make:eloquent-model')) { - $this->createEloquentModel($this->commandParams(0)); - } // make:api {className} - elseif ($this->isCommand('make:api')) { - $this->createAPIController($this->commandParams(0)); - } // else... - else { - $extended = false; - - if ($this->kernel) { - $extended = $this->kernel->handle($this->arguments()); - } - - if (!$extended) { - $this->info("\nUnknown command! Use --help for commands list\n"); - } - } - } - /** * Create a deployment version of the plugin * @@ -1598,8 +1838,10 @@ namespace Bones { $path = rtrim($path, '/'); + $this->startCommand("Deploy"); + if (empty($path)) { - $path = $this->ask('Enter the complete path of deploy:'); + $path = $this->ask('Enter the complete path of deploy'); } elseif ('--help' === $path) { $this->line("\nUsage:"); $this->info(" deploy \n"); @@ -1609,158 +1851,161 @@ namespace Bones { exit(0); } - $this->info("\nStarting deploy ¯\_(ツ)_/¯\n"); - - if (!empty($path)) { + if (empty($path)) { + $this->error("The path is empty!"); + exit(1); + } - $dontSkipWhenDeploy = [ - '/gulpfile.js', - '/package.json', - '/package-lock.json', - '/yarn.lock', - '/tsconfig.json', - '/pnpm-lock.yaml', - '/resources/assets', - '/composer.json', - '/composer.lock', - ]; + do_action('wpbones_console_deploy_start', $this, $path); - if ($is_wp_org) { - $this->info("\n🚦 You are going to release this plugin in the WordPress.org public repository"); - $this->line("\n→ In this case some files won't be skipped during the deployment."); - $this->line("→ The files that won't be skipped are:\n\n" . implode("\n", $dontSkipWhenDeploy) . "\n\n"); - } + // alternative method to customize the deployment + @include 'deploy.php'; - // alternative method to customize the deployment - @include 'deploy.php'; + /** + * Filter the list of files and folders that won't be skipped during the deployment. + * + * @since 1.9.0 + * @param array $array The files and folders are relative to the root of plugin. + */ + $dontSkipWhenDeploy = apply_filters('wpbones_console_deploy_dont_skip_files_folders', [ + '/gulpfile.js', + '/package.json', + '/package-lock.json', + '/yarn.lock', + '/tsconfig.json', + '/pnpm-lock.yaml', + '/resources/assets', + '/composer.json', + '/composer.lock', + ]); - do_action('wpbones_console_deploy_start', $this, $path); + if ($is_wp_org) { + $this->info("\n🚦 You are going to release this plugin in the WordPress.org public repository"); + $this->line("\n→ In this case some files won't be skipped during the deployment."); + $this->line("→ The files that won't be skipped are:\n\n" . implode("\n", $dontSkipWhenDeploy) . "\n\n"); + } - // first delete previous path - $this->info("🕐 Delete folder {$path}"); - $this->deleteDirectory($path); - $this->info("\033[1A👍"); + /** + * Filter to enable or disable the build assets during the deployment. + * + * @since 1.9.0 + * @param bool $buildAssets True to build assets, false to skip the build. + */ + $buildAssets = apply_filters('wpbones_console_deploy_build_assets', true); + if ($buildAssets) { do_action('wpbones_console_deploy_before_build_assets', $this, $path); + $this->buildAssets($path); + } - $packageManager = $this->getAvailablePackageManager(); - - if ($packageManager) { - - $this->info("✅ Found '$packageManager' as package manager"); - - $answer = $this->ask("Do you want to run '$packageManager run build' to build assets? (y/n)", 'y'); - - if (strtolower($answer) === 'y') { - $this->info("📦 Build for production by using '{$packageManager} run build'"); - shell_exec("{$packageManager} run build"); - $this->info("✅ Built assets successfully"); - do_action('wpbones_console_deploy_after_build_assets', $this, $path); - } else { - $answer = $this->ask("Enter the package manager to build assets (press RETURN to skip the build)", ''); - if (empty($answer)) { - $this->info('⏭︎ Skip build assets'); - } else { - $this->info("📦 Build for production by using '{$answer} run build'"); - shell_exec("{$answer} run build"); - $this->info("✅ Built assets successfully"); - do_action('wpbones_console_deploy_after_build_assets', $this, $path); - } - } - } else { - $this->info("🛑 No package manager found. The build assets will be skipped"); - } - - // files and folders to skip - $this->skipWhenDeploy = [ - '/.git', - '/.cache', - '/assets', - '/.gitignore', - '/.gitkeep', - '/.DS_Store', - '/.babelrc', - '/node_modules', - '/bones', - '/vendor/wpbones/wpbones/src/Console/stubs', - '/vendor/wpbones/wpbones/src/Console/bin', - '/vendor/bin/bladeonecli', - '/vendor/eftec/bladeone/lib/bladeonecli', - '/deploy.php', - '/namespace', - '/README.md', - '/webpack.mix.js', - '/webpack.config.js', - '/phpcs.xml.dist', - '/mix-manifest.json', - '/release.sh', - ]; + // check if the destination folder exists + if (is_dir($path)) { + $this->startProgress("Delete destination folder 📁 {$path}"); + sleep(5); + $this->deleteDirectory($path); + $this->endProgress(); + } - // The new strict rules require that the composer must also be released to publish the plugin in the WordPress.org repository. - if (!$is_wp_org) { - $this->skipWhenDeploy = array_merge($this->skipWhenDeploy, $dontSkipWhenDeploy); - } + /** + * Filter the default list of files and folders to skip during the deployment. + * + * @since 1.9.0 + * @param array $array The files and folders are relative to the root of plugin. + */ + $this->skipWhenDeploy = apply_filters('wpbones_console_deploy_default_skip_files_folders', [ + '/.git', + '/.cache', + '/assets', + '/.gitignore', + '/.gitkeep', + '/.DS_Store', + '/.babelrc', + '/node_modules', + '/bones', + '/vendor/wpbones/wpbones/src/Console/stubs', + '/vendor/wpbones/wpbones/src/Console/bin', + '/vendor/bin/bladeonecli', + '/vendor/eftec/bladeone/lib/bladeonecli', + '/deploy.php', + '/namespace', + '/README.md', + '/webpack.mix.js', + '/webpack.config.js', + '/phpcs.xml.dist', + '/mix-manifest.json', + '/release.sh', + ]); + // The new strict rules require that the composer must also be released to publish the plugin in the WordPress.org repository. + if (!$is_wp_org) { + $this->skipWhenDeploy = array_merge($this->skipWhenDeploy, $dontSkipWhenDeploy); + } - /** - * Filter the list of files and folders to skip during the deployment. - * - * @param array $array The files and folders are relative to the root of plugin. - */ - $this->skipWhenDeploy = apply_filters( - 'wpbones_console_deploy_skip_folders', - $this->skipWhenDeploy - ); + /** + * Filter the list of files and folders to skip during the deployment. + * + * @param array $array The files and folders are relative to the root of plugin. + */ + $this->skipWhenDeploy = apply_filters( + 'wpbones_console_deploy_skip_folders', + $this->skipWhenDeploy + ); - $this->rootDeploy = __DIR__; + $this->rootDeploy = __DIR__; - $this->info("🚧 Copying to {$path}"); - $this->xcopy(__DIR__, $path); - $this->info("✅ Copy completed"); + $this->startProgress("Copying to 📁 {$path}"); + $this->xcopy(__DIR__, $path); + $this->endProgress(); - /** - * Fires when the console deploy is completed. - * - * @param mixed $bones Bones command instance. - * @param string $path The deployed path. - */ - do_action('wpbones_console_deploy_completed', $this, $path); + /** + * Fires when the console deploy is completed. + * + * @param mixed $bones Bones command instance. + * @param string $path The deployed path. + */ + do_action('wpbones_console_deploy_completed', $this, $path); - $this->info("\n\e[5m👏 Deploy completed!\e[0m"); - $this->info("\n🚀 You can now deploy the plugin from the path: {$path}\n"); - } + $this->success("Deploy completed!"); + $this->info("\n🚀 You can now deploy the plugin from the path: {$path}\n"); } /** - * Returns the available package manager + * Build assets * - * @return string|null The name of the available package manager (yarn, npm, pnpm, bun) or null if none is available. + * @since 1.9.0 + * @param string $path The path of the deploy */ - protected function getAvailablePackageManager(): ?string + protected function buildAssets($path) { - $packageManagers = ['yarn', 'npm', 'pnpm', 'bun']; + $packageManager = $this->askPackageManager(); + + if ($packageManager) { - foreach ($packageManagers as $manager) { - if ($this->isCommandAvailable($manager)) { - return $manager; + // ask to build assets + $answer = $this->ask("Do you want to run '$packageManager run build' to build assets? (y/n)", 'y'); + + if (strtolower($answer) === 'y') { + $this->startProgress("Build for production by using '{$packageManager} run build'"); + shell_exec("{$packageManager} run build"); + $this->processCompleted("Build completed\n"); + do_action('wpbones_console_deploy_after_build_assets', $this, $path); + } else { + $answer = $this->ask("Enter the package manager to build assets (press RETURN to skip the build)", ''); + if (empty($answer)) { + $this->info('⏭︎ Skip build assets'); + } else { + $this->startProgress("Build for production by using '{$answer} run build'"); + shell_exec("{$answer} run build"); + $this->endProgress(); + do_action('wpbones_console_deploy_after_build_assets', $this, $path); + } } + } else { + $this->warning("No package manager found. The build assets will be skipped"); } - - return null; } - /** - * Return true if the command is available in the system. - * - * @param string $command - * @return bool - */ - protected function isCommandAvailable(string $command): bool - { - $whereCommand = (PHP_OS_FAMILY === 'Windows') ? 'where' : 'command -v'; - $output = shell_exec("$whereCommand $command"); - return !empty($output); - } + /** * Copy a whole folder. Used by deploy() @@ -1835,21 +2080,25 @@ namespace Bones { /* Start a Tinker emulation */ protected function tinker() { - $eval = $this->ask('>>>'); + $eval = trim(readline(WPBONES_COLOR_BOLD_GREEN . "〉" . WPBONES_COLOR_LIGHT_GREEN), " \t\n\r\0\x0B"); try { if ($eval == 'exit') { exit(); } - if (substr($eval, -1) != ';') { + if (!empty($eval) && substr($eval, -1) != ';') { $eval .= ';'; } - $this->line(eval($eval)); + if (!empty($eval)) { + echo WPBONES_COLOR_RESET; + $this->line(eval($eval)); + } } catch (Exception $e) { $this->info(eval($e->getMessage())); } finally { + echo empty($eval) ? "" : "\n"; $this->tinker(); } } @@ -1859,13 +2108,24 @@ namespace Bones { * * @param string $package The composer package to install */ - protected function requirePackage(string $package) + protected function requirePackage(?string $package = '') { if ($this->isHelp($package)) { $this->info('Use php bones require '); exit(); } + $this->startCommand("Require"); + + if (empty($package)) { + $package = $this->ask('Enter the composer package to install'); + } + + if (empty($package)) { + $this->error("The package is empty!"); + exit(1); + } + $this->line(`composer require {$package}`); // rename as it is @@ -1882,6 +2142,8 @@ namespace Bones { */ protected function version($argv) { + $this->startCommand("Version"); + $version_number_from_index_php = ''; $version_number_from_readme_txt = ''; $stable_tag_version_from_readme_txt = ''; @@ -1897,18 +2159,14 @@ namespace Bones { $lines = explode("\n", $readme_txt_content); foreach ($lines as $line) { if (preg_match('/^[ \t\/*#@]*Stable tag:\s*(.*)$/i', $line, $matches)) { - /** - * The version is in the format of: Stable tag: 1.0.0 - */ + // The version is in the format of: Stable tag: 1.0.0 $stable_tag_version_from_readme_txt = $matches[0]; - /** - * The version is in the format of: 1.0.0 or 1.0.0-beta.1 or 1.0.0-alpha.1 or 1.0.0-rc.1 - */ + // The version is in the format of: 1.0.0 or 1.0.0-beta.1 or 1.0.0-alpha.1 or 1.0.0-rc.1 $version_number_from_readme_txt = $matches[1]; - $this->line( - "\nreadme.txt > $version_number_from_readme_txt ($stable_tag_version_from_readme_txt)" + $this->info( + "\n→ readme.txt > $version_number_from_readme_txt ($stable_tag_version_from_readme_txt)" ); break; } @@ -1919,18 +2177,15 @@ namespace Bones { foreach ($lines as $line) { // get the plugin version for WordPress comments if (preg_match('/^[ \t\/*#@]*Version:\s*(.*)$/i', $line, $matches)) { - /** - * The version is in the format of: * Version: 1.0.0 - */ + + // The version is in the format of: * Version: 1.0.0 $version_string_from_index_php = $matches[0]; - /** - * The version is in the format of: 1.0.0 - */ + // The version is in the format of: 1.0.0 $version_number_from_index_php = $matches[1]; - $this->line( - "$mainPluginFilename > {$version_number_from_index_php} ($version_string_from_index_php)" + $this->info( + "→ $mainPluginFilename > {$version_number_from_index_php} ($version_string_from_index_php)\n" ); break; } @@ -1943,7 +2198,7 @@ namespace Bones { } if (!isset($argv[0]) || empty($argv[0])) { - $version = $this->ask('Enter new version of your plugin:'); + $version = $this->ask('Enter new version of your plugin'); } elseif ($this->isHelp()) { $this->line("\nUsage:"); $this->info(" version [plugin version]\n"); @@ -2003,7 +2258,7 @@ namespace Bones { try { $version = Version::parse($version); } catch (InvalidVersionException $e) { - $this->error("\nERROR:\n\nThe version is not valid.\n"); + $this->error("Error! The version is not valid."); exit(1); } @@ -2052,12 +2307,12 @@ namespace Bones { file_put_contents($mainPluginFilename, $new_index_php_content); - $this->line("\nVersion updated to {$version}"); + $this->processCompleted("Version updated to {$version}"); return; } - $this->line("\nVersion is already {$version}"); + $this->warning("Version is already {$version}"); } /** @@ -2109,29 +2364,15 @@ namespace Bones { foreach ($headerKeys as $key) { if (isset($header[$key])) { - echo '✅ ' . $key . ': '; + $this->success($key . ': '); $this->info($header[$key]); } else { - $this->warning('👀 ' . $key . ': ', false); - $this->error(" Not found \n", false); + $this->warning($key . ': ', false); + $this->color(" Not found \n", WPBONES_COLOR_RED); } } } - /** - * Help method to get the Domain Path from the plugin header - * - * @return string|null - */ - protected function getDomainPath() - { - $header = $this->extractPluginHeaderInfo('Domain Path'); - if (isset($header['Domain Path'])) { - return $header['Domain Path']; - } - return null; - } - /** * Create a migrate file * @@ -2183,19 +2424,6 @@ namespace Bones { ); } - /** - * Return the content of a stub file. - * - * @param string $filename - * @return string - */ - public function getStubContent(string $filename): string - { - return file_get_contents( - "vendor/wpbones/wpbones/src/Console/stubs/{$filename}.stub" - ); - } - /** * Create a controller * @@ -2248,26 +2476,6 @@ namespace Bones { $this->optimize(); } - /** - * Commodity function to check if ClassName has been requested. - * - * @param string|null $className Optional. Command to check. - * - * @return string - */ - protected function askClassNameIfEmpty(?string $className = ''): string - { - if (empty($className)) { - $className = $this->ask('ClassName:'); - if (empty($className)) { - $this->error('ClassName is required'); - exit(0); - } - } - - return $className; - } - /** * Create a Command controller * @@ -2292,8 +2500,8 @@ namespace Bones { $signature = str_replace('-', '', $this->sanitize($pluginName)); $command = str_replace('-', '', $this->sanitize($className)); - $signature = $this->ask('Enter a signature:', $signature); - $command = $this->ask('Enter the command:', $command); + $signature = $this->ask('Enter a signature', $signature); + $command = $this->ask('Enter the command', $command); // stubbing $content = $this->prepareStub('command', [ @@ -2328,6 +2536,47 @@ namespace Bones { $this->line(' Created plugin/Console/Kernel.php'); } } + protected function simplePluralize($word) { + $strLength = strlen($word); + $last = strtolower($word[$strLength - 1]); + $secondLast = $strLength > 1 ? strtolower($word[$strLength - 2]) : ''; + + // Special cases (irregulars) + $irregular = [ + 'child' => 'children', + 'man' => 'men', + 'woman' => 'women', + 'tooth' => 'teeth', + 'foot' => 'feet', + 'mouse' => 'mice', + 'goose' => 'geese', + 'person' => 'people', + ]; + + if (array_key_exists(strtolower($word), $irregular)) { + return $irregular[strtolower($word)]; + } + + // common words + if ($last === 's' || $last === 'x' || $last === 'z' || ($secondLast . $last) === 'ch' || ($secondLast . $last) === 'sh') { + return $word . 'es'; + } + + if ($last === 'y' && !in_array($secondLast, ['a', 'e', 'i', 'o', 'u'])) { + return substr($word, 0, $strLength - 1) . 'ies'; + } + + if ($last === 'f' || ($secondLast . $last) === 'fe') { + $base = substr($word, 0, $strLength - ($last === 'f' ? 1 : 2)); + return $base . 'ves'; + } + + if ($last === 'o' && in_array($secondLast, ['a', 'e', 'i', 'o', 'u']) === false) { + return $word . 'es'; + } + + return $word . 's'; + } /** * Create a Custom Post Type controller @@ -2343,18 +2592,28 @@ namespace Bones { } // ask className if empty - $className = $this->askClassNameIfEmpty($className); - - $filename = sprintf('%s.php', $className); + $className = $this->askClassNameIfEmpty($className); // current plugin name and namespace [$pluginName, $namespace] = $this->getPluginNameAndNamespace(); + $className = ucfirst($this->sanitize($className)); + $filename = sprintf('%s.php', $className); + $name = ($className); + $plural = ucfirst($this->simplePluralize($className)); + $slug = $this->slugify($namespace,$plural); + $id = $this->ask('Enter a ID', $slug); + $name = $this->ask('Enter the name',$name); + $plural = $this->ask('Enter the plural name',$plural); + + $showInRest = true; + $showInRest = $this->askYesNo('Show in Rest?',$showInRest); - $slug = str_replace('-', '_', $this->sanitize($pluginName)); + $showUI = true; + $showUI = $this->askYesNo('Show in admin sidebar?',$showUI); - $id = $this->ask('Enter a ID:', $slug); - $name = $this->ask('Enter the name:'); - $plural = $this->ask('Enter the plural name:'); + $supports = 'title, editor, thumbnail, excerpt'; + $supports = $this->ask('Features available for the CPT',$supports); + $itemsSupport = $this->stringToArray($supports); if (empty($id)) { $id = $slug; @@ -2362,11 +2621,14 @@ namespace Bones { // stubbing $content = $this->prepareStub('cpt', [ - '{Namespace}' => $namespace, - '{ClassName}' => $className, - '{ID}' => $id, - '{Name}' => $name, - '{Plural}' => $plural, + '{Namespace}' => $namespace, + '{ClassName}' => $className, + '{ID}' => $id, + '{Name}' => $name, + '{Plural}' => $plural, + '{ShowInRest}' => $showInRest, + '{showUI}' => $showUI, + '{Supports}' => "'".join("','",$itemsSupport)."'", ]); // Create the folder if it doesn't exist @@ -2379,9 +2641,28 @@ namespace Bones { $this->line(" Created plugin/CustomPostTypes/{$filename}"); $this->info( - "Remember to add {$className} in the config/plugin.php array in the 'custom_post_types' key." + "Remember to add \\{$namespace}\CustomPostTypes\\{$className} in the config/plugin.php array in the 'custom_post_types' key." ); } + private function stringToArray($content){ + $itemsSupport=[] ; + foreach(explode(",",$content) as $item) + { + if(!empty($item)) + $itemsSupport[]=trim($item); + } + return $itemsSupport; + } + private function slugify($namespace,$name,$remove=0){ + + $subNamespace = substr($namespace,0,20-$remove-strlen($name)-1); + $slug = strtolower($subNamespace.(empty($namespace)?"":"_").str_replace('-', '_', $name)); + $length = strlen($slug); + if($length>20){ + return $this->slugify($namespace,$name,$length-20); + } + return $slug; + } /** * Create a Shortcode controller @@ -2559,24 +2840,29 @@ namespace Bones { } // ask className if empty - $className = $this->askClassNameIfEmpty($className); - - $filename = sprintf('%s.php', $className); + $className = $this->askClassNameIfEmpty($className); + $filename = sprintf('%s.php', $className); // current plugin name and namespace - $namespace = $this->getNamespace(); - - $slug = $this->getPluginId(); - - $id = $this->ask('Enter a ID:', $slug); - $name = $this->ask('Enter the name:'); - $plural = $this->ask('Enter the plural name:'); + $namespace = $this->getNamespace(); + // current plugin name and namespace + $className = ucfirst($this->sanitize($className)); + $name = $className; + $plural = ucfirst($this->simplePluralize($className)); + $slug = $this->slugify("",$plural); + $id = $this->ask('Enter a ID', $slug); + $name = $this->ask('Enter the name',$name); + $plural = $this->ask('Enter the plural name',$plural); + $showInRest = true; + $showInRest = $this->askYesNo('Show in Rest?',$showInRest); $this->line( 'The object type below refers to the id of your previous Custom Post Type' ); - - $objectType = $this->ask('Enter the object type to bound:'); + $objectType ="post"; + $example ="post, your_custom_post_type, etc"; + $objectType = $this->ask('Enter the object type to bound',$objectType,$example); + $objectType = $this->stringToArray($objectType); if (empty($id)) { $id = $slug; @@ -2588,8 +2874,9 @@ namespace Bones { '{ClassName}' => $className, '{ID}' => $id, '{Name}' => $name, + '{ShowInRest}' => $showInRest, '{Plural}' => $plural, - '{ObjectType}' => $objectType, + '{ObjectType}' => "'".join("','",$objectType)."'", ]); // Create the folder if it doesn't exist @@ -2602,7 +2889,7 @@ namespace Bones { $this->line(" Created plugin/CustomTaxonomyTypes/{$filename}"); $this->info( - "Remember to add {$className} in the config/plugin.php array in the 'custom_taxonomy_types' key." + "Remember to add \\{$namespace}\CustomTaxonomyTypes\\{$className} in the config/plugin.php array in the 'custom_taxonomy_types' key." ); } diff --git a/bootstrap/autoload.php b/bootstrap/autoload.php index afc788b..94f1cb9 100644 --- a/bootstrap/autoload.php +++ b/bootstrap/autoload.php @@ -30,12 +30,18 @@ final class WPKirk { public const TEXTDOMAIN = 'wp-kirk'; + public static string $textDomain ="wp-kirk"; public static $plugin; public static $start; } WPKirk::$plugin = require_once __DIR__ . '/plugin.php'; WPKirk::$start = microtime(true); +if (! defined("WPBONES_TEXTDOMAIN")) { + $options = include_once "{$plugin->basePath}/config/options.php"; + define("WPBONES_TEXTDOMAIN", isset($options["textdomain"]) ? $options["textdomain"] : "wp-kirk"); + WPKirk::$textDomain = WPBONES_TEXTDOMAIN; +} // Commodity function to get the plugin instance if (! function_exists('WPKirk')) { diff --git a/config/options.php b/config/options.php index 5b72ba8..2cdc1cd 100644 --- a/config/options.php +++ b/config/options.php @@ -18,6 +18,7 @@ return [ 'version' => '1.0', + "textdomain" => "wp-kirk", 'General' => [ 'option_1' => 'true', 'option_2' => 'true',