diff --git a/classes/redis.php b/classes/redis.php index f72cd4fc..2140ed1b 100644 --- a/classes/redis.php +++ b/classes/redis.php @@ -87,10 +87,15 @@ class redis implements redis_i public function cache_department_name(int $department_id, string $department_name): self { // Cache the department name (If it is not empty or null) - if (!empty($department_name)) { + if (empty($department_name)) { $department_name = 'IS_EMPTY_OR_NULL'; + return $this; } - $this->set('department_name_' . $department_id, $department_name); + // Set the department's name in the cache + if ($this->get_department_name($department_id) === null) { + $this->set('department_' . $department_id, json_encode(['name' => $department_name] + )); + }; return $this; } @@ -100,7 +105,12 @@ class redis implements redis_i public function get_department_name(int $department_id): string|null { // Get the department name - return $this->get('department_name_' . $department_id); + $tmp_department = $this->get('department_' . $department_id); + if ($tmp_department === 'IS_EMPTY_OR_NULL' || $tmp_department === null) { + return null; + } + // Return the department name + return json_decode($tmp_department, true)->name; } /** @@ -109,7 +119,7 @@ class redis implements redis_i public function clear_department_name(int $department_id): self { // Clear the department name - $this->delete('department_name_' . $department_id); + $this->delete('department_' . $department_id); return $this; } diff --git a/classes/slack.php b/classes/slack.php index 95d8d78d..e3c83ea0 100644 --- a/classes/slack.php +++ b/classes/slack.php @@ -129,4 +129,11 @@ class slack implements notification_i . "Notes: $notes\n" . "Status: $status"; } + + public function send_message(string $string): void + { + global $SLACK_DEFAULT_WEBHOOK; + // Send the message to the slack webhook + self::add_log(self::send_webhook_message($string, $SLACK_DEFAULT_WEBHOOK)); + } } \ No newline at end of file diff --git a/cli.php b/cli.php index 92b58a94..0213c9d9 100644 --- a/cli.php +++ b/cli.php @@ -1,9 +1,27 @@ 'error', + 'message' => 'Invalid token' + ]); + exit; + } +} + // If the first argument is 'run', switch to the second argument if ($args[1] === 'run') { @@ -45,4 +63,6 @@ if ($args[1] === 'run') { echo "Invalid script name"; break; } +} else { + echo "Invalid action"; } \ No newline at end of file diff --git a/config.example.php b/config.example.php index cb6f0939..8d7684e5 100644 --- a/config.example.php +++ b/config.example.php @@ -1,4 +1,4 @@ - '', // IP address of the database server e.g. 127.0.0.1 'user' => '', // Username of the database server e.g. root @@ -29,6 +29,7 @@ $MINIO = [ 'access_key' => '', // Minio access 'secret_key' => '' // Minio secret key ]; +$SLACK_DEFAULT_WEBHOOK = ''; // Default Slack webhook URL e.g. https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX $REDIS_CONFIG = [ 'host' => '', // Redis host (IP address) 'database' => 0, // Redis database number (0-15) diff --git a/cron/ClearAllUsersEconomicCustomerDetails.php b/cron/ClearAllUsersEconomicCustomerDetails.php new file mode 100644 index 00000000..d4b98ab3 --- /dev/null +++ b/cron/ClearAllUsersEconomicCustomerDetails.php @@ -0,0 +1,22 @@ +clearAllUsersEconomicCustomerDetailsFromCache(); +$end = microtime(true); +//$slack = new \classes\slack(); +//$slack->send_message("The booking sync script has finished. It took " . round($end - $start, 2) . " seconds to run."); \ No newline at end of file diff --git a/cron/ClearAllUsersEconomicCustomerDiscounts.php b/cron/ClearAllUsersEconomicCustomerDiscounts.php new file mode 100644 index 00000000..f5b7cb0d --- /dev/null +++ b/cron/ClearAllUsersEconomicCustomerDiscounts.php @@ -0,0 +1,22 @@ +clearAllUsersEconomicCustomerDiscountsFromCache(); +$end = microtime(true); +//$slack = new \classes\slack(); +//$slack->send_message("The booking sync script has finished. It took " . round($end - $start, 2) . " seconds to run."); \ No newline at end of file diff --git a/cron/Cron.php b/cron/Cron.php index 4c556dc6..a6d77669 100644 --- a/cron/Cron.php +++ b/cron/Cron.php @@ -3,6 +3,7 @@ use objects\bookings_o; use objects\logs_o; +use objects\users_o; if (!defined('WD')) { exit; @@ -14,6 +15,8 @@ function warn($message): void echo "\n\033[33m$message\033[0m\n"; } +$response_cron = []; + // Define the cron tasks $cron_tasks = [ 'CheckUnfulfilledBookings' => [ @@ -35,6 +38,18 @@ $cron_tasks = [ 'next_run' => 0, 'function' => 'syncLogsToDatabase', ], + 'SyncUserEconomicCustomerDiscounts' => [ + 'interval' => 600, // 10 minutes + 'last_run' => 0, + 'next_run' => 0, + 'function' => 'SyncUserEconomicCustomerDiscounts', + ], + 'SyncUserEconomicCustomerDetails' => [ + 'interval' => 43200, // 12 hours + 'last_run' => 0, + 'next_run' => 0, + 'function' => 'SyncUserEconomicCustomerDetails', + ], ]; function checkUnfulfilledBookings(): void @@ -46,7 +61,7 @@ function checkUnfulfilledBookings(): void function syncBookings(): void { $bookings_o = new bookings_o(); - $bookings_o->syncBookings(); + $response_cron[] = $bookings_o->syncBookings(); } function syncLogsToDatabase(): void @@ -55,6 +70,19 @@ function syncLogsToDatabase(): void $logs_o->syncLogsToDatabase(); } +function SyncUserEconomicCustomerDiscounts(): void +{ + $users_o = new users_o(); + $users_o->clearAllUsersEconomicCustomerDiscountsFromCache(); +} + +function SyncUserEconomicCustomerDetails(): void +{ + $users_o = new users_o(); + $users_o->clearAllUsersEconomicCustomerDetailsFromCache(); + $users_o->syncAllUsersEconomicCustomerDetails(); +} + foreach ( $cron_tasks as $task => $data ) { $lastRun = redis->get_last_crond_run($task) === null ? 0 : redis->get_last_crond_run($task); $nextRun = $lastRun + $data['interval']; @@ -64,6 +92,6 @@ foreach ( $cron_tasks as $task => $data ) { $data['function'](); redis->set_last_crond_run($task, time()); } else { - warn('Task ' . $task . ' is not due to run yet, next run is at ' . date('Y-m-d H:i:s', $nextRun) . ' (' . ($nextRun - time()) . ' seconds)'); + $response_cron[] = $task . ' is not due to run yet, next run is at ' . date('Y-m-d H:i:s', $nextRun) . ' (' . ($nextRun - time()) . ' seconds)'; } } \ No newline at end of file diff --git a/cron/SyncBookings.php b/cron/SyncBookings.php index fdd2b93a..2c6d98f3 100644 --- a/cron/SyncBookings.php +++ b/cron/SyncBookings.php @@ -13,7 +13,10 @@ use objects\bookings_o; if (!defined('WD')) { exit; } - +$start = microtime(true); // Sync the bookings $bookings_o = new bookings_o(); -$bookings_o->syncBookings(); \ No newline at end of file +$bookings_o->syncBookings(); +$end = microtime(true); +//$slack = new \classes\slack(); +//$slack->send_message("The booking sync script has finished. It took " . round($end - $start, 2) . " seconds to run."); \ No newline at end of file diff --git a/index.php b/index.php index c2a451d3..63eba215 100644 --- a/index.php +++ b/index.php @@ -96,7 +96,7 @@ foreach ( glob(WD . '/objects/*.php') as $object ) { } // If the program was called from the command line, run the cli script -if (php_sapi_name() === 'cli') { +if (php_sapi_name() === 'cli' || isset($_GET['internalCronCall'])) { require_once 'cli.php'; exit; } diff --git a/objects/logs_o.php b/objects/logs_o.php index 1b2b78fc..1df90b20 100644 --- a/objects/logs_o.php +++ b/objects/logs_o.php @@ -58,10 +58,12 @@ class logs_o extends db echo "\r\033[33m" . str_repeat('=', $percentage) . str_repeat(' ', 100 - $percentage) . "\033[0m " . number_format($percentage, 2) . '%'; } // Show the percentage, completed and total - echo "\r\033[33m" . number_format($percentage, 2) . '% (' . $current . '/' . $total . ")\033[0m"; - // If the current is equal to the total, add a new line - if ($current === $total) { - echo "\n"; + if ($display_progress) { + echo "\r\033[33m" . number_format($percentage, 2) . '% (' . $current . '/' . $total . ")\033[0m"; + // If the current is equal to the total, add a new line + if ($current === $total) { + echo "\n"; + } } } diff --git a/objects/users_o.php b/objects/users_o.php index a0c51307..5e077602 100644 --- a/objects/users_o.php +++ b/objects/users_o.php @@ -259,11 +259,9 @@ class users_o extends db if ($customer_number === 0) { return null; } - $tmp_user = redis->get_economic_customer_name((int)$customer_number); - if ($tmp_user !== null) { - return $tmp_user; - } + // Create a temporary user object $tmp_user = new users_o(); + $tmp_user->getUserByCustomerNumber($customer_number); // Get the customer name (Check cache first) $cached = $tmp_user->getCached('economic_customer'); if (!$cached) { @@ -578,23 +576,6 @@ class users_o extends db $this->keys->setUser($this->id)->deleteValue('open_invoice_draft'); } - /** - * Set a custom price (DISCOUNT) for the user - * @param int $user_id - * @param int $object_id The ID of the object - * @param int $discount_percentage The discount percentage - * @param bool $is_category If the object is a category - * @return void - */ - public function setCustomPrice(int $user_id, int|string $object_id, int $discount_percentage, bool $is_category = false): void - { - $this->id = $user_id; - // Get the user object properties - $this->getObjectProperties(); - // Set the custom price (key = 'custom_price') - $this->price_overrides->setUser($this->id)->setPrice($is_category, $object_id, $discount_percentage); - } - /** * Get the custom price (DISCOUNT) for the user * @param int $object_id The ID of the object @@ -726,7 +707,7 @@ class users_o extends db // Check if the name is cached $cached_name = $this->getCached('economic_customer', $id); if (!$cached_name) { - echo 'No cached name found for user with ID: ' . $id; + // No cached name, get the name from the external source // Get the name from the external source $tmp_user = new users_o(); $tmp_user->getUserById($id); @@ -741,6 +722,38 @@ class users_o extends db return $cached_name->name ?? null; } + public function hasPassword(): bool + { + return $this->password->value() !== null; + } + + public function getPassword(): string|null + { + return $this->password->value(); + } + + public function clearAllUsersEconomicCustomerDiscountsFromCache(): void + { + // Get all the cached results matching the pattern 'users_*_economic_customer_discount_percentage' + $cached_results = redis->get_keys('users_*_economic_customer_discount_percentage'); + // Loop through the cached results + foreach ( $cached_results as $key ) { + // Clear the cached discount percentage + redis->delete($key); + } + } + + public function clearAllUsersEconomicCustomerDetailsFromCache(): void + { + // Get all the cached results matching the pattern 'users_*_economic_customer' + $cached_results = redis->get_keys('users_*_economic_customer'); + // Loop through the cached results + foreach ( $cached_results as $key ) { + // Clear the cached economic customer details + redis->delete($key); + } + } + public function getEconomicCustomerDiscountPercentage(): int { // Check if the discount percentage is cached @@ -756,13 +769,35 @@ class users_o extends db return $discount_percentage; } - public function hasPassword(): bool + /** + * Set a custom price (DISCOUNT) for the user + * @param int $user_id + * @param int $object_id The ID of the object + * @param int $discount_percentage The discount percentage + * @param bool $is_category If the object is a category + * @return void + */ + public function setCustomPrice(int $user_id, int|string $object_id, int $discount_percentage, bool $is_category = false): void { - return $this->password->value() !== null; + $this->id = $user_id; + // Get the user object properties + $this->getObjectProperties(); + // Set the custom price (key = 'custom_price') + $this->price_overrides->setUser($this->id)->setPrice($is_category, $object_id, $discount_percentage); } - public function getPassword(): string|null + public function syncAllUsersEconomicCustomerDetails(): void { - return $this->password->value(); + // Get all users + $users = $this->getFields(['id', 'customer_number']); + // Loop through the users + foreach ( $users as $user ) { + // Check if the customer number is set (Or if it is 0) + if ($user['customer_number'] === 0) { + continue; + } + // Get the customer data from the external source + $this->getCustomerEcocomicData($user['customer_number']); + } } } \ No newline at end of file diff --git a/routes/cronRoute.php b/routes/cronRoute.php new file mode 100644 index 00000000..86925d7b --- /dev/null +++ b/routes/cronRoute.php @@ -0,0 +1,48 @@ +post('/superuser/cron', function () { + // Get the post data + global $response; + // Make sure the user has the SUPERUSER_RUN_CRON permission + if (!$this->requirePermission('SUPERUSER_RUN_CRON')) { + $response->error('Permission denied', 403); + } + // Get the user object + $user = (new authentication())->get_user(); + // Get the post data + $data = json_decode(file_get_contents('php://input'), true); + // Check if a specific cron job is requested + if (isset($data['job'])) { + // Check if the cron job exists + if (!file_exists(WD . '/cron/' . $data['job'] . '.php')) { + $response->error('Cron job not found', 404); + } + // Include the cron job + require_once WD . '/cron/' . $data['job'] . '.php'; + // Log the incident + (new logs_o())->add('cron', 'global', 1, $user->id, 'CRON_JOB_RUN', 'Ran cron job: ' . $data['job']); + $response->success('Cron job ran successfully'); + } + // If no specific cron job is requested, run all cron jobs (through the cron.php file) + require_once WD . '/cron/Cron.php'; + // Log the incident + (new logs_o())->add('cron', 'global', 1, $user->id, 'CRON_RUN', 'Ran all cron jobs'); + $response->success([ + 'message' => 'All cron jobs ran successfully', + 'data' => $response_cron ?? [] + ]); + }); + } +} \ No newline at end of file diff --git a/traits/db_object_t.php b/traits/db_object_t.php index d84a5348..05243adb 100644 --- a/traits/db_object_t.php +++ b/traits/db_object_t.php @@ -375,4 +375,17 @@ trait db_object_t // Delete the cached data redis->delete($this->table . '_' . $objectId . '_' . $key); } + + /** + * Get all objects fields + * @param array $fields The fields to get from the objects (Eg. ['name', 'description']) + * @return array The list of fields from the objects (Eg. [['name' => 'John', 'description' => 'Doe'], ['name' => 'Jane', 'description' => 'Doe']]) + */ + public function getFields(array $fields): array + { + global $db; + $sql = "SELECT " . implode(', ', $fields) . " FROM $this->table"; + $result = $db->query($sql); + return $db->fetch_all($result); + } } \ No newline at end of file