From c2b24e15a5402659584503ddc7388f02b128eff3 Mon Sep 17 00:00:00 2001 From: addison74 <8360474+ADDISON74@users.noreply.github.com> Date: Sat, 4 Oct 2025 20:49:28 +0300 Subject: [PATCH 1/5] Add CLI script for cleaning admin notifications with advanced filtering --- shell/cleanAdminNotifications.php | 217 ++++++++++++++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 shell/cleanAdminNotifications.php diff --git a/shell/cleanAdminNotifications.php b/shell/cleanAdminNotifications.php new file mode 100644 index 00000000000..be0c2e6e89a --- /dev/null +++ b/shell/cleanAdminNotifications.php @@ -0,0 +1,217 @@ + + */ + protected $severityMap = [ + 0 => 'Notice', + 1 => 'Critical', + 2 => 'Major', + 3 => 'Minor', + ]; + + /** + * Run the shell logic for cleaning admin notifications. + * + * Supports: + * --before Delete notifications before a specific date (format: YYYY-MM-DD or YYYY-MM-DD HH:MM:SS) + * --only-read Delete only notifications marked as read + * --include-unread Allows deletion of unread notifications (use with caution!) + * --severity-0 Only notifications with severity Notice + * --severity-1 Only notifications with severity Critical + * --severity-2 Only notifications with severity Major + * --severity-3 Only notifications with severity Minor + * --all Delete all notifications (requires --include-unread for unread notifications) + * --dry-run Show notifications that would be deleted (table output) + * help Display usage information + * + * Example: php shell/cleanAdminNotifications.php --before 2024-01-01 --severity-0 --dry-run + */ + public function run(): void + { + // Read CLI arguments, support fallback for direct _args usage + $before = $this->getArg('before'); + if (!$before && isset($this->_args['before'])) { + $before = $this->_args['before']; + } + $onlyRead = $this->getArg('only-read') ?: (isset($this->_args['only-read']) ? $this->_args['only-read'] : null); + $includeUnread = $this->getArg('include-unread') ?: (isset($this->_args['include-unread']) ? $this->_args['include-unread'] : null); + $all = $this->getArg('all') ?: (isset($this->_args['all']) ? $this->_args['all'] : null); + $dryRun = $this->getArg('dry-run') ?: (isset($this->_args['dry-run']) ? $this->_args['dry-run'] : null); + + // Parse severity options + $severities = []; + foreach ([0, 1, 2, 3] as $sev) { + if ($this->getArg('severity-' . $sev) || isset($this->_args['severity-' . $sev])) { + $severities[] = $sev; + } + } + + $conn = Mage::getSingleton('core/resource')->getConnection('core_write'); + $table = Mage::getSingleton('core/resource')->getTableName('adminnotification/inbox'); + + // PROTECTION: By default, protect unread notifications unless --include-unread is present + $protectUnread = !$includeUnread; + + // If --all is used and --include-unread is not present, abort with error and count + if ($all && $protectUnread) { + // Count unread notifications + $countUnread = $conn->fetchOne("SELECT COUNT(*) FROM $table WHERE is_read = 0"); + echo "ERROR: Cannot delete ALL notifications unless --include-unread is specified!\n"; + if ($countUnread > 0) { + echo "There are $countUnread unread notifications present in the database. Unread notifications are protected by default.\n"; + } else { + echo "Unread notifications are protected by default.\n"; + } + echo "To delete all notifications, including unread, use:\n"; + echo " php shell/cleanAdminNotifications.php --all --include-unread\n"; + return; + } + + if ($dryRun) { + // Build SELECT for dry-run + $select = $conn->select() + ->from($table, [ + 'notification_id', + 'title', + 'description', + 'date_added', + 'is_read', + 'severity', + ]); + if (!$all) { + if ($before) { + $select->where('date_added < ?', $before); + } + // Apply unread protection unless include-unread is present + if ($protectUnread || $onlyRead) { + $select->where('is_read = ?', 1); + } + if (!empty($severities)) { + $select->where('severity IN (?)', $severities); + } + } + $select->limit(50); + + $rows = $conn->fetchAll($select); + + if (count($rows)) { + // Print table header + printf("%-5s | %-40s | %-40s | %-20s | %-6s | %-8s\n", 'ID', 'Title', 'Description', 'Date Added', 'Read', 'Severity'); + printf("%s\n", str_repeat('-', 140)); + foreach ($rows as $row) { + // Truncate description for display + $desc = isset($row['description']) ? substr((string) $row['description'], 0, 40) : ''; + $severityLabel = isset($this->severityMap[(int) $row['severity']]) ? $this->severityMap[(int) $row['severity']] : (string) $row['severity']; + printf( + "%-5d | %-40s | %-40s | %-20s | %-6s | %-8s\n", + (int) $row['notification_id'], + substr((string) $row['title'], 0, 40), + $desc, + (string) $row['date_added'], + ((int) $row['is_read'] === 1 ? 'Yes' : 'No'), + $severityLabel, + ); + } + if (count($rows) == 50) { + echo "(Showing first 50 results)\n"; + } + // Show total count of matching notifications + $selectCount = $conn->select()->from($table, 'COUNT(*)'); + if (!$all) { + if ($before) { + $selectCount->where('date_added < ?', $before); + } + if ($protectUnread || $onlyRead) { + $selectCount->where('is_read = ?', 1); + } + if (!empty($severities)) { + $selectCount->where('severity IN (?)', $severities); + } + } + $countTotal = $conn->fetchOne($selectCount); + echo "Total notifications that would be deleted: $countTotal\n"; + } else { + echo "No notifications would be deleted.\n"; + } + } else { + // Prepare WHERE for delete + $where = []; + if (!$all) { + if ($before) { + $where[] = $conn->quoteInto('date_added < ?', $before); + } + // Apply unread protection unless include-unread is present + if ($protectUnread || $onlyRead) { + $where[] = $conn->quoteInto('is_read = ?', 1); + } + if (!empty($severities)) { + $where[] = $conn->quoteInto('severity IN (?)', $severities); + } + } + $whereClause = $all ? '' : (count($where) ? implode(' AND ', $where) : ''); + if ($all) { + $count = $conn->delete($table); // Delete everything (only possible if --include-unread) + } elseif ($whereClause !== '') { + $count = $conn->delete($table, $whereClause); // Delete with filter + } else { + echo "No filters specified, and --all not set. Nothing deleted.\n"; + return; + } + echo "Deleted $count notifications.\n"; + } + } + + /** + * Display usage/help for the CLI script. + */ + public function usageHelp(): string + { + return + "Usage: php -f cleanAdminNotifications.php [options]\n" . + " --before YYYY-MM-DD Delete notifications before this date (space, not =)\n" . + " --only-read Delete only notifications marked as read (default: ON unless --include-unread is used)\n" . + " --include-unread Allows deletion of unread notifications (use with caution!)\n" . + " --severity-0 Only notifications with severity Notice\n" . + " --severity-1 Only notifications with severity Critical\n" . + " --severity-2 Only notifications with severity Major\n" . + " --severity-3 Only notifications with severity Minor\n" . + " --all Delete all notifications (requires --include-unread for unread notifications)\n" . + " --dry-run Show what would be deleted, no changes\n" . + " help This help message\n" . + "\nIMPORTANT: For options with values, use space (not =) between option and value!\n" . + "Example: php shell/cleanAdminNotifications.php --before 2024-01-01 --severity-0 --dry-run\n" . + " php shell/cleanAdminNotifications.php --all --include-unread\n"; + } +} + +// Instantiate and run the shell script +$shell = new Mage_Shell_CleanAdminNotifications(); +$shell->run(); From e1762d99fcd7f6bcc750f665768760527975cbbd Mon Sep 17 00:00:00 2001 From: addison74 <8360474+ADDISON74@users.noreply.github.com> Date: Sat, 4 Oct 2025 21:29:58 +0300 Subject: [PATCH 2/5] Implementing Copilot comments --- shell/cleanAdminNotifications.php | 33 +++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/shell/cleanAdminNotifications.php b/shell/cleanAdminNotifications.php index be0c2e6e89a..0f928966753 100644 --- a/shell/cleanAdminNotifications.php +++ b/shell/cleanAdminNotifications.php @@ -1,5 +1,7 @@ where('severity IN (?)', $severities); } } - $select->limit(50); + $select->limit(self::DRY_RUN_LIMIT); $rows = $conn->fetchAll($select); if (count($rows)) { // Print table header printf("%-5s | %-40s | %-40s | %-20s | %-6s | %-8s\n", 'ID', 'Title', 'Description', 'Date Added', 'Read', 'Severity'); - printf("%s\n", str_repeat('-', 140)); + printf("%s\n", str_repeat('-', self::TABLE_WIDTH)); foreach ($rows as $row) { - // Truncate description for display - $desc = isset($row['description']) ? substr((string) $row['description'], 0, 40) : ''; + // Truncate title and description for display + $desc = isset($row['description']) ? substr((string) $row['description'], 0, self::COLUMN_TRUNCATE_LENGTH) : ''; $severityLabel = isset($this->severityMap[(int) $row['severity']]) ? $this->severityMap[(int) $row['severity']] : (string) $row['severity']; printf( "%-5d | %-40s | %-40s | %-20s | %-6s | %-8s\n", (int) $row['notification_id'], - substr((string) $row['title'], 0, 40), + substr((string) $row['title'], 0, self::COLUMN_TRUNCATE_LENGTH), $desc, (string) $row['date_added'], ((int) $row['is_read'] === 1 ? 'Yes' : 'No'), $severityLabel, ); } - if (count($rows) == 50) { - echo "(Showing first 50 results)\n"; + if (count($rows) == self::DRY_RUN_LIMIT) { + echo '(Showing first ' . self::DRY_RUN_LIMIT . " results)\n"; } // Show total count of matching notifications $selectCount = $conn->select()->from($table, 'COUNT(*)'); From 3a087c8f61befffb04949ae6e6ac39da5591cc42 Mon Sep 17 00:00:00 2001 From: addison74 <8360474+ADDISON74@users.noreply.github.com> Date: Sat, 4 Oct 2025 21:38:57 +0300 Subject: [PATCH 3/5] Fixes Rector issues --- shell/cleanAdminNotifications.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/shell/cleanAdminNotifications.php b/shell/cleanAdminNotifications.php index 0f928966753..4ffbd936557 100644 --- a/shell/cleanAdminNotifications.php +++ b/shell/cleanAdminNotifications.php @@ -78,10 +78,10 @@ public function run(): void if (!$before && isset($this->_args['before'])) { $before = $this->_args['before']; } - $onlyRead = $this->getArg('only-read') ?: (isset($this->_args['only-read']) ? $this->_args['only-read'] : null); - $includeUnread = $this->getArg('include-unread') ?: (isset($this->_args['include-unread']) ? $this->_args['include-unread'] : null); - $all = $this->getArg('all') ?: (isset($this->_args['all']) ? $this->_args['all'] : null); - $dryRun = $this->getArg('dry-run') ?: (isset($this->_args['dry-run']) ? $this->_args['dry-run'] : null); + $onlyRead = $this->getArg('only-read') ?: ($this->_args['only-read'] ?? null); + $includeUnread = $this->getArg('include-unread') ?: ($this->_args['include-unread'] ?? null); + $all = $this->getArg('all') ?: ($this->_args['all'] ?? null); + $dryRun = $this->getArg('dry-run') ?: ($this->_args['dry-run'] ?? null); // Parse severity options $severities = []; @@ -146,7 +146,7 @@ public function run(): void foreach ($rows as $row) { // Truncate title and description for display $desc = isset($row['description']) ? substr((string) $row['description'], 0, self::COLUMN_TRUNCATE_LENGTH) : ''; - $severityLabel = isset($this->severityMap[(int) $row['severity']]) ? $this->severityMap[(int) $row['severity']] : (string) $row['severity']; + $severityLabel = $this->severityMap[(int) $row['severity']] ?? (string) $row['severity']; printf( "%-5d | %-40s | %-40s | %-20s | %-6s | %-8s\n", (int) $row['notification_id'], From 939bf0b7b131f209f859d3631dc4097dc28ee750 Mon Sep 17 00:00:00 2001 From: addison74 <8360474+ADDISON74@users.noreply.github.com> Date: Sat, 4 Oct 2025 22:07:53 +0300 Subject: [PATCH 4/5] Replacing a nested ternary with clear conditional statements for readability --- shell/cleanAdminNotifications.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/shell/cleanAdminNotifications.php b/shell/cleanAdminNotifications.php index 4ffbd936557..17d0156ac84 100644 --- a/shell/cleanAdminNotifications.php +++ b/shell/cleanAdminNotifications.php @@ -193,7 +193,16 @@ public function run(): void $where[] = $conn->quoteInto('severity IN (?)', $severities); } } - $whereClause = $all ? '' : (count($where) ? implode(' AND ', $where) : ''); + + // Refactored for clarity, as per suggestion: + if ($all) { + $whereClause = ''; + } elseif (count($where)) { + $whereClause = implode(' AND ', $where); + } else { + $whereClause = ''; + } + if ($all) { $count = $conn->delete($table); // Delete everything (only possible if --include-unread) } elseif ($whereClause !== '') { From bcdd95448e7bd23f47a75e24c03a93d45ab22bd8 Mon Sep 17 00:00:00 2001 From: addison74 <8360474+ADDISON74@users.noreply.github.com> Date: Sat, 4 Oct 2025 23:38:43 +0300 Subject: [PATCH 5/5] Renamed the file name for clarity and consistency with the other shell scripts --- .../{cleanAdminNotifications.php => notifications.php} | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) rename shell/{cleanAdminNotifications.php => notifications.php} (95%) diff --git a/shell/cleanAdminNotifications.php b/shell/notifications.php similarity index 95% rename from shell/cleanAdminNotifications.php rename to shell/notifications.php index 17d0156ac84..edee547f77a 100644 --- a/shell/cleanAdminNotifications.php +++ b/shell/notifications.php @@ -69,7 +69,7 @@ class Mage_Shell_CleanAdminNotifications extends Mage_Shell_Abstract * --dry-run Show notifications that would be deleted (table output) * help Display usage information * - * Example: php shell/cleanAdminNotifications.php --before 2024-01-01 --severity-0 --dry-run + * Example: php shell/notifications.php --before 2024-01-01 --severity-0 --dry-run */ public function run(): void { @@ -108,7 +108,7 @@ public function run(): void echo "Unread notifications are protected by default.\n"; } echo "To delete all notifications, including unread, use:\n"; - echo " php shell/cleanAdminNotifications.php --all --include-unread\n"; + echo " php shell/notifications.php --all --include-unread\n"; return; } @@ -221,7 +221,7 @@ public function run(): void public function usageHelp(): string { return - "Usage: php -f cleanAdminNotifications.php [options]\n" . + "Usage: php -f notifications.php [options]\n" . " --before YYYY-MM-DD Delete notifications before this date (space, not =)\n" . " --only-read Delete only notifications marked as read (default: ON unless --include-unread is used)\n" . " --include-unread Allows deletion of unread notifications (use with caution!)\n" . @@ -233,8 +233,8 @@ public function usageHelp(): string " --dry-run Show what would be deleted, no changes\n" . " help This help message\n" . "\nIMPORTANT: For options with values, use space (not =) between option and value!\n" . - "Example: php shell/cleanAdminNotifications.php --before 2024-01-01 --severity-0 --dry-run\n" . - " php shell/cleanAdminNotifications.php --all --include-unread\n"; + "Example: php shell/notifications.php --before 2024-01-01 --severity-0 --dry-run\n" . + " php shell/notifications.php --all --include-unread\n"; } }