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:
599
modules/desk_moloni/controllers/ClientPortal.php
Normal file
599
modules/desk_moloni/controllers/ClientPortal.php
Normal file
@@ -0,0 +1,599 @@
|
||||
<?php
|
||||
|
||||
defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
* Desk-Moloni Client Portal Controller
|
||||
*
|
||||
* Provides client-facing API endpoints for portal access and data management
|
||||
* Handles authentication, data access, and client-specific operations
|
||||
*
|
||||
* @package DeskMoloni
|
||||
* @subpackage Controllers
|
||||
* @version 3.0.0
|
||||
* @author Descomplicar®
|
||||
*/
|
||||
class ClientPortal extends ClientsController
|
||||
{
|
||||
private $client_id;
|
||||
|
||||
/**
|
||||
* Constructor - Initialize libraries and models
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
// Set JSON content type for API responses
|
||||
$this->output->set_content_type('application/json');
|
||||
|
||||
// Load required libraries
|
||||
$this->load->library('desk_moloni/moloni_api_client');
|
||||
$this->load->library('desk_moloni/client_sync_service');
|
||||
|
||||
// Load required models
|
||||
$this->load->model('desk_moloni/desk_moloni_config_model', 'config_model');
|
||||
$this->load->model('desk_moloni/desk_moloni_sync_log_model', 'sync_log_model');
|
||||
$this->load->model('clients_model', 'client_model');
|
||||
$this->load->model('invoices_model', 'invoice_model');
|
||||
|
||||
// Validate client session (required)
|
||||
$this->validate_client_session();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate CSRF token for POST/PUT/DELETE requests
|
||||
*/
|
||||
private function validate_csrf_token()
|
||||
{
|
||||
$method = $this->input->method();
|
||||
if (in_array($method, ['POST', 'PUT', 'DELETE'])) {
|
||||
$tokenName = $this->security->get_csrf_token_name();
|
||||
$token = $this->input->get_post($tokenName);
|
||||
$hash = $this->security->get_csrf_hash();
|
||||
if (!$token || !$hash || !hash_equals($hash, $token)) {
|
||||
$this->set_error_response('CSRF token validation failed', 403);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate client data access permissions
|
||||
*/
|
||||
private function validate_data_access($requested_client_id = null)
|
||||
{
|
||||
// If specific client ID requested, validate access
|
||||
if ($requested_client_id !== null) {
|
||||
if (!$this->client_id || $this->client_id != $requested_client_id) {
|
||||
$this->set_error_response('Access denied - You can only access your own data', 403);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate input data with sanitization and rate limiting
|
||||
*/
|
||||
private function validate_and_sanitize($data, $rules = [])
|
||||
{
|
||||
// Rate limiting check (simplified)
|
||||
$this->check_rate_limit();
|
||||
|
||||
$sanitized = [];
|
||||
|
||||
foreach ($data as $key => $value) {
|
||||
if ($value === null) {
|
||||
$sanitized[$key] = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Basic XSS protection
|
||||
$sanitized[$key] = $this->security->xss_clean($value);
|
||||
|
||||
// Apply validation rules
|
||||
if (isset($rules[$key])) {
|
||||
$rule = $rules[$key];
|
||||
|
||||
if (isset($rule['required']) && $rule['required'] && empty($sanitized[$key])) {
|
||||
throw new Exception("Field {$key} is required");
|
||||
}
|
||||
|
||||
if (!empty($sanitized[$key]) && isset($rule['type'])) {
|
||||
switch ($rule['type']) {
|
||||
case 'email':
|
||||
if (!filter_var($sanitized[$key], FILTER_VALIDATE_EMAIL)) {
|
||||
throw new Exception("Invalid email format");
|
||||
}
|
||||
break;
|
||||
case 'int':
|
||||
if (!filter_var($sanitized[$key], FILTER_VALIDATE_INT)) {
|
||||
throw new Exception("Invalid number format");
|
||||
}
|
||||
$sanitized[$key] = (int) $sanitized[$key];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($rule['max_length']) && strlen($sanitized[$key]) > $rule['max_length']) {
|
||||
throw new Exception("Input too long");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $sanitized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple rate limiting check
|
||||
*/
|
||||
private function check_rate_limit()
|
||||
{
|
||||
$client_ip = $this->input->ip_address();
|
||||
$cache_key = 'rate_limit_' . md5($client_ip . '_' . ($this->client_id ?? 'anonymous'));
|
||||
|
||||
// Allow 60 requests per minute per client
|
||||
$current_requests = $this->cache->get($cache_key) ?? 0;
|
||||
|
||||
if ($current_requests >= 60) {
|
||||
$this->set_error_response('Rate limit exceeded. Please try again later.', 429);
|
||||
exit;
|
||||
}
|
||||
|
||||
$this->cache->save($cache_key, $current_requests + 1, 60);
|
||||
}
|
||||
|
||||
// =======================================================================
|
||||
// Authentication & Session Endpoints
|
||||
// =======================================================================
|
||||
|
||||
/**
|
||||
* Client authentication endpoint
|
||||
* POST /client_portal/desk_moloni/client_login
|
||||
*/
|
||||
public function client_login()
|
||||
{
|
||||
if ($this->input->method() !== 'POST') {
|
||||
$this->set_error_response('Method not allowed', 405);
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate CSRF token
|
||||
if (!$this->validate_csrf_token()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Validate and sanitize input
|
||||
$input_data = [
|
||||
'email' => $this->input->post('email', true),
|
||||
'password' => $this->input->post('password', true)
|
||||
];
|
||||
|
||||
$validation_rules = [
|
||||
'email' => ['required' => true, 'type' => 'email', 'max_length' => 255],
|
||||
'password' => ['required' => true, 'max_length' => 255]
|
||||
];
|
||||
|
||||
$sanitized = $this->validate_and_sanitize($input_data, $validation_rules);
|
||||
|
||||
// Log login attempt
|
||||
log_message('info', 'Client login attempt for email: ' . $sanitized['email']);
|
||||
|
||||
// Authentication logic would go here
|
||||
$this->set_success_response([
|
||||
'message' => 'Client login endpoint - implementation in progress',
|
||||
'authenticated' => false
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
// Log failed login attempt with IP
|
||||
$error_context = [
|
||||
'method' => __METHOD__,
|
||||
'ip_address' => $this->input->ip_address(),
|
||||
'user_agent' => $this->input->user_agent(),
|
||||
'error' => $e->getMessage()
|
||||
];
|
||||
log_message('error', 'Client login error: ' . json_encode($error_context));
|
||||
|
||||
$this->set_error_response('Authentication failed', 401);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Client logout endpoint
|
||||
* POST /client_portal/desk_moloni/client_logout
|
||||
*/
|
||||
public function client_logout()
|
||||
{
|
||||
if ($this->input->method() !== 'POST') {
|
||||
$this->set_error_response('Method not allowed', 405);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->set_success_response(['message' => 'Client logout endpoint - implementation in progress']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Session validation endpoint
|
||||
* GET /client_portal/desk_moloni/client_session_check
|
||||
*/
|
||||
public function client_session_check()
|
||||
{
|
||||
if ($this->input->method() !== 'GET') {
|
||||
$this->set_error_response('Method not allowed', 405);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->set_success_response(['message' => 'Session check endpoint - implementation in progress']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Password reset endpoint
|
||||
* POST /client_portal/desk_moloni/client_password_reset
|
||||
*/
|
||||
public function client_password_reset()
|
||||
{
|
||||
if ($this->input->method() !== 'POST') {
|
||||
$this->set_error_response('Method not allowed', 405);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->set_success_response(['message' => 'Password reset endpoint - implementation in progress']);
|
||||
}
|
||||
|
||||
// =======================================================================
|
||||
// Dashboard & Overview Endpoints
|
||||
// =======================================================================
|
||||
|
||||
/**
|
||||
* Client dashboard data
|
||||
* GET /client_portal/desk_moloni/dashboard
|
||||
*/
|
||||
public function dashboard()
|
||||
{
|
||||
if ($this->input->method() !== 'GET') {
|
||||
$this->set_error_response('Method not allowed', 405);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->set_success_response(['message' => 'Dashboard endpoint - implementation in progress']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Current sync status for client
|
||||
* GET /client_portal/desk_moloni/sync_status
|
||||
*/
|
||||
public function sync_status()
|
||||
{
|
||||
if ($this->input->method() !== 'GET') {
|
||||
$this->set_error_response('Method not allowed', 405);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->set_success_response(['message' => 'Sync status endpoint - implementation in progress']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recent sync activity log
|
||||
* GET /client_portal/desk_moloni/recent_activity
|
||||
*/
|
||||
public function recent_activity()
|
||||
{
|
||||
if ($this->input->method() !== 'GET') {
|
||||
$this->set_error_response('Method not allowed', 405);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->set_success_response(['message' => 'Recent activity endpoint - implementation in progress']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Summary of sync errors
|
||||
* GET /client_portal/desk_moloni/error_summary
|
||||
*/
|
||||
public function error_summary()
|
||||
{
|
||||
if ($this->input->method() !== 'GET') {
|
||||
$this->set_error_response('Method not allowed', 405);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->set_success_response(['message' => 'Error summary endpoint - implementation in progress']);
|
||||
}
|
||||
|
||||
// =======================================================================
|
||||
// Invoice Management Endpoints
|
||||
// =======================================================================
|
||||
|
||||
/**
|
||||
* Get client invoices list
|
||||
* GET /client_portal/desk_moloni/get_invoices
|
||||
*/
|
||||
public function get_invoices()
|
||||
{
|
||||
if ($this->input->method() !== 'GET') {
|
||||
$this->set_error_response('Method not allowed', 405);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->set_success_response(['message' => 'Get invoices endpoint - implementation in progress']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get specific invoice details
|
||||
* GET /client_portal/desk_moloni/get_invoice_details
|
||||
*/
|
||||
public function get_invoice_details()
|
||||
{
|
||||
if ($this->input->method() !== 'GET') {
|
||||
$this->set_error_response('Method not allowed', 405);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->set_success_response(['message' => 'Invoice details endpoint - implementation in progress']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Download invoice PDF
|
||||
* GET /client_portal/desk_moloni/download_invoice
|
||||
*/
|
||||
public function download_invoice()
|
||||
{
|
||||
if ($this->input->method() !== 'GET') {
|
||||
$this->set_error_response('Method not allowed', 405);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->set_success_response(['message' => 'Download invoice endpoint - implementation in progress']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Manual invoice sync trigger
|
||||
* POST /client_portal/desk_moloni/sync_invoice
|
||||
*/
|
||||
public function sync_invoice()
|
||||
{
|
||||
if ($this->input->method() !== 'POST') {
|
||||
$this->set_error_response('Method not allowed', 405);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->set_success_response(['message' => 'Sync invoice endpoint - implementation in progress']);
|
||||
}
|
||||
|
||||
// =======================================================================
|
||||
// Client Data Management Endpoints
|
||||
// =======================================================================
|
||||
|
||||
/**
|
||||
* Get client profile data
|
||||
* GET /client_portal/desk_moloni/get_client_data
|
||||
*/
|
||||
public function get_client_data()
|
||||
{
|
||||
if ($this->input->method() !== 'GET') {
|
||||
$this->set_error_response('Method not allowed', 405);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->set_success_response(['message' => 'Get client data endpoint - implementation in progress']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update client information
|
||||
* PUT /client_portal/desk_moloni/update_client_data
|
||||
*/
|
||||
public function update_client_data()
|
||||
{
|
||||
if ($this->input->method() !== 'PUT') {
|
||||
$this->set_error_response('Method not allowed', 405);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->set_success_response(['message' => 'Update client data endpoint - implementation in progress']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sync preferences
|
||||
* GET /client_portal/desk_moloni/get_sync_preferences
|
||||
*/
|
||||
public function get_sync_preferences()
|
||||
{
|
||||
if ($this->input->method() !== 'GET') {
|
||||
$this->set_error_response('Method not allowed', 405);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->set_success_response(['message' => 'Get sync preferences endpoint - implementation in progress']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update sync preferences
|
||||
* PUT /client_portal/desk_moloni/update_sync_preferences
|
||||
*/
|
||||
public function update_sync_preferences()
|
||||
{
|
||||
if ($this->input->method() !== 'PUT') {
|
||||
$this->set_error_response('Method not allowed', 405);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->set_success_response(['message' => 'Update sync preferences endpoint - implementation in progress']);
|
||||
}
|
||||
|
||||
// =======================================================================
|
||||
// Reports & Analytics Endpoints
|
||||
// =======================================================================
|
||||
|
||||
/**
|
||||
* Get synchronization report
|
||||
* GET /client_portal/desk_moloni/get_sync_report
|
||||
*/
|
||||
public function get_sync_report()
|
||||
{
|
||||
if ($this->input->method() !== 'GET') {
|
||||
$this->set_error_response('Method not allowed', 405);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->set_success_response(['message' => 'Sync report endpoint - implementation in progress']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get revenue analytics
|
||||
* GET /client_portal/desk_moloni/get_revenue_report
|
||||
*/
|
||||
public function get_revenue_report()
|
||||
{
|
||||
if ($this->input->method() !== 'GET') {
|
||||
$this->set_error_response('Method not allowed', 405);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->set_success_response(['message' => 'Revenue report endpoint - implementation in progress']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export client data
|
||||
* GET /client_portal/desk_moloni/export_data
|
||||
*/
|
||||
public function export_data()
|
||||
{
|
||||
if ($this->input->method() !== 'GET') {
|
||||
$this->set_error_response('Method not allowed', 405);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->set_success_response(['message' => 'Export data endpoint - implementation in progress']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get invoice statistics
|
||||
* GET /client_portal/desk_moloni/get_invoice_stats
|
||||
*/
|
||||
public function get_invoice_stats()
|
||||
{
|
||||
if ($this->input->method() !== 'GET') {
|
||||
$this->set_error_response('Method not allowed', 405);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->set_success_response(['message' => 'Invoice stats endpoint - implementation in progress']);
|
||||
}
|
||||
|
||||
// =======================================================================
|
||||
// Support & Help Endpoints
|
||||
// =======================================================================
|
||||
|
||||
/**
|
||||
* Submit support request
|
||||
* POST /client_portal/desk_moloni/submit_support_ticket
|
||||
*/
|
||||
public function submit_support_ticket()
|
||||
{
|
||||
if ($this->input->method() !== 'POST') {
|
||||
$this->set_error_response('Method not allowed', 405);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->set_success_response(['message' => 'Submit support ticket endpoint - implementation in progress']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get client support tickets
|
||||
* GET /client_portal/desk_moloni/get_support_tickets
|
||||
*/
|
||||
public function get_support_tickets()
|
||||
{
|
||||
if ($this->input->method() !== 'GET') {
|
||||
$this->set_error_response('Method not allowed', 405);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->set_success_response(['message' => 'Get support tickets endpoint - implementation in progress']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get help documentation
|
||||
* GET /client_portal/desk_moloni/get_help_resources
|
||||
*/
|
||||
public function get_help_resources()
|
||||
{
|
||||
if ($this->input->method() !== 'GET') {
|
||||
$this->set_error_response('Method not allowed', 405);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->set_success_response(['message' => 'Get help resources endpoint - implementation in progress']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Contact support form
|
||||
* POST /client_portal/desk_moloni/contact_support
|
||||
*/
|
||||
public function contact_support()
|
||||
{
|
||||
if ($this->input->method() !== 'POST') {
|
||||
$this->set_error_response('Method not allowed', 405);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->set_success_response(['message' => 'Contact support endpoint - implementation in progress']);
|
||||
}
|
||||
|
||||
// =======================================================================
|
||||
// Helper Methods
|
||||
// =======================================================================
|
||||
|
||||
/**
|
||||
* Validate client session
|
||||
*/
|
||||
private function validate_client_session()
|
||||
{
|
||||
// Require authenticated client session
|
||||
$this->client_id = $this->session->userdata('client_user_id') ?? $this->session->userdata('client_id') ?? null;
|
||||
if (!$this->client_id) {
|
||||
$this->set_error_response('Authentication required', 401);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set success response format
|
||||
*
|
||||
* @param array $data Response data
|
||||
*/
|
||||
private function set_success_response($data)
|
||||
{
|
||||
$this->output
|
||||
->set_status_header(200)
|
||||
->set_output(json_encode([
|
||||
'success' => true,
|
||||
'data' => $data,
|
||||
'client_id' => $this->client_id
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set error response format
|
||||
*
|
||||
* @param string $message Error message
|
||||
* @param int $status_code HTTP status code
|
||||
*/
|
||||
private function set_error_response($message, $status_code = 400)
|
||||
{
|
||||
$this->output
|
||||
->set_status_header($status_code)
|
||||
->set_output(json_encode([
|
||||
'success' => false,
|
||||
'error' => [
|
||||
'message' => $message,
|
||||
'code' => $status_code,
|
||||
'timestamp' => date('Y-m-d H:i:s')
|
||||
]
|
||||
]));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user