fix(perfexcrm module): align version to 3.0.1, unify entrypoint, and harden routes/views
- Bump DESK_MOLONI version to 3.0.1 across module - Normalize hooks to after_client_* and instantiate PerfexHooks safely - Fix OAuthController view path and API client class name - Add missing admin views for webhook config/logs; adjust view loading - Harden client portal routes and admin routes mapping - Make Dashboard/Logs/Queue tolerant to optional model methods - Align log details query with existing schema; avoid broken joins This makes the module operational in Perfex (admin + client), reduces 404s, and avoids fatal errors due to inconsistent tables/methods.
This commit is contained in:
445
modules/desk_moloni/config/bootstrap.php
Normal file
445
modules/desk_moloni/config/bootstrap.php
Normal file
@@ -0,0 +1,445 @@
|
||||
<?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;
|
||||
}
|
||||
Reference in New Issue
Block a user