Files
desk-moloni/modules/desk_moloni/controllers/Logs.php
Emanuel Almeida c19f6fd9ee 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.
2025-09-11 17:38:45 +01:00

476 lines
16 KiB
PHP

<?php
defined('BASEPATH') or exit('No direct script access allowed');
/**
* Desk-Moloni Logs Controller
* Handles log viewing and monitoring
*
* @package Desk-Moloni
* @version 3.0.0
* @author Descomplicar Business Solutions
*/
class Logs extends AdminController
{
public function __construct()
{
parent::__construct();
$this->load->model('desk_moloni/desk_moloni_sync_log_model', 'sync_log_model');
$this->load->model('desk_moloni/desk_moloni_config_model', 'config_model');
}
/**
* Logs viewing interface
*/
public function index()
{
if (!has_permission('desk_moloni', '', 'view')) {
access_denied('desk_moloni');
}
$data = [
'title' => _l('desk_moloni_sync_logs'),
'entity_types' => ['client', 'product', 'invoice', 'estimate', 'credit_note'],
'log_stats' => method_exists($this->sync_log_model, 'get_log_statistics') ? $this->sync_log_model->get_log_statistics() : []
];
$this->load->view('admin/includes/header', $data);
$this->load->view('admin/modules/desk_moloni/logs', $data);
$this->load->view('admin/includes/footer');
}
/**
* Get logs with filtering and pagination
*/
public function get_logs()
{
if (!has_permission('desk_moloni', '', 'view')) {
$this->output
->set_status_header(403)
->set_content_type('application/json')
->set_output(json_encode(['success' => false, 'message' => _l('access_denied')]));
return;
}
try {
$filters = [
'entity_type' => $this->input->get('entity_type'),
'status' => $this->input->get('status'),
'operation_type' => $this->input->get('operation_type'),
'direction' => $this->input->get('direction'),
'from_date' => $this->input->get('from_date'),
'to_date' => $this->input->get('to_date'),
'search' => $this->input->get('search')
];
$pagination = [
'limit' => (int) $this->input->get('limit') ?: 100,
'offset' => (int) $this->input->get('offset') ?: 0
];
$sort = [
'field' => $this->input->get('sort_field') ?: 'created_at',
'direction' => $this->input->get('sort_direction') ?: 'desc'
];
$log_data = $this->sync_log_model->get_filtered_logs($filters, $pagination, $sort);
$this->output
->set_content_type('application/json')
->set_output(json_encode([
'success' => true,
'data' => [
'total' => $log_data['total'],
'logs' => $log_data['logs'],
'pagination' => [
'current_page' => floor($pagination['offset'] / $pagination['limit']) + 1,
'per_page' => $pagination['limit'],
'total_items' => $log_data['total'],
'total_pages' => ceil($log_data['total'] / $pagination['limit'])
]
]
]));
} catch (Exception $e) {
log_message('error', 'Desk-Moloni get logs error: ' . $e->getMessage());
$this->output
->set_status_header(500)
->set_content_type('application/json')
->set_output(json_encode([
'success' => false,
'message' => $e->getMessage()
]));
}
}
/**
* Get detailed log entry
*/
public function get_log_details($log_id)
{
if (!has_permission('desk_moloni', '', 'view')) {
$this->output
->set_status_header(403)
->set_content_type('application/json')
->set_output(json_encode(['success' => false, 'message' => _l('access_denied')]));
return;
}
try {
$log_id = (int) $log_id;
if (!$log_id) {
throw new Exception(_l('desk_moloni_invalid_log_id'));
}
$log = method_exists($this->sync_log_model, 'get_log_details') ? $this->sync_log_model->get_log_details($log_id) : null;
if (!$log) {
throw new Exception(_l('desk_moloni_log_not_found'));
}
$this->output
->set_content_type('application/json')
->set_output(json_encode([
'success' => true,
'data' => $log
]));
} catch (Exception $e) {
log_message('error', 'Desk-Moloni get log details error: ' . $e->getMessage());
$this->output
->set_status_header(400)
->set_content_type('application/json')
->set_output(json_encode([
'success' => false,
'message' => $e->getMessage()
]));
}
}
/**
* Get log statistics
*/
public function get_statistics()
{
if (!has_permission('desk_moloni', '', 'view')) {
$this->output
->set_status_header(403)
->set_content_type('application/json')
->set_output(json_encode(['success' => false, 'message' => _l('access_denied')]));
return;
}
try {
$days = (int) $this->input->get('days') ?: 7;
$entity_type = $this->input->get('entity_type');
$statistics = [
'summary' => $this->sync_log_model->get_log_summary($days, $entity_type),
'trends' => $this->sync_log_model->get_log_trends($days, $entity_type),
'top_errors' => $this->sync_log_model->get_top_errors($days, 10),
'performance_stats' => $this->sync_log_model->get_performance_statistics($days, $entity_type)
];
$this->output
->set_content_type('application/json')
->set_output(json_encode([
'success' => true,
'data' => $statistics
]));
} catch (Exception $e) {
log_message('error', 'Desk-Moloni log statistics error: ' . $e->getMessage());
$this->output
->set_status_header(500)
->set_content_type('application/json')
->set_output(json_encode([
'success' => false,
'message' => $e->getMessage()
]));
}
}
/**
* Export logs
*/
public function export()
{
if (!has_permission('desk_moloni_view', '', 'view')) {
access_denied('desk_moloni');
}
try {
$format = $this->input->get('format') ?: 'csv';
$filters = [
'entity_type' => $this->input->get('entity_type'),
'status' => $this->input->get('status'),
'operation_type' => $this->input->get('operation_type'),
'direction' => $this->input->get('direction'),
'from_date' => $this->input->get('from_date'),
'to_date' => $this->input->get('to_date'),
'search' => $this->input->get('search')
];
$logs = $this->sync_log_model->get_logs_for_export($filters);
if ($format === 'json') {
$this->_export_as_json($logs);
} else {
$this->_export_as_csv($logs);
}
} catch (Exception $e) {
log_message('error', 'Desk-Moloni export logs error: ' . $e->getMessage());
set_alert('danger', $e->getMessage());
redirect(admin_url('modules/desk_moloni/logs'));
}
}
/**
* Clear old logs
*/
public function clear_old_logs()
{
if (!has_permission('desk_moloni', '', 'delete')) {
$this->output
->set_status_header(403)
->set_content_type('application/json')
->set_output(json_encode(['success' => false, 'message' => _l('access_denied')]));
return;
}
try {
$days_old = (int) $this->input->post('days_old') ?: 30;
$keep_errors = $this->input->post('keep_errors') === '1';
$deleted_count = $this->sync_log_model->clear_old_logs($days_old, $keep_errors);
$this->output
->set_content_type('application/json')
->set_output(json_encode([
'success' => true,
'message' => sprintf(
_l('desk_moloni_logs_cleared'),
$deleted_count
),
'data' => ['deleted_count' => $deleted_count]
]));
} catch (Exception $e) {
log_message('error', 'Desk-Moloni clear logs error: ' . $e->getMessage());
$this->output
->set_status_header(500)
->set_content_type('application/json')
->set_output(json_encode([
'success' => false,
'message' => $e->getMessage()
]));
}
}
/**
* Get error analysis
*/
public function get_error_analysis()
{
if (!has_permission('desk_moloni_view', '', 'view')) {
$this->output
->set_status_header(403)
->set_content_type('application/json')
->set_output(json_encode(['success' => false, 'message' => _l('access_denied')]));
return;
}
try {
$days = (int) $this->input->get('days') ?: 7;
$analysis = [
'error_categories' => $this->sync_log_model->get_error_categories($days),
'error_trends' => $this->sync_log_model->get_error_trends($days),
'frequent_errors' => $this->sync_log_model->get_frequent_errors($days, 20),
'error_by_entity' => $this->sync_log_model->get_errors_by_entity($days),
'resolution_suggestions' => $this->_get_resolution_suggestions()
];
$this->output
->set_content_type('application/json')
->set_output(json_encode([
'success' => true,
'data' => $analysis
]));
} catch (Exception $e) {
log_message('error', 'Desk-Moloni error analysis error: ' . $e->getMessage());
$this->output
->set_status_header(500)
->set_content_type('application/json')
->set_output(json_encode([
'success' => false,
'message' => $e->getMessage()
]));
}
}
/**
* Search logs
*/
public function search()
{
if (!has_permission('desk_moloni_view', '', 'view')) {
$this->output
->set_status_header(403)
->set_content_type('application/json')
->set_output(json_encode(['success' => false, 'message' => _l('access_denied')]));
return;
}
try {
$query = $this->input->get('q');
$limit = (int) $this->input->get('limit') ?: 50;
if (empty($query) || strlen($query) < 3) {
throw new Exception(_l('desk_moloni_search_query_too_short'));
}
$results = $this->sync_log_model->search_logs($query, $limit);
$this->output
->set_content_type('application/json')
->set_output(json_encode([
'success' => true,
'data' => $results
]));
} catch (Exception $e) {
log_message('error', 'Desk-Moloni search logs error: ' . $e->getMessage());
$this->output
->set_status_header(400)
->set_content_type('application/json')
->set_output(json_encode([
'success' => false,
'message' => $e->getMessage()
]));
}
}
/**
* Export logs as CSV
*/
private function _export_as_csv($logs)
{
$filename = 'desk_moloni_logs_' . date('Y-m-d_H-i-s') . '.csv';
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Cache-Control: no-cache, must-revalidate');
header('Expires: Sat, 26 Jul 1997 05:00:00 GMT');
$output = fopen('php://output', 'w');
// CSV headers
$headers = [
'ID',
'Timestamp',
'Operation',
'Entity Type',
'Perfex ID',
'Moloni ID',
'Direction',
'Status',
'Execution Time (ms)',
'Error Message'
];
fputcsv($output, $headers);
// CSV data
foreach ($logs as $log) {
$row = [
$log['id'],
$log['created_at'],
$log['operation_type'],
$log['entity_type'],
$log['perfex_id'] ?: '',
$log['moloni_id'] ?: '',
$log['direction'],
$log['status'],
$log['execution_time_ms'] ?: '',
$log['error_message'] ?: ''
];
fputcsv($output, $row);
}
fclose($output);
exit;
}
/**
* Export logs as JSON
*/
private function _export_as_json($logs)
{
$filename = 'desk_moloni_logs_' . date('Y-m-d_H-i-s') . '.json';
header('Content-Type: application/json');
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Cache-Control: no-cache, must-revalidate');
header('Expires: Sat, 26 Jul 1997 05:00:00 GMT');
echo json_encode([
'export_date' => date('Y-m-d H:i:s'),
'total_records' => count($logs),
'logs' => $logs
], JSON_PRETTY_PRINT);
exit;
}
/**
* Get resolution suggestions for common errors
*/
private function _get_resolution_suggestions()
{
return [
'authentication_failed' => [
'title' => _l('desk_moloni_auth_error_title'),
'description' => _l('desk_moloni_auth_error_desc'),
'actions' => [
_l('desk_moloni_refresh_oauth_token'),
_l('desk_moloni_check_api_credentials')
]
],
'rate_limit_exceeded' => [
'title' => _l('desk_moloni_rate_limit_title'),
'description' => _l('desk_moloni_rate_limit_desc'),
'actions' => [
_l('desk_moloni_reduce_sync_frequency'),
_l('desk_moloni_implement_backoff')
]
],
'validation_error' => [
'title' => _l('desk_moloni_validation_error_title'),
'description' => _l('desk_moloni_validation_error_desc'),
'actions' => [
_l('desk_moloni_check_required_fields'),
_l('desk_moloni_verify_data_format')
]
],
'network_error' => [
'title' => _l('desk_moloni_network_error_title'),
'description' => _l('desk_moloni_network_error_desc'),
'actions' => [
_l('desk_moloni_check_connectivity'),
_l('desk_moloni_verify_firewall')
]
]
];
}
}