- 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.
812 lines
24 KiB
PHP
812 lines
24 KiB
PHP
<?php
|
|
|
|
defined('BASEPATH') or exit('No direct script access allowed');
|
|
|
|
/**
|
|
* Desk-Moloni Helper Functions
|
|
*
|
|
* Collection of utility functions for the Desk-Moloni module
|
|
* to simplify common operations and provide consistent interfaces.
|
|
*
|
|
* @package DeskMoloni
|
|
* @subpackage Helpers
|
|
* @version 3.0.0
|
|
* @author Descomplicar®
|
|
*/
|
|
|
|
if (!function_exists('desk_moloni_log')) {
|
|
/**
|
|
* Centralized logging function for Desk-Moloni
|
|
*
|
|
* @param string $level Log level (error, info, debug, warning)
|
|
* @param string $message Log message
|
|
* @param array $context Additional context data
|
|
* @param string $category Log category (api, sync, oauth, etc.)
|
|
*/
|
|
function desk_moloni_log($level, $message, $context = [], $category = 'general')
|
|
{
|
|
$timestamp = date('Y-m-d H:i:s');
|
|
$formatted_message = "[$timestamp] [DESK-MOLONI] [$category] [$level] $message";
|
|
|
|
if (!empty($context)) {
|
|
$formatted_message .= ' | Context: ' . json_encode($context, JSON_UNESCAPED_UNICODE);
|
|
}
|
|
|
|
// Log to CodeIgniter log
|
|
log_message($level, $formatted_message);
|
|
|
|
// Also log to custom desk_moloni log file if debug mode is enabled
|
|
if (get_option('desk_moloni_debug_mode') == '1') {
|
|
$log_dir = APPPATH . '../uploads/desk_moloni/logs/';
|
|
if (!is_dir($log_dir)) {
|
|
mkdir($log_dir, 0755, true);
|
|
}
|
|
|
|
$log_file = $log_dir . 'desk_moloni_' . date('Y-m-d') . '.log';
|
|
file_put_contents($log_file, $formatted_message . PHP_EOL, FILE_APPEND | LOCK_EX);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!function_exists('desk_moloni_log_api')) {
|
|
/**
|
|
* Specialized logging for API calls
|
|
*/
|
|
function desk_moloni_log_api($endpoint, $method, $data = [], $response = [], $execution_time = null)
|
|
{
|
|
$context = [
|
|
'endpoint' => $endpoint,
|
|
'method' => $method,
|
|
'request_data' => $data,
|
|
'response_data' => $response,
|
|
'execution_time_ms' => $execution_time
|
|
];
|
|
|
|
desk_moloni_log('info', "API Call: $method $endpoint", $context, 'api');
|
|
}
|
|
}
|
|
|
|
if (!function_exists('desk_moloni_log_sync')) {
|
|
/**
|
|
* Specialized logging for sync operations
|
|
*/
|
|
function desk_moloni_log_sync($entity_type, $entity_id, $action, $status, $details = [])
|
|
{
|
|
$context = [
|
|
'entity_type' => $entity_type,
|
|
'entity_id' => $entity_id,
|
|
'action' => $action,
|
|
'status' => $status,
|
|
'details' => $details
|
|
];
|
|
|
|
$level = ($status === 'success') ? 'info' : 'error';
|
|
desk_moloni_log($level, "Sync $action for $entity_type #$entity_id: $status", $context, 'sync');
|
|
}
|
|
}
|
|
|
|
if (!function_exists('desk_moloni_is_enabled')) {
|
|
/**
|
|
* Check if Desk-Moloni module is enabled
|
|
*
|
|
* @return bool
|
|
*/
|
|
function desk_moloni_is_enabled()
|
|
{
|
|
return get_option('desk_moloni_enabled') == '1';
|
|
}
|
|
}
|
|
|
|
if (!function_exists('validate_moloni_data')) {
|
|
/**
|
|
* Centralized data validation for Moloni data
|
|
*
|
|
* @param array $data Data to validate
|
|
* @param array $rules Validation rules
|
|
* @param array $messages Custom error messages
|
|
* @return array ['valid' => bool, 'errors' => array]
|
|
*/
|
|
function validate_moloni_data($data, $rules, $messages = [])
|
|
{
|
|
$CI = &get_instance();
|
|
$CI->load->library('form_validation');
|
|
|
|
// Clear previous rules
|
|
$CI->form_validation->reset_validation();
|
|
|
|
// Set validation rules
|
|
foreach ($rules as $field => $rule) {
|
|
$label = isset($messages[$field]) ? $messages[$field] : ucfirst(str_replace('_', ' ', $field));
|
|
$CI->form_validation->set_rules($field, $label, $rule, $messages);
|
|
}
|
|
|
|
$is_valid = $CI->form_validation->run($data);
|
|
|
|
$result = [
|
|
'valid' => $is_valid,
|
|
'errors' => []
|
|
];
|
|
|
|
if (!$is_valid) {
|
|
$result['errors'] = $CI->form_validation->error_array();
|
|
desk_moloni_log('warning', 'Validation failed', [
|
|
'data' => $data,
|
|
'errors' => $result['errors']
|
|
], 'validation');
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('validate_moloni_client')) {
|
|
/**
|
|
* Validate client data for Moloni
|
|
*/
|
|
function validate_moloni_client($client_data)
|
|
{
|
|
$rules = [
|
|
'name' => 'required|max_length[255]',
|
|
'vat' => 'required|exact_length[9]|numeric',
|
|
'email' => 'valid_email',
|
|
'phone' => 'max_length[20]',
|
|
'address' => 'max_length[255]',
|
|
'city' => 'max_length[100]',
|
|
'zip_code' => 'max_length[20]',
|
|
'country_id' => 'required|numeric'
|
|
];
|
|
|
|
$messages = [
|
|
'name' => 'Client name',
|
|
'vat' => 'VAT number',
|
|
'email' => 'Email address',
|
|
'phone' => 'Phone number',
|
|
'address' => 'Address',
|
|
'city' => 'City',
|
|
'zip_code' => 'ZIP code',
|
|
'country_id' => 'Country'
|
|
];
|
|
|
|
return validate_moloni_data($client_data, $rules, $messages);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('validate_moloni_product')) {
|
|
/**
|
|
* Validate product data for Moloni
|
|
*/
|
|
function validate_moloni_product($product_data)
|
|
{
|
|
$rules = [
|
|
'name' => 'required|max_length[255]',
|
|
'reference' => 'max_length[50]',
|
|
'price' => 'required|numeric|greater_than[0]',
|
|
'category_id' => 'numeric',
|
|
'unit_id' => 'numeric',
|
|
'tax_id' => 'required|numeric',
|
|
'stock_enabled' => 'in_list[0,1]'
|
|
];
|
|
|
|
$messages = [
|
|
'name' => 'Product name',
|
|
'reference' => 'Product reference',
|
|
'price' => 'Product price',
|
|
'category_id' => 'Category',
|
|
'unit_id' => 'Unit',
|
|
'tax_id' => 'Tax',
|
|
'stock_enabled' => 'Stock control'
|
|
];
|
|
|
|
return validate_moloni_data($product_data, $rules, $messages);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('validate_moloni_invoice')) {
|
|
/**
|
|
* Validate invoice data for Moloni
|
|
*/
|
|
function validate_moloni_invoice($invoice_data)
|
|
{
|
|
$rules = [
|
|
'customer_id' => 'required|numeric',
|
|
'document_type' => 'required|in_list[invoices,receipts,bills_of_lading,estimates]',
|
|
'products' => 'required|is_array',
|
|
'date' => 'required|valid_date[Y-m-d]',
|
|
'due_date' => 'valid_date[Y-m-d]',
|
|
'notes' => 'max_length[500]'
|
|
];
|
|
|
|
$messages = [
|
|
'customer_id' => 'Customer',
|
|
'document_type' => 'Document type',
|
|
'products' => 'Products',
|
|
'date' => 'Invoice date',
|
|
'due_date' => 'Due date',
|
|
'notes' => 'Notes'
|
|
];
|
|
|
|
// Validate main invoice data
|
|
$validation = validate_moloni_data($invoice_data, $rules, $messages);
|
|
|
|
// Validate products if present
|
|
if (isset($invoice_data['products']) && is_array($invoice_data['products'])) {
|
|
foreach ($invoice_data['products'] as $index => $product) {
|
|
$product_rules = [
|
|
'product_id' => 'required|numeric',
|
|
'qty' => 'required|numeric|greater_than[0]',
|
|
'price' => 'required|numeric|greater_than_equal_to[0]'
|
|
];
|
|
|
|
$product_validation = validate_moloni_data($product, $product_rules);
|
|
if (!$product_validation['valid']) {
|
|
$validation['valid'] = false;
|
|
foreach ($product_validation['errors'] as $field => $error) {
|
|
$validation['errors']["products[{$index}][{$field}]"] = $error;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $validation;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('validate_moloni_api_response')) {
|
|
/**
|
|
* Validate Moloni API response
|
|
*/
|
|
function validate_moloni_api_response($response)
|
|
{
|
|
if (!is_array($response)) {
|
|
return [
|
|
'valid' => false,
|
|
'errors' => ['Invalid response format']
|
|
];
|
|
}
|
|
|
|
// Check for API errors
|
|
if (isset($response['error'])) {
|
|
return [
|
|
'valid' => false,
|
|
'errors' => [$response['error']]
|
|
];
|
|
}
|
|
|
|
return [
|
|
'valid' => true,
|
|
'errors' => []
|
|
];
|
|
}
|
|
}
|
|
|
|
if (!function_exists('sanitize_moloni_data')) {
|
|
/**
|
|
* Sanitize data for Moloni API
|
|
*/
|
|
function sanitize_moloni_data($data)
|
|
{
|
|
if (is_array($data)) {
|
|
$sanitized = [];
|
|
foreach ($data as $key => $value) {
|
|
$sanitized[$key] = sanitize_moloni_data($value);
|
|
}
|
|
return $sanitized;
|
|
}
|
|
|
|
if (is_string($data)) {
|
|
// Remove harmful characters, trim whitespace
|
|
$data = trim($data);
|
|
$data = filter_var($data, FILTER_SANITIZE_STRING, FILTER_FLAG_NO_ENCODE_QUOTES);
|
|
return $data;
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('verify_desk_moloni_csrf')) {
|
|
/**
|
|
* Verify CSRF token for Desk-Moloni forms
|
|
*
|
|
* @param bool $ajax_response Return JSON response for AJAX requests
|
|
* @return bool|void True if valid, false or exit if invalid
|
|
*/
|
|
function verify_desk_moloni_csrf($ajax_response = false)
|
|
{
|
|
$CI = &get_instance();
|
|
|
|
// Check if CSRF verification is enabled
|
|
if ($CI->config->item('csrf_protection') !== TRUE) {
|
|
return true;
|
|
}
|
|
|
|
$token_name = $CI->security->get_csrf_token_name();
|
|
$posted_token = $CI->input->post($token_name);
|
|
$session_token = $CI->security->get_csrf_hash();
|
|
|
|
if (!$posted_token || !hash_equals($session_token, $posted_token)) {
|
|
desk_moloni_log('warning', 'CSRF token validation failed', [
|
|
'ip' => $CI->input->ip_address(),
|
|
'user_agent' => $CI->input->user_agent(),
|
|
'posted_token' => $posted_token ? 'present' : 'missing',
|
|
'session_token' => $session_token ? 'present' : 'missing'
|
|
], 'security');
|
|
|
|
if ($ajax_response) {
|
|
header('Content-Type: application/json');
|
|
echo json_encode([
|
|
'success' => false,
|
|
'error' => 'Invalid security token. Please refresh the page and try again.',
|
|
'csrf_error' => true
|
|
]);
|
|
exit;
|
|
} else {
|
|
show_error('Invalid security token. Please refresh the page and try again.', 403, 'Security Error');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('get_desk_moloni_csrf_data')) {
|
|
/**
|
|
* Get CSRF data for JavaScript/AJAX use
|
|
*
|
|
* @return array CSRF token name and value
|
|
*/
|
|
function get_desk_moloni_csrf_data()
|
|
{
|
|
$CI = &get_instance();
|
|
|
|
return [
|
|
'name' => $CI->security->get_csrf_token_name(),
|
|
'value' => $CI->security->get_csrf_hash()
|
|
];
|
|
}
|
|
}
|
|
|
|
if (!function_exists('include_csrf_protection')) {
|
|
/**
|
|
* Include CSRF protection in forms - shortcut function
|
|
*/
|
|
function include_csrf_protection()
|
|
{
|
|
$CI = &get_instance();
|
|
$token_name = $CI->security->get_csrf_token_name();
|
|
$token_value = $CI->security->get_csrf_hash();
|
|
|
|
echo '<input type="hidden" name="' . $token_name . '" value="' . $token_value . '">';
|
|
}
|
|
}
|
|
|
|
if (!function_exists('desk_moloni_get_api_client')) {
|
|
/**
|
|
* Get configured API client instance
|
|
*
|
|
* @return object|null
|
|
*/
|
|
function desk_moloni_get_api_client()
|
|
{
|
|
$CI = &get_instance();
|
|
$CI->load->library('desk_moloni/moloni_api_client');
|
|
return $CI->moloni_api_client;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('desk_moloni_format_currency')) {
|
|
/**
|
|
* Format currency value for Moloni API
|
|
*
|
|
* @param float $amount
|
|
* @param string $currency
|
|
* @return string
|
|
*/
|
|
function desk_moloni_format_currency($amount, $currency = 'EUR')
|
|
{
|
|
return number_format((float)$amount, 2, '.', '');
|
|
}
|
|
}
|
|
|
|
if (!function_exists('desk_moloni_validate_vat')) {
|
|
/**
|
|
* Validate VAT number format
|
|
*
|
|
* @param string $vat
|
|
* @param string $country_code
|
|
* @return bool
|
|
*/
|
|
function desk_moloni_validate_vat($vat, $country_code = 'PT')
|
|
{
|
|
$vat = preg_replace('/[^0-9A-Za-z]/', '', $vat);
|
|
|
|
switch (strtoupper($country_code)) {
|
|
case 'PT':
|
|
return preg_match('/^[0-9]{9}$/', $vat);
|
|
case 'ES':
|
|
return preg_match('/^[A-Z0-9][0-9]{7}[A-Z0-9]$/', $vat);
|
|
default:
|
|
return strlen($vat) >= 8 && strlen($vat) <= 12;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!function_exists('desk_moloni_get_sync_status')) {
|
|
/**
|
|
* Get synchronization status for an entity
|
|
*
|
|
* @param string $entity_type
|
|
* @param int $entity_id
|
|
* @return string|null
|
|
*/
|
|
function desk_moloni_get_sync_status($entity_type, $entity_id)
|
|
{
|
|
$CI = &get_instance();
|
|
|
|
$mapping = $CI->db->get_where(db_prefix() . 'desk_moloni_entity_mappings', [
|
|
'entity_type' => $entity_type,
|
|
'perfex_id' => $entity_id
|
|
])->row();
|
|
|
|
return $mapping ? $mapping->sync_status : null;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('desk_moloni_queue_sync')) {
|
|
/**
|
|
* Queue an entity for synchronization
|
|
*
|
|
* @param string $entity_type
|
|
* @param int $entity_id
|
|
* @param string $action
|
|
* @param string $priority
|
|
* @return bool
|
|
*/
|
|
function desk_moloni_queue_sync($entity_type, $entity_id, $action = 'sync', $priority = 'normal')
|
|
{
|
|
$CI = &get_instance();
|
|
|
|
$queue_data = [
|
|
'entity_type' => $entity_type,
|
|
'entity_id' => $entity_id,
|
|
'action' => $action,
|
|
'priority' => $priority,
|
|
'status' => 'pending',
|
|
'created_at' => date('Y-m-d H:i:s'),
|
|
'created_by' => get_staff_user_id()
|
|
];
|
|
|
|
return $CI->db->insert(db_prefix() . 'desk_moloni_sync_queue', $queue_data);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('desk_moloni_log_error')) {
|
|
/**
|
|
* Log an error to the error logs table
|
|
*
|
|
* @param string $error_type
|
|
* @param string $message
|
|
* @param array $context
|
|
* @param string $severity
|
|
* @return bool
|
|
*/
|
|
function desk_moloni_log_error($error_type, $message, $context = [], $severity = 'medium')
|
|
{
|
|
$CI = &get_instance();
|
|
|
|
$error_data = [
|
|
'error_type' => $error_type,
|
|
'severity' => $severity,
|
|
'message' => $message,
|
|
'context' => !empty($context) ? json_encode($context) : null,
|
|
'created_at' => date('Y-m-d H:i:s'),
|
|
'created_by' => get_staff_user_id(),
|
|
'ip_address' => $CI->input->ip_address()
|
|
];
|
|
|
|
return $CI->db->insert(db_prefix() . 'desk_moloni_error_logs', $error_data);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('desk_moloni_encrypt_data')) {
|
|
/**
|
|
* Encrypt sensitive data
|
|
*
|
|
* @param mixed $data
|
|
* @param string $context
|
|
* @return string|false
|
|
*/
|
|
function desk_moloni_encrypt_data($data, $context = '')
|
|
{
|
|
if (!get_option('desk_moloni_enable_encryption')) {
|
|
return $data;
|
|
}
|
|
|
|
$CI = &get_instance();
|
|
$CI->load->library('desk_moloni/Encryption');
|
|
|
|
try {
|
|
return $CI->encryption->encrypt($data, $context);
|
|
} catch (Exception $e) {
|
|
desk_moloni_log_error('encryption', 'Failed to encrypt data: ' . $e->getMessage());
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!function_exists('desk_moloni_decrypt_data')) {
|
|
/**
|
|
* Decrypt sensitive data
|
|
*
|
|
* @param string $encrypted_data
|
|
* @param string $context
|
|
* @return mixed|false
|
|
*/
|
|
function desk_moloni_decrypt_data($encrypted_data, $context = '')
|
|
{
|
|
if (!get_option('desk_moloni_enable_encryption')) {
|
|
return $encrypted_data;
|
|
}
|
|
|
|
$CI = &get_instance();
|
|
$CI->load->library('desk_moloni/Encryption');
|
|
|
|
try {
|
|
return $CI->encryption->decrypt($encrypted_data, $context);
|
|
} catch (Exception $e) {
|
|
desk_moloni_log_error('decryption', 'Failed to decrypt data: ' . $e->getMessage());
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!function_exists('desk_moloni_get_performance_metrics')) {
|
|
/**
|
|
* Get performance metrics for a time period
|
|
*
|
|
* @param string $metric_type
|
|
* @param string $start_date
|
|
* @param string $end_date
|
|
* @return array
|
|
*/
|
|
function desk_moloni_get_performance_metrics($metric_type = null, $start_date = null, $end_date = null)
|
|
{
|
|
$CI = &get_instance();
|
|
|
|
$CI->db->select('metric_name, AVG(metric_value) as avg_value, MAX(metric_value) as max_value, MIN(metric_value) as min_value, COUNT(*) as count')
|
|
->from(db_prefix() . 'desk_moloni_performance_metrics');
|
|
|
|
if ($metric_type) {
|
|
$CI->db->where('metric_type', $metric_type);
|
|
}
|
|
|
|
if ($start_date) {
|
|
$CI->db->where('recorded_at >=', $start_date);
|
|
}
|
|
|
|
if ($end_date) {
|
|
$CI->db->where('recorded_at <=', $end_date);
|
|
}
|
|
|
|
$CI->db->group_by('metric_name');
|
|
|
|
return $CI->db->get()->result_array();
|
|
}
|
|
}
|
|
|
|
if (!function_exists('desk_moloni_clean_phone_number')) {
|
|
/**
|
|
* Clean and format phone number
|
|
*
|
|
* @param string $phone
|
|
* @return string
|
|
*/
|
|
function desk_moloni_clean_phone_number($phone)
|
|
{
|
|
// Remove all non-digit characters except +
|
|
$cleaned = preg_replace('/[^+\d]/', '', $phone);
|
|
|
|
// If starts with 00, replace with +
|
|
if (substr($cleaned, 0, 2) === '00') {
|
|
$cleaned = '+' . substr($cleaned, 2);
|
|
}
|
|
|
|
// If Portuguese number without country code, add it
|
|
if (preg_match('/^[29]\d{8}$/', $cleaned)) {
|
|
$cleaned = '+351' . $cleaned;
|
|
}
|
|
|
|
return $cleaned;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('desk_moloni_sanitize_html')) {
|
|
/**
|
|
* Sanitize HTML content for safe storage
|
|
*
|
|
* @param string $html
|
|
* @return string
|
|
*/
|
|
function desk_moloni_sanitize_html($html)
|
|
{
|
|
$allowed_tags = '<p><br><strong><b><em><i><u><ul><ol><li><a>';
|
|
return strip_tags($html, $allowed_tags);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('desk_moloni_generate_reference')) {
|
|
/**
|
|
* Generate a unique reference for sync operations
|
|
*
|
|
* @param string $prefix
|
|
* @return string
|
|
*/
|
|
function desk_moloni_generate_reference($prefix = 'DM')
|
|
{
|
|
return $prefix . date('YmdHis') . sprintf('%04d', mt_rand(0, 9999));
|
|
}
|
|
}
|
|
|
|
if (!function_exists('desk_moloni_is_debug_mode')) {
|
|
/**
|
|
* Check if debug mode is enabled
|
|
*
|
|
* @return bool
|
|
*/
|
|
function desk_moloni_is_debug_mode()
|
|
{
|
|
return get_option('desk_moloni_debug_mode') == '1' || ENVIRONMENT === 'development';
|
|
}
|
|
}
|
|
|
|
if (!function_exists('desk_moloni_cache_key')) {
|
|
/**
|
|
* Generate a cache key for the given parameters
|
|
*
|
|
* @param string $type
|
|
* @param mixed ...$params
|
|
* @return string
|
|
*/
|
|
function desk_moloni_cache_key($type, ...$params)
|
|
{
|
|
$key_parts = [$type];
|
|
foreach ($params as $param) {
|
|
$key_parts[] = is_array($param) ? md5(serialize($param)) : (string)$param;
|
|
}
|
|
return 'desk_moloni:' . implode(':', $key_parts);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('desk_moloni_get_cached_data')) {
|
|
/**
|
|
* Get data from cache
|
|
*
|
|
* @param string $key
|
|
* @param mixed $default
|
|
* @return mixed
|
|
*/
|
|
function desk_moloni_get_cached_data($key, $default = null)
|
|
{
|
|
if (!get_option('desk_moloni_enable_caching')) {
|
|
return $default;
|
|
}
|
|
|
|
$CI = &get_instance();
|
|
|
|
// Try to get from Redis if available
|
|
if (get_option('desk_moloni_enable_redis')) {
|
|
$CI->load->library('redis');
|
|
$data = $CI->redis->get($key);
|
|
return $data !== null ? json_decode($data, true) : $default;
|
|
}
|
|
|
|
// Fallback to file cache
|
|
$cache_file = DESK_MOLONI_MODULE_UPLOAD_FOLDER . 'cache/' . md5($key) . '.cache';
|
|
if (file_exists($cache_file)) {
|
|
$cache_data = json_decode(file_get_contents($cache_file), true);
|
|
if ($cache_data && $cache_data['expires'] > time()) {
|
|
return $cache_data['data'];
|
|
}
|
|
}
|
|
|
|
return $default;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('desk_moloni_set_cached_data')) {
|
|
/**
|
|
* Set data in cache
|
|
*
|
|
* @param string $key
|
|
* @param mixed $data
|
|
* @param int $ttl
|
|
* @return bool
|
|
*/
|
|
function desk_moloni_set_cached_data($key, $data, $ttl = 3600)
|
|
{
|
|
if (!get_option('desk_moloni_enable_caching')) {
|
|
return false;
|
|
}
|
|
|
|
$CI = &get_instance();
|
|
|
|
// Try to set in Redis if available
|
|
if (get_option('desk_moloni_enable_redis')) {
|
|
$CI->load->library('redis');
|
|
return $CI->redis->setex($key, $ttl, json_encode($data));
|
|
}
|
|
|
|
// Fallback to file cache
|
|
$cache_dir = DESK_MOLONI_MODULE_UPLOAD_FOLDER . 'cache/';
|
|
if (!is_dir($cache_dir)) {
|
|
mkdir($cache_dir, 0755, true);
|
|
}
|
|
|
|
$cache_file = $cache_dir . md5($key) . '.cache';
|
|
$cache_data = [
|
|
'data' => $data,
|
|
'expires' => time() + $ttl,
|
|
'created' => time()
|
|
];
|
|
|
|
return file_put_contents($cache_file, json_encode($cache_data)) !== false;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('desk_moloni_format_date')) {
|
|
/**
|
|
* Format date for Moloni API
|
|
*
|
|
* @param string $date
|
|
* @param string $format
|
|
* @return string
|
|
*/
|
|
function desk_moloni_format_date($date, $format = 'Y-m-d')
|
|
{
|
|
if (empty($date)) {
|
|
return '';
|
|
}
|
|
|
|
$timestamp = is_numeric($date) ? $date : strtotime($date);
|
|
return date($format, $timestamp);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('desk_moloni_get_module_version')) {
|
|
/**
|
|
* Get module version
|
|
*
|
|
* @return string
|
|
*/
|
|
function desk_moloni_get_module_version()
|
|
{
|
|
return defined('DESK_MOLONI_MODULE_VERSION') ? DESK_MOLONI_MODULE_VERSION : DESK_MOLONI_VERSION;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('desk_moloni_has_permission')) {
|
|
/**
|
|
* Check if current user has permission for Desk-Moloni operations
|
|
*
|
|
* @param string $capability
|
|
* @return bool
|
|
*/
|
|
function desk_moloni_has_permission($capability = 'view')
|
|
{
|
|
return has_permission('desk_moloni', '', $capability);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('desk_moloni_admin_url')) {
|
|
/**
|
|
* Generate admin URL for Desk-Moloni module
|
|
*
|
|
* @param string $path
|
|
* @return string
|
|
*/
|
|
function desk_moloni_admin_url($path = '')
|
|
{
|
|
return admin_url('desk_moloni' . ($path ? '/' . ltrim($path, '/') : ''));
|
|
}
|
|
} |