- Added GitHub spec-kit for development workflow - Standardized file signatures to Descomplicar® format - Updated development configuration 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
450 lines
11 KiB
PHP
450 lines
11 KiB
PHP
/**
|
|
* Descomplicar® Crescimento Digital
|
|
* https://descomplicar.pt
|
|
*/
|
|
|
|
<?php
|
|
/**
|
|
* Desk-Moloni v3.0 Bootstrap Configuration
|
|
*
|
|
* Initializes the module environment, sets up autoloading,
|
|
* and prepares the system for CLI and web operations.
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
// Define module constants
|
|
if (!defined('DESK_MOLONI_VERSION')) {
|
|
define('DESK_MOLONI_VERSION', '3.0.1');
|
|
}
|
|
|
|
if (!defined('DESK_MOLONI_MODULE_DIR')) {
|
|
define('DESK_MOLONI_MODULE_DIR', dirname(__DIR__));
|
|
}
|
|
|
|
if (!defined('DESK_MOLONI_PROJECT_ROOT')) {
|
|
define('DESK_MOLONI_PROJECT_ROOT', dirname(dirname(dirname(__DIR__))));
|
|
}
|
|
|
|
// Environment detection
|
|
$cli_mode = (php_sapi_name() === 'cli');
|
|
$debug_mode = isset($_ENV['DESK_MOLONI_DEBUG']) ? (bool)$_ENV['DESK_MOLONI_DEBUG'] : false;
|
|
|
|
// Set error reporting based on environment
|
|
if ($debug_mode) {
|
|
error_reporting(E_ALL);
|
|
ini_set('display_errors', '1');
|
|
ini_set('log_errors', '1');
|
|
} else {
|
|
error_reporting(E_ERROR | E_WARNING | E_PARSE);
|
|
ini_set('display_errors', '0');
|
|
ini_set('log_errors', '1');
|
|
}
|
|
|
|
// Set timezone
|
|
if (!ini_get('date.timezone')) {
|
|
date_default_timezone_set('UTC');
|
|
}
|
|
|
|
// Set memory limit for CLI operations
|
|
if ($cli_mode) {
|
|
ini_set('memory_limit', '256M');
|
|
set_time_limit(0);
|
|
}
|
|
|
|
/**
|
|
* Simple autoloader for Desk-Moloni classes
|
|
*/
|
|
spl_autoload_register(function ($className) {
|
|
// Only handle DeskMoloni namespace
|
|
if (strpos($className, 'DeskMoloni\\') !== 0) {
|
|
return false;
|
|
}
|
|
|
|
// Convert namespace to file path
|
|
$relativePath = str_replace('DeskMoloni\\', '', $className);
|
|
$relativePath = str_replace('\\', DIRECTORY_SEPARATOR, $relativePath);
|
|
|
|
// Common locations to check
|
|
$possiblePaths = [
|
|
DESK_MOLONI_MODULE_DIR . '/src/' . $relativePath . '.php',
|
|
DESK_MOLONI_MODULE_DIR . '/lib/' . $relativePath . '.php',
|
|
DESK_MOLONI_MODULE_DIR . '/classes/' . $relativePath . '.php'
|
|
];
|
|
|
|
foreach ($possiblePaths as $path) {
|
|
if (file_exists($path)) {
|
|
require_once $path;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
});
|
|
|
|
/**
|
|
* Load Perfex CRM environment if available
|
|
*/
|
|
function loadPerfexEnvironment(): bool
|
|
{
|
|
$perfexPaths = [
|
|
DESK_MOLONI_PROJECT_ROOT . '/application/config/config.php',
|
|
DESK_MOLONI_PROJECT_ROOT . '/config/config.php',
|
|
dirname(DESK_MOLONI_PROJECT_ROOT) . '/application/config/config.php'
|
|
];
|
|
|
|
foreach ($perfexPaths as $configPath) {
|
|
if (file_exists($configPath)) {
|
|
// Set up basic Perfex environment
|
|
if (!defined('BASEPATH')) {
|
|
define('BASEPATH', dirname($configPath) . '/');
|
|
}
|
|
|
|
if (!defined('APPPATH')) {
|
|
define('APPPATH', dirname($configPath) . '/application/');
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Initialize database connection
|
|
*/
|
|
function initializeDatabase(): ?PDO
|
|
{
|
|
try {
|
|
$configFile = DESK_MOLONI_MODULE_DIR . '/config/config.php';
|
|
|
|
if (!file_exists($configFile)) {
|
|
return null;
|
|
}
|
|
|
|
$config = include $configFile;
|
|
|
|
if (!isset($config['database'])) {
|
|
return null;
|
|
}
|
|
|
|
$dbConfig = $config['database'];
|
|
|
|
// Try to get password from Perfex config if not provided
|
|
$password = $dbConfig['password'] ?? '';
|
|
if (empty($password) && loadPerfexEnvironment()) {
|
|
// In a real implementation, this would extract the password from Perfex config
|
|
$password = ''; // Placeholder
|
|
}
|
|
|
|
$dsn = sprintf(
|
|
'mysql:host=%s;dbname=%s;charset=utf8mb4',
|
|
$dbConfig['host'],
|
|
$dbConfig['database']
|
|
);
|
|
|
|
$options = [
|
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
|
PDO::ATTR_EMULATE_PREPARES => false,
|
|
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci"
|
|
];
|
|
|
|
return new PDO($dsn, $dbConfig['username'], $password, $options);
|
|
|
|
} catch (Exception $e) {
|
|
if ($debug_mode) {
|
|
error_log("Database connection failed: " . $e->getMessage());
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load configuration
|
|
*/
|
|
function loadConfiguration(): array
|
|
{
|
|
$config = [];
|
|
|
|
// Load main config
|
|
$mainConfigFile = DESK_MOLONI_MODULE_DIR . '/config/config.php';
|
|
if (file_exists($mainConfigFile)) {
|
|
$config = include $mainConfigFile;
|
|
}
|
|
|
|
// Load environment-specific config
|
|
$environment = $config['environment'] ?? 'production';
|
|
$envConfigFile = DESK_MOLONI_MODULE_DIR . "/config/config.{$environment}.php";
|
|
if (file_exists($envConfigFile)) {
|
|
$envConfig = include $envConfigFile;
|
|
$config = array_merge_recursive($config, $envConfig);
|
|
}
|
|
|
|
// Apply environment variables
|
|
foreach ($_ENV as $key => $value) {
|
|
if (strpos($key, 'DESK_MOLONI_') === 0) {
|
|
$configKey = strtolower(str_replace('DESK_MOLONI_', '', $key));
|
|
$config[$configKey] = $value;
|
|
}
|
|
}
|
|
|
|
return $config;
|
|
}
|
|
|
|
/**
|
|
* Initialize logging
|
|
*/
|
|
function initializeLogging(array $config): void
|
|
{
|
|
$logDir = DESK_MOLONI_MODULE_DIR . '/logs';
|
|
|
|
if (!is_dir($logDir)) {
|
|
mkdir($logDir, 0755, true);
|
|
}
|
|
|
|
// Set error log path
|
|
$errorLogPath = $logDir . '/error.log';
|
|
ini_set('error_log', $errorLogPath);
|
|
|
|
// Set up custom log handler if needed
|
|
if (isset($config['logging']['level'])) {
|
|
// Custom logging setup would go here
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize CLI environment
|
|
*/
|
|
function initializeCLI(): void
|
|
{
|
|
if (!function_exists('readline')) {
|
|
// Provide basic readline functionality if not available
|
|
function readline($prompt = '') {
|
|
echo $prompt;
|
|
return trim(fgets(STDIN));
|
|
}
|
|
}
|
|
|
|
// Set up signal handlers if available
|
|
if (function_exists('pcntl_signal')) {
|
|
// SIGTERM handler
|
|
pcntl_signal(SIGTERM, function($signo) {
|
|
echo "\nReceived SIGTERM, shutting down gracefully...\n";
|
|
exit(0);
|
|
});
|
|
|
|
// SIGINT handler (Ctrl+C)
|
|
pcntl_signal(SIGINT, function($signo) {
|
|
echo "\nReceived SIGINT, shutting down gracefully...\n";
|
|
exit(0);
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Global exception handler
|
|
*/
|
|
function handleUncaughtException(Throwable $exception): void
|
|
{
|
|
$message = sprintf(
|
|
"[%s] Uncaught %s: %s in %s:%d\nStack trace:\n%s",
|
|
date('Y-m-d H:i:s'),
|
|
get_class($exception),
|
|
$exception->getMessage(),
|
|
$exception->getFile(),
|
|
$exception->getLine(),
|
|
$exception->getTraceAsString()
|
|
);
|
|
|
|
error_log($message);
|
|
|
|
if (php_sapi_name() === 'cli') {
|
|
fprintf(STDERR, "Fatal error: %s\n", $exception->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Global error handler
|
|
*/
|
|
function handleError(int $errno, string $errstr, string $errfile, int $errline): bool
|
|
{
|
|
// Don't handle errors if error_reporting is 0 (@ operator used)
|
|
if (error_reporting() === 0) {
|
|
return false;
|
|
}
|
|
|
|
$errorTypes = [
|
|
E_ERROR => 'ERROR',
|
|
E_WARNING => 'WARNING',
|
|
E_PARSE => 'PARSE',
|
|
E_NOTICE => 'NOTICE',
|
|
E_CORE_ERROR => 'CORE_ERROR',
|
|
E_CORE_WARNING => 'CORE_WARNING',
|
|
E_COMPILE_ERROR => 'COMPILE_ERROR',
|
|
E_COMPILE_WARNING => 'COMPILE_WARNING',
|
|
E_USER_ERROR => 'USER_ERROR',
|
|
E_USER_WARNING => 'USER_WARNING',
|
|
E_USER_NOTICE => 'USER_NOTICE',
|
|
E_STRICT => 'STRICT',
|
|
E_RECOVERABLE_ERROR => 'RECOVERABLE_ERROR',
|
|
E_DEPRECATED => 'DEPRECATED',
|
|
E_USER_DEPRECATED => 'USER_DEPRECATED'
|
|
];
|
|
|
|
$errorType = $errorTypes[$errno] ?? 'UNKNOWN';
|
|
|
|
$message = sprintf(
|
|
"[%s] %s: %s in %s:%d",
|
|
date('Y-m-d H:i:s'),
|
|
$errorType,
|
|
$errstr,
|
|
$errfile,
|
|
$errline
|
|
);
|
|
|
|
error_log($message);
|
|
|
|
// Don't execute PHP internal error handler
|
|
return true;
|
|
}
|
|
|
|
// Bootstrap initialization
|
|
try {
|
|
// Load configuration
|
|
$config = loadConfiguration();
|
|
|
|
// Initialize logging
|
|
initializeLogging($config);
|
|
|
|
// Set exception and error handlers
|
|
set_exception_handler('handleUncaughtException');
|
|
set_error_handler('handleError');
|
|
|
|
// Initialize CLI if in CLI mode
|
|
if ($cli_mode) {
|
|
initializeCLI();
|
|
}
|
|
|
|
// Try to load Perfex environment
|
|
loadPerfexEnvironment();
|
|
|
|
// Initialize database connection (lazy loading)
|
|
$GLOBALS['desk_moloni_db'] = null;
|
|
|
|
// Store configuration globally
|
|
$GLOBALS['desk_moloni_config'] = $config;
|
|
|
|
} catch (Throwable $e) {
|
|
error_log("Bootstrap failed: " . $e->getMessage());
|
|
|
|
if ($cli_mode) {
|
|
fprintf(STDERR, "Fatal error during bootstrap: %s\n", $e->getMessage());
|
|
exit(1);
|
|
} else {
|
|
// In web mode, try to fail gracefully
|
|
http_response_code(500);
|
|
if ($debug_mode) {
|
|
echo "Bootstrap error: " . $e->getMessage();
|
|
} else {
|
|
echo "Internal server error";
|
|
}
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Utility functions
|
|
*/
|
|
|
|
/**
|
|
* Get database connection (lazy initialization)
|
|
*/
|
|
function getDeskMoloniDB(): ?PDO
|
|
{
|
|
if ($GLOBALS['desk_moloni_db'] === null) {
|
|
$GLOBALS['desk_moloni_db'] = initializeDatabase();
|
|
}
|
|
|
|
return $GLOBALS['desk_moloni_db'];
|
|
}
|
|
|
|
/**
|
|
* Get configuration value
|
|
*/
|
|
function getDeskMoloniConfig(string $key = null, $default = null)
|
|
{
|
|
$config = $GLOBALS['desk_moloni_config'] ?? [];
|
|
|
|
if ($key === null) {
|
|
return $config;
|
|
}
|
|
|
|
// Support dot notation for nested keys
|
|
$keys = explode('.', $key);
|
|
$value = $config;
|
|
|
|
foreach ($keys as $k) {
|
|
if (!is_array($value) || !isset($value[$k])) {
|
|
return $default;
|
|
}
|
|
$value = $value[$k];
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* Log message to application log
|
|
*/
|
|
function deskMoloniLog(string $level, string $message, array $context = []): void
|
|
{
|
|
$logFile = DESK_MOLONI_MODULE_DIR . '/logs/application.log';
|
|
|
|
$contextString = '';
|
|
if (!empty($context)) {
|
|
$contextString = ' ' . json_encode($context, JSON_UNESCAPED_SLASHES);
|
|
}
|
|
|
|
$logEntry = sprintf(
|
|
"[%s] [%s] %s%s\n",
|
|
date('Y-m-d H:i:s'),
|
|
strtoupper($level),
|
|
$message,
|
|
$contextString
|
|
);
|
|
|
|
file_put_contents($logFile, $logEntry, FILE_APPEND | LOCK_EX);
|
|
}
|
|
|
|
/**
|
|
* Check if running in CLI mode
|
|
*/
|
|
function isDeskMoloniCLI(): bool
|
|
{
|
|
return php_sapi_name() === 'cli';
|
|
}
|
|
|
|
/**
|
|
* Check if debug mode is enabled
|
|
*/
|
|
function isDeskMoloniDebug(): bool
|
|
{
|
|
return getDeskMoloniConfig('debug', false) ||
|
|
(isset($_ENV['DESK_MOLONI_DEBUG']) && $_ENV['DESK_MOLONI_DEBUG']);
|
|
}
|
|
|
|
/**
|
|
* Get module version
|
|
*/
|
|
function getDeskMoloniVersion(): string
|
|
{
|
|
$versionFile = DESK_MOLONI_MODULE_DIR . '/VERSION';
|
|
|
|
if (file_exists($versionFile)) {
|
|
return trim(file_get_contents($versionFile));
|
|
}
|
|
|
|
return DESK_MOLONI_VERSION;
|
|
} |