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