add_action('after_client_added', 'desk_moloni_sync_customer_added'); hooks()->add_action('after_client_updated', 'desk_moloni_sync_customer_updated'); hooks()->add_action('after_invoice_added', 'desk_moloni_sync_invoice_added'); hooks()->add_action('after_invoice_updated', 'desk_moloni_sync_invoice_updated'); hooks()->add_action('after_estimate_added', 'desk_moloni_sync_estimate_added'); hooks()->add_action('after_estimate_updated', 'desk_moloni_sync_estimate_updated'); hooks()->add_action('after_item_added', 'desk_moloni_sync_item_added'); hooks()->add_action('after_item_updated', 'desk_moloni_sync_item_updated'); /** * Register admin menu hooks */ hooks()->add_action('admin_init', 'desk_moloni_admin_init_hook'); hooks()->add_action('admin_init', 'desk_moloni_init_admin_menu'); /** * Register client portal hooks */ hooks()->add_action('client_init', 'desk_moloni_client_init_hook'); } // Optionally initialize the advanced PerfexHooks class if available if (class_exists('PerfexHooks') && function_exists('hooks')) { // Instantiate once to register internal handlers (safe to try) try { new PerfexHooks(); } catch (Throwable $e) { /* ignore */ } } /** * Module lifecycle hooks - only register if functions exist */ if (function_exists('register_activation_hook')) { register_activation_hook(DESK_MOLONI_MODULE_NAME, 'desk_moloni_activation_hook'); } if (function_exists('register_deactivation_hook')) { register_deactivation_hook(DESK_MOLONI_MODULE_NAME, 'desk_moloni_deactivation_hook'); } if (function_exists('register_uninstall_hook')) { register_uninstall_hook(DESK_MOLONI_MODULE_NAME, 'desk_moloni_uninstall_hook'); } /** * Hook functions */ function desk_moloni_admin_init_hook() { $CI = &get_instance(); // Load module configuration safely try { $config_file = DESK_MOLONI_MODULE_PATH . '/config/config.php'; if (file_exists($config_file)) { include_once $config_file; } } catch (Exception $e) { log_message('error', 'Desk-Moloni: Failed to load config: ' . $e->getMessage()); } // Ensure version option is up to date if (function_exists('update_option')) { update_option('desk_moloni_module_version', DESK_MOLONI_VERSION); } // Add CSS and JS for admin if files exist $css_file = DESK_MOLONI_MODULE_PATH . '/assets/css/admin.css'; $js_file = DESK_MOLONI_MODULE_PATH . '/assets/js/admin.js'; if (file_exists($css_file)) { $CI->app_css->add('desk-moloni-admin-css', base_url('modules/desk_moloni/assets/css/admin.css')); } if (file_exists($js_file)) { $CI->app_scripts->add('desk-moloni-admin-js', base_url('modules/desk_moloni/assets/js/admin.js')); } } function desk_moloni_init_admin_menu() { $CI = &get_instance(); if (has_permission('desk_moloni', '', 'view')) { $CI->app_menu->add_sidebar_menu_item('desk-moloni', [ 'name' => 'Desk-Moloni', 'href' => admin_url('desk_moloni/admin'), 'icon' => 'fa fa-refresh', 'position' => 35, ]); $CI->app_menu->add_sidebar_children_item('desk-moloni', [ 'slug' => 'desk-moloni-dashboard', 'name' => _l('Dashboard'), 'href' => admin_url('desk_moloni/dashboard'), 'position' => 1, ]); $CI->app_menu->add_sidebar_children_item('desk-moloni', [ 'slug' => 'desk-moloni-config', 'name' => _l('Configuration'), 'href' => admin_url('desk_moloni/admin/config'), 'position' => 2, ]); $CI->app_menu->add_sidebar_children_item('desk-moloni', [ 'slug' => 'desk-moloni-sync', 'name' => _l('Synchronization'), 'href' => admin_url('desk_moloni/admin/manual_sync'), 'position' => 3, ]); $CI->app_menu->add_sidebar_children_item('desk-moloni', [ 'slug' => 'desk-moloni-queue', 'name' => _l('Queue Status'), 'href' => admin_url('desk_moloni/queue'), 'position' => 4, ]); $CI->app_menu->add_sidebar_children_item('desk-moloni', [ 'slug' => 'desk-moloni-mapping', 'name' => _l('Mappings'), 'href' => admin_url('desk_moloni/mapping'), 'position' => 5, ]); $CI->app_menu->add_sidebar_children_item('desk-moloni', [ 'slug' => 'desk-moloni-logs', 'name' => _l('Sync Logs'), 'href' => admin_url('desk_moloni/logs'), 'position' => 6, ]); } } function desk_moloni_client_init_hook() { $CI = &get_instance(); // Add client portal CSS and JS only if files exist $css_path = DESK_MOLONI_MODULE_PATH . '/assets/css/client.css'; if (file_exists($css_path)) { $CI->app_css->add('desk-moloni-client-css', base_url('modules/desk_moloni/assets/css/client.css')); } // Skip non-existent JS file to avoid 404 errors // Client portal JavaScript will be loaded when the frontend is properly built // Add client portal tab if helper exists if (function_exists('hooks')) { hooks()->add_action('clients_navigation_end', 'desk_moloni_add_client_tab'); } } function desk_moloni_add_client_tab() { $CI = &get_instance(); echo '
  • '; echo ''; echo ' ' . _l('My Documents'); echo ''; echo '
  • '; } /** * Synchronization hook functions */ function desk_moloni_sync_customer_added($customer_id) { desk_moloni_add_sync_task('sync_client', 'client', $customer_id); } function desk_moloni_sync_customer_updated($customer_id) { desk_moloni_add_sync_task('sync_client', 'client', $customer_id); } function desk_moloni_sync_invoice_added($invoice_id) { desk_moloni_add_sync_task('sync_invoice', 'invoice', $invoice_id); } function desk_moloni_sync_invoice_updated($invoice_id) { desk_moloni_add_sync_task('sync_invoice', 'invoice', $invoice_id); } function desk_moloni_sync_estimate_added($estimate_id) { desk_moloni_add_sync_task('sync_estimate', 'estimate', $estimate_id); } function desk_moloni_sync_estimate_updated($estimate_id) { desk_moloni_add_sync_task('sync_estimate', 'estimate', $estimate_id); } function desk_moloni_sync_item_added($item_id) { desk_moloni_add_sync_task('sync_product', 'product', $item_id); } function desk_moloni_sync_item_updated($item_id) { desk_moloni_add_sync_task('sync_product', 'product', $item_id); } /** * Add task to sync queue */ function desk_moloni_add_sync_task($task_type, $entity_type, $entity_id, $priority = 5) { $CI = &get_instance(); // Check if sync is enabled $sync_enabled = get_option('desk_moloni_sync_enabled'); if (!$sync_enabled) { return false; } // Check if specific entity sync is enabled $entity_sync_key = 'desk_moloni_auto_sync_' . $entity_type . 's'; $entity_sync_enabled = get_option($entity_sync_key); if (!$entity_sync_enabled) { return false; } // Load sync queue model with correct path and alias $CI->load->model('desk_moloni/desk_moloni_sync_queue_model', 'sync_queue_model'); // Add task to queue if (method_exists($CI->sync_queue_model, 'addTask')) { return $CI->sync_queue_model->addTask($task_type, $entity_type, $entity_id, [], $priority); } if (method_exists($CI->sync_queue_model, 'add_task')) { return $CI->sync_queue_model->add_task([ 'task_type' => $task_type, 'entity_type' => $entity_type, 'entity_id' => $entity_id, 'priority' => $priority, ]); } return false; } /** * Module lifecycle hooks */ function desk_moloni_activation_hook() { $CI = &get_instance(); try { // Run database migrations $migration_success = desk_moloni_run_migrations(); if (!$migration_success) { log_message('warning', 'Desk-Moloni: Migration failed, but continuing activation'); } // Create default configuration desk_moloni_create_default_config(); // Setup permissions desk_moloni_setup_permissions(); // Load and run install.php for complete setup $install_file = DESK_MOLONI_MODULE_PATH . '/install.php'; if (file_exists($install_file)) { include_once $install_file; } log_activity('Desk-Moloni module activated successfully'); } catch (Exception $e) { log_message('error', 'Desk-Moloni activation error: ' . $e->getMessage()); log_activity('Desk-Moloni module activation failed: ' . $e->getMessage()); } } function desk_moloni_deactivation_hook() { log_activity('Desk-Moloni module deactivated'); } function desk_moloni_uninstall_hook() { $CI = &get_instance(); // Remove module data (optional - admin choice) $remove_data = get_option('desk_moloni_remove_data_on_uninstall'); if ($remove_data) { desk_moloni_remove_database_tables(); desk_moloni_remove_module_options(); desk_moloni_remove_permissions(); } log_activity('Desk-Moloni module uninstalled'); } /** * Database migration functions */ function desk_moloni_run_migrations() { $CI = &get_instance(); try { $CI->load->database(); $migration_path = DESK_MOLONI_MODULE_PATH . '/database/migrations/'; // Check if migrations directory exists if (!is_dir($migration_path)) { log_message('info', 'Desk-Moloni: No migrations directory found, using install.php for database setup'); return true; } // Get all migration files $migrations = glob($migration_path . '*.sql'); if (empty($migrations)) { log_message('info', 'Desk-Moloni: No migration files found, using install.php for database setup'); return true; } sort($migrations); // Ensure migrations table exists $table_name = db_prefix() . 'desk_moloni_migrations'; if (!$CI->db->table_exists($table_name)) { $CI->db->query("CREATE TABLE IF NOT EXISTS `{$table_name}` ( id INT AUTO_INCREMENT PRIMARY KEY, migration VARCHAR(255) NOT NULL UNIQUE, executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"); } foreach ($migrations as $migration_file) { $migration_name = basename($migration_file, '.sql'); // Check if this specific migration has been run $executed = $CI->db->get_where($table_name, ['migration' => $migration_name])->num_rows(); if ($executed == 0) { // Execute migration $sql = file_get_contents($migration_file); if (!empty($sql)) { $queries = explode(';', $sql); foreach ($queries as $query) { $query = trim($query); if (!empty($query) && strlen($query) > 10) { try { $CI->db->query($query); } catch (Exception $e) { log_message('error', 'Desk-Moloni migration error in ' . $migration_name . ': ' . $e->getMessage()); // Continue with other queries } } } // Record migration as executed $CI->db->insert($table_name, [ 'migration' => $migration_name, 'executed_at' => date('Y-m-d H:i:s') ]); log_activity("Desk-Moloni migration executed: {$migration_name}"); } } } return true; } catch (Exception $e) { log_message('error', 'Desk-Moloni migration error: ' . $e->getMessage()); return false; } } function desk_moloni_create_default_config() { $default_options = [ 'desk_moloni_sync_enabled' => '0', 'desk_moloni_auto_sync_clients' => '1', 'desk_moloni_auto_sync_products' => '0', 'desk_moloni_auto_sync_invoices' => '1', 'desk_moloni_auto_sync_estimates' => '1', 'desk_moloni_queue_processing_enabled' => '1', 'desk_moloni_max_retry_attempts' => '3', 'desk_moloni_sync_timeout' => '30', 'desk_moloni_remove_data_on_uninstall' => '0' ]; foreach ($default_options as $key => $value) { if (get_option($key) === false) { add_option($key, $value); } } } function desk_moloni_setup_permissions() { $CI = &get_instance(); // Check if permission already exists $permission_exists = $CI->db->get_where('tblpermissions', ['name' => 'desk_moloni'])->num_rows(); if ($permission_exists == 0) { // Add module permissions $permissions = [ 'view' => 'View Desk-Moloni', 'create' => 'Create Sync Tasks', 'edit' => 'Edit Configuration', 'delete' => 'Delete Sync Tasks' ]; foreach ($permissions as $short_name => $description) { $CI->db->insert('tblpermissions', [ 'name' => 'desk_moloni', 'shortname' => $short_name, 'description' => $description ]); } } } function desk_moloni_remove_database_tables() { $CI = &get_instance(); $CI->load->database(); // Drop both legacy and current table names for safety $legacyTables = [ 'desk_moloni_config', 'desk_moloni_mapping', 'desk_moloni_sync_queue', 'desk_moloni_sync_log', 'desk_moloni_migrations' ]; $currentTables = [ 'tbldeskmoloni_config', 'tbldeskmoloni_mapping', 'tbldeskmoloni_sync_queue', 'tbldeskmoloni_sync_log', 'tbldeskmoloni_migrations' ]; foreach (array_merge($legacyTables, $currentTables) as $table) { $CI->db->query("DROP TABLE IF EXISTS {$table}"); } } function desk_moloni_remove_module_options() { $CI = &get_instance(); // Remove all module options $CI->db->like('name', 'desk_moloni_', 'after'); $CI->db->delete('tbloptions'); } function desk_moloni_remove_permissions() { $CI = &get_instance(); // Remove module permissions $CI->db->where('name', 'desk_moloni'); $CI->db->delete('tblpermissions'); } /** * Client portal route handler */ function desk_moloni_client_portal_route() { $CI = &get_instance(); // Check if client is logged in if (!is_client_logged_in()) { redirect("clients/login"); return; } // Load the client portal view $CI->load->view("desk_moloni/client_portal/index"); } /** * Register client portal routes */ if (function_exists('hooks')) { hooks()->add_action("clients_init", function() { $CI = &get_instance(); // Register the main client portal route if router is available if (isset($CI->router) && property_exists($CI->router, 'route') && is_array($CI->router->route)) { $CI->router->route["clients/desk_moloni"] = "desk_moloni_client_portal_route"; } }); }