info('Job started.'); $lockFile = '/tmp/domain-lifecycle-manager.lock'; if (file_exists($lockFile)) { $lockTime = filemtime($lockFile); // Check if the lock file is stale (e.g., older than 10 minutes) if (time() - $lockTime > 600) { // Remove stale lock file unlink($lockFile); } else { $log->warning('Script is already running. Exiting.'); exit(0); } } // Create the lock file touch($lockFile); try { // Connect to the database $dsn = "{$c['db_type']}:host={$c['db_host']};dbname={$c['db_database']};port={$c['db_port']}"; $dbh = new PDO($dsn, $c['db_username'], $c['db_password']); $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // Instantiate the DomainLifecycleManager $manager = new DomainLifecycleManager($dbh, $log, $c); $manager->processLifecycle(); $log->info('Job finished successfully.'); } catch (PDOException $e) { $log->error('DB Connection failed: ' . $e->getMessage()); exit(1); } catch (Exception $e) { $log->error('An unexpected error occurred: ' . $e->getMessage()); exit(1); } finally { // Remove the lock file unlink($lockFile); } class DomainLifecycleManager { private $dbh; private $log; private $config; public function __construct($dbh, $log, $config) { $this->dbh = $dbh; $this->log = $log; $this->config = $config; } public function processLifecycle() { // Process lifecycle phases if ($this->config['enableAutoRenew']) { $this->processAutoRenewal(); } else { $this->processGracePeriod(); } $this->cleanUpPeriods(); if ($this->config['enableRedemptionPeriod']) { $this->processPendingDelete(); $this->processPendingRestore(); } if ($this->config['enablePendingDelete']) { $this->processDomainDeletion(); } } // ======================== // 1. Auto-Renewal Phase // ======================== private function processAutoRenewal() { $this->log->info('Starting Auto-Renewal Phase.'); $minimum_data = $this->config['minimum_data']; // Fetch domains eligible for auto-renewal $sth = $this->dbh->prepare(" SELECT id, name, tldid, exdate, clid FROM domain WHERE CURRENT_TIMESTAMP > exdate AND rgpstatus IS NULL "); $sth->execute(); while ($row = $sth->fetch(PDO::FETCH_ASSOC)) { $domain_id = $row['id']; $name = $row['name']; $tldid = $row['tldid']; $exdate = $row['exdate']; $clid = $row['clid']; // Check if domain can be set to auto-renew period if ($this->canSetAutoRenewPeriod($domain_id)) { // Process auto-renewal $this->handleAutoRenewal($domain_id, $name, $tldid, $exdate, $clid); } $this->log->info("$name (ID $domain_id) processed for auto-renewal."); } $this->log->info('Completed Auto-Renewal Phase.'); } private function canSetAutoRenewPeriod($domain_id) { $sth_status = $this->dbh->prepare("SELECT status FROM domain_status WHERE domain_id = ?"); $sth_status->execute([$domain_id]); while ($status_row = $sth_status->fetch(PDO::FETCH_ASSOC)) { $status = $status_row['status']; if (preg_match("/(serverUpdateProhibited|serverDeleteProhibited)$/", $status) || preg_match("/^pending/", $status)) { return false; } } return true; } private function handleAutoRenewal($domain_id, $name, $tldid, $exdate, $clid) { // Get registrar balance and credit limit $sthRegistrar = $this->dbh->prepare(" SELECT accountBalance, creditLimit FROM registrar WHERE id = ? LIMIT 1 "); $sthRegistrar->execute([$clid]); list($registrar_balance, $creditLimit) = $sthRegistrar->fetch(PDO::FETCH_NUM); // Get domain price $returnValue = getDomainPrice($this->dbh, $name, $tldid, 12, 'renew', $clid); $price = $returnValue['price']; if (($registrar_balance + $creditLimit) > $price) { // Proceed with auto-renewal $this->renewDomain($domain_id, $name, $exdate, $clid, $price); } else { // Insufficient funds, move to redemption period $this->moveToRedemptionPeriod($domain_id, $exdate); } } private function renewDomain($domain_id, $name, $exdate, $clid, $price) { // Update domain and registrar records $sth = $this->dbh->prepare("UPDATE domain SET rgpstatus = 'autoRenewPeriod', exdate = DATE_ADD(exdate, INTERVAL 12 MONTH), autoRenewPeriod = '12', renewedDate = exdate WHERE id = ?"); $sth->execute([$domain_id]); $sth = $this->dbh->prepare("UPDATE registrar SET accountBalance = (accountBalance - ?) WHERE id = ?"); $sth->execute([$price, $clid]); $sth = $this->dbh->prepare("INSERT INTO payment_history (registrar_id, date, description, amount) VALUES(?, CURRENT_TIMESTAMP, ?, ?)"); $description = "autoRenew domain $name for period 12 MONTH"; $sth->execute([$clid, $description, -$price]); list($to) = $this->dbh->query("SELECT exdate FROM domain WHERE id = '$domain_id' LIMIT 1")->fetch(PDO::FETCH_NUM); $sthStatement = $this->dbh->prepare("INSERT INTO statement (registrar_id, date, command, domain_name, length_in_months, fromS, toS, amount) VALUES(?, CURRENT_TIMESTAMP, ?, ?, ?, ?, ?, ?)"); $sthStatement->execute([$clid, 'autoRenew', $name, '12', $exdate, $to, $price]); // Update statistics $this->updateStatistics('renewed_domains'); } private function moveToRedemptionPeriod($domain_id, $exdate) { $this->dbh->beginTransaction(); try { $this->dbh->prepare("DELETE FROM domain_status WHERE domain_id = ?")->execute([$domain_id]); $this->dbh->prepare("UPDATE domain SET rgpstatus = 'redemptionPeriod', delTime = ? WHERE id = ?")->execute([$exdate, $domain_id]); $this->dbh->prepare("INSERT INTO domain_status (domain_id, status) VALUES(?, 'pendingDelete')")->execute([$domain_id]); $this->dbh->commit(); } catch (Exception $e) { $this->dbh->rollBack(); $this->log->error("Failed to move domain ID $domain_id to redemption period: " . $e->getMessage()); } } // ======================== // 2. Grace Period Phase // ======================== private function processGracePeriod() { $this->log->info('Starting Grace Period Phase.'); $gracePeriodDays = $this->config['gracePeriodDays']; // Fetch domains eligible for grace period $sth = $this->dbh->prepare(" SELECT id, name, exdate FROM domain WHERE CURRENT_TIMESTAMP > DATE_ADD(exdate, INTERVAL ? DAY) AND rgpstatus IS NULL "); $sth->execute([$gracePeriodDays]); while ($row = $sth->fetch(PDO::FETCH_ASSOC)) { $domain_id = $row['id']; $name = $row['name']; $exdate = $row['exdate']; // Check if domain can be moved to redemption period if ($this->canSetGracePeriod($domain_id)) { $this->moveToRedemptionPeriod($domain_id, $exdate); $this->log->info("$name (ID $domain_id) moved to redemption period."); } } $this->log->info('Completed Grace Period Phase.'); } private function canSetGracePeriod($domain_id) { $sth_status = $this->dbh->prepare("SELECT status FROM domain_status WHERE domain_id = ?"); $sth_status->execute([$domain_id]); while ($status_row = $sth_status->fetch(PDO::FETCH_ASSOC)) { $status = $status_row['status']; if (preg_match("/(serverUpdateProhibited|serverDeleteProhibited)$/", $status) || preg_match("/^pending/", $status)) { return false; } } return true; } // ======================== // 3. Clean-Up Periods // ======================== private function cleanUpPeriods() { $this->log->info('Starting Clean-Up Periods Phase.'); $periods = [ 'autoRenewPeriod' => $this->config['autoRenewPeriodDays'], 'addPeriod' => $this->config['addPeriodDays'], 'renewPeriod' => $this->config['renewPeriodDays'], 'transferPeriod' => $this->config['transferPeriodDays'], ]; foreach ($periods as $periodName => $days) { $this->cleanupPeriod($periodName, $days); } $this->log->info('Completed Clean-Up Periods Phase.'); } private function cleanupPeriod($periodName, $days) { $column = $periodName === 'addPeriod' ? 'crdate' : ($periodName === 'renewPeriod' ? 'renewedDate' : ($periodName === 'transferPeriod' ? 'trdate' : 'exdate')); $sth = $this->dbh->prepare("UPDATE domain SET rgpstatus = NULL WHERE CURRENT_TIMESTAMP > DATE_ADD($column, INTERVAL ? DAY) AND rgpstatus = ?"); $sth->execute([$days, $periodName]); } // ======================== // 4. Pending Delete Phase // ======================== private function processPendingDelete() { $this->log->info('Starting Pending Delete Phase.'); $redemptionPeriodDays = $this->config['redemptionPeriodDays']; // Fetch domains eligible for pending delete $sth = $this->dbh->prepare(" SELECT id, name, exdate FROM domain WHERE CURRENT_TIMESTAMP > DATE_ADD(delTime, INTERVAL ? DAY) AND rgpstatus = 'redemptionPeriod' "); $sth->execute([$redemptionPeriodDays]); while ($row = $sth->fetch(PDO::FETCH_ASSOC)) { $domain_id = $row['id']; $name = $row['name']; if ($this->canSetPendingDelete($domain_id)) { $sthUpdate = $this->dbh->prepare("UPDATE domain SET rgpstatus = 'pendingDelete' WHERE id = ?"); $sthUpdate->execute([$domain_id]); $this->log->info("$name (ID $domain_id) moved to pending delete."); } } $this->log->info('Completed Pending Delete Phase.'); } private function canSetPendingDelete($domain_id) { $sth_status = $this->dbh->prepare("SELECT status FROM domain_status WHERE domain_id = ?"); $sth_status->execute([$domain_id]); while ($status_row = $sth_status->fetch(PDO::FETCH_ASSOC)) { $status = $status_row['status']; if (preg_match("/(serverUpdateProhibited|serverDeleteProhibited)$/", $status)) { return false; } } return true; } // ======================== // 5. Pending Restore Phase // ======================== private function processPendingRestore() { $this->log->info('Starting Pending Restore Phase.'); // Fetch domains in pendingRestore status that have exceeded the restore period $sth = $this->dbh->prepare(" SELECT id, name FROM domain WHERE rgpstatus = 'pendingRestore' AND CURRENT_TIMESTAMP > DATE_ADD(resTime, INTERVAL 7 DAY) "); $sth->execute(); while ($row = $sth->fetch(PDO::FETCH_ASSOC)) { $domain_id = $row['id']; $name = $row['name']; $sthUpdate = $this->dbh->prepare("UPDATE domain SET rgpstatus = 'redemptionPeriod' WHERE id = ?"); $sthUpdate->execute([$domain_id]); $this->log->info("$name (ID $domain_id) returned to redemption period from pending restore."); } $this->log->info('Completed Pending Restore Phase.'); } // ======================== // 6. Domain Deletion Phase // ======================== private function processDomainDeletion() { $this->log->info('Starting Domain Deletion Phase.'); $totalPendingDays = $this->config['redemptionPeriodDays'] + $this->config['pendingDeletePeriodDays']; // Fetch domains eligible for deletion $sth = $this->dbh->prepare(" SELECT id, name, delTime FROM domain WHERE CURRENT_TIMESTAMP > DATE_ADD(delTime, INTERVAL ? DAY) AND rgpstatus = 'pendingDelete' "); $sth->execute([$totalPendingDays]); while ($row = $sth->fetch(PDO::FETCH_ASSOC)) { $domain_id = $row['id']; $name = $row['name']; $delTime = $row['delTime']; if ($this->shouldDeleteDomainNow($delTime)) { if ($this->canDeleteDomain($domain_id)) { $this->deleteDomain($domain_id, $name); $this->log->info("$name (ID $domain_id) has been deleted."); } } } $this->log->info('Completed Domain Deletion Phase.'); } private function shouldDeleteDomainNow($delTime) { $currentTime = new DateTime(); $deletionTime = new DateTime($delTime); $totalPendingDays = $this->config['redemptionPeriodDays'] + $this->config['pendingDeletePeriodDays']; $deletionTime->modify("+$totalPendingDays days"); if ($this->config['dropStrategy'] === 'fixed') { $dropTime = $this->config['dropTime']; $deletionTime->setTime((int)substr($dropTime, 0, 2), (int)substr($dropTime, 3, 2), (int)substr($dropTime, 6, 2)); return $currentTime >= $deletionTime; } elseif ($this->config['dropStrategy'] === 'random') { $randomDays = rand(0, $this->config['pendingDeletePeriodDays']); $deletionTime->modify("+$randomDays days"); return $currentTime >= $deletionTime; } else { return $currentTime >= $deletionTime; } } private function canDeleteDomain($domain_id) { $sth_status = $this->dbh->prepare("SELECT status FROM domain_status WHERE domain_id = ?"); $sth_status->execute([$domain_id]); $delete_domain = false; while ($status_row = $sth_status->fetch(PDO::FETCH_ASSOC)) { $status = $status_row['status']; if ($status == 'pendingDelete') { $delete_domain = true; } if (preg_match("/(serverUpdateProhibited|serverDeleteProhibited)$/", $status)) { return false; } } return $delete_domain; } private function deleteDomain($domain_id, $name) { $minimum_data = $this->config['minimum_data']; $this->dbh->beginTransaction(); try { // Delete associated hosts $sth = $this->dbh->prepare("SELECT id FROM host WHERE domain_id = ?"); $sth->execute([$domain_id]); while ($host_row = $sth->fetch(PDO::FETCH_ASSOC)) { $host_id = $host_row['id']; $this->dbh->prepare("DELETE FROM host_addr WHERE host_id = ?")->execute([$host_id]); $this->dbh->prepare("DELETE FROM host_status WHERE host_id = ?")->execute([$host_id]); $this->dbh->prepare("DELETE FROM domain_host_map WHERE host_id = ?")->execute([$host_id]); } if (!$minimum_data) { $this->dbh->prepare("DELETE FROM domain_contact_map WHERE domain_id = ?")->execute([$domain_id]); } $this->dbh->prepare("DELETE FROM domain_host_map WHERE domain_id = ?")->execute([$domain_id]); $this->dbh->prepare("DELETE FROM domain_authInfo WHERE domain_id = ?")->execute([$domain_id]); $this->dbh->prepare("DELETE FROM domain_status WHERE domain_id = ?")->execute([$domain_id]); $this->dbh->prepare("DELETE FROM host WHERE domain_id = ?")->execute([$domain_id]); $this->dbh->prepare("DELETE FROM domain WHERE id = ?")->execute([$domain_id]); // Update statistics $this->updateStatistics('deleted_domains'); $this->dbh->commit(); } catch (Exception $e) { $this->dbh->rollBack(); $this->log->error("$domain_id|$name could not be deleted: " . $e->getMessage()); } } // ======================== // Helper Methods // ======================== private function updateStatistics($field) { // Ensure today's statistics record exists $sth = $this->dbh->prepare("SELECT id FROM statistics WHERE date = CURDATE()"); $sth->execute(); if (!$sth->fetchColumn()) { $this->dbh->prepare("INSERT INTO statistics (date) VALUES (CURDATE())")->execute(); } // Update the specific field $sthUpdate = $this->dbh->prepare("UPDATE statistics SET $field = $field + 1 WHERE date = CURDATE()"); $sthUpdate->execute(); } }