Mind if I ask what's the point of leaving both notifiers? Either way, the observer needs to be modified to return now completely different code, so what's the gain in leaving the old notifier next to the new one in index_dashboard.php? I would understand leaving both in the observer files of a plugin so it can attach to either version of the dashboard, but leaving both notifiers could IMO just cause confusion and possibly issues down the road. Or am I missing something here because my coffee hasn't kicked in yet?
Here's an updated admin observer for AbuseIPDB plugin:
Code:
<?php
/**
* Module: AbuseIPDB
*
* @requires Zen Cart 2.1.0 or later, PHP 7.4+ (recommended: PHP 8.x)
* @author Marcopolo
* @copyright 2023-2025
* @license GNU General Public License (GPL) - https://www.gnu.org/licenses/gpl-3.0.html
* @version 4.0.5
* @updated 07-Feb-2026 (Modern Admin Dashboard)
* @github https://github.com/CcMarc/AbuseIPDB
*/
class zcObserverAbuseIPDBWidget extends base
{
function __construct()
{
$this->attach($this, array(
'NOTIFY_ADMIN_DASHBOARD_ZONES', // Modern Admin Dashboard
'NOTIFY_ADMIN_DASHBOARD_WIDGETS' // Legacy Dashboard
));
}
private function getPluginConfig()
{
if (!defined('ABUSEIPDB_WIDGET_ENABLED') || ABUSEIPDB_WIDGET_ENABLED !== 'true') {
return false;
}
global $db;
$version_sql = $db->execute("SELECT version FROM " . TABLE_PLUGIN_CONTROL . " WHERE unique_key = 'AbuseIPDB'");
if ($version_sql->EOF) return false;
$version = $version_sql->fields["version"];
$session_rate_limit_enabled = false;
$session_setting = $db->Execute("SELECT configuration_value FROM " . TABLE_CONFIGURATION . " WHERE configuration_key = 'ABUSEIPDB_SESSION_RATE_LIMIT_ENABLED'");
if (!$session_setting->EOF && $session_setting->fields['configuration_value'] === 'true') {
$session_rate_limit_enabled = true;
}
return [$version, $session_rate_limit_enabled];
}
/**
* handler for Modern Admin Dashboard
*/
public function updateNotifyAdminDashboardZones(&$class, $eventID, $empty, &$zones)
{
$config = $this->getPluginConfig();
if (!$config) return;
list($version, $session_rate_limit_enabled) = $config;
$widget_filename = 'AbuseIPDBDashboardWidget.php';
$widget_path = DIR_FS_CATALOG . "zc_plugins/AbuseIPDB/$version/admin/includes/modules/dashboard_widgets/$widget_filename";
$found = false;
// scan ALL zones. If widget found, update it to the full path
foreach ($zones as $zone_name => &$widgets_list) {
if (!is_array($widgets_list)) continue;
foreach ($widgets_list as $key => &$val) {
if (basename($val) === $widget_filename) {
$found = true;
// overwrite the simple filename with the full plugin path
$val = $widget_path;
}
}
}
// inject if NOT found anywhere
if (!$found) {
if ($session_rate_limit_enabled) {
// sidebar
if (!isset($zones['sidebar'])) $zones['sidebar'] = [];
array_unshift($zones['sidebar'], $widget_path);
} else {
// main
if (!isset($zones['main'])) $zones['main'] = [];
$zones['main'][] = $widget_path;
}
}
}
/**
* handler for Legacy Dashboard
*/
public function updateNotifyAdminDashboardWidgets(&$class, $eventID, $empty, &$widgets)
{
$config = $this->getPluginConfig();
if (!$config) return;
list($version, $session_rate_limit_enabled) = $config;
$widget_path = DIR_FS_CATALOG . "zc_plugins/AbuseIPDB/$version/admin/includes/modules/dashboard_widgets/AbuseIPDBDashboardWidget.php";
if ($session_rate_limit_enabled) {
// Place widget in column 3 (right), at the top
$filteredWidgets = array_filter($widgets, function($item) {
return $item['column'] === 3;
});
$sortValues = array_column($filteredWidgets, 'sort');
$minSort = empty($sortValues) ? 10 : min($sortValues) - 10;
$minSort = max(10, $minSort);
$widgets[] = [
'column' => 3,
'sort' => $minSort,
'visible' => true,
'path' => $widget_path
];
} else {
// Place widget in column 1 (left), at the bottom
$filteredWidgets = array_filter($widgets, function($item) {
return $item['column'] === 1;
});
$sortValues = array_column($filteredWidgets, 'sort');
$maxSort = empty($sortValues) ? 10 : max($sortValues) + 10;
$widgets[] = [
'column' => 1,
'sort' => $maxSort,
'visible' => true,
'path' => $widget_path
];
}
}
}
There's also a required change in index_dashboard.php (simply replace the current render_zone() function:
Code:
function render_zone($zone_name, $widgets_array)
{
global $db, $currencies, $show_status_pills, $target_status_ids, $sniffer, $zco_notifier, $messageStack; // Globals needed inside widgets
echo '<ul id="zone-' . $zone_name . '" class="sortable-list list-unstyled row" style="min-height: 200px; padding-bottom: 50px;">';
foreach ($widgets_array as $widget_file) {
$path = '';
// check if this is an Encapsulated Plugin (absolute path provided)
if (file_exists($widget_file)) {
$path = $widget_file;
}
// treat as Core Widget (relative filename provided)
else {
$path = DIR_WS_MODULES . 'dashboard_widgets/' . $widget_file;
}
if (file_exists($path)) {
$widget_name = basename($path);
$col_class = 'col-md-12';
if ($zone_name == 'bottom') {
if ($widget_name == 'TrafficDashboardWidget.php') {
$col_class = 'col-xs-12 col-md-6'; // traffic gets half width
} else {
$col_class = 'col-xs-12 col-md-3'; // others get quarter width
}
}
// data-markers for JS
$data_attr = 'data-id="' . $widget_name . '"';
$li_class = $col_class . ' widget-li';
// Traffic widget - prevent it moving to sidebar
if ($widget_name == 'TrafficDashboardWidget.php') {
$li_class .= ' locked-bottom';
}
echo '<li class="' . $li_class . '" ' . $data_attr . '>';
include $path;
echo '</li>';
}
}
echo '</ul>';
}
Please let me know how you feel about this before I update the plugin on github. THX