<?php

namespace CleantalkSP\SpbctWP\VulnerabilityAlarm;

use CleantalkSP\SpbctWP\LinkConstructor;
use CleantalkSP\SpbctWP\VulnerabilityAlarm\Dto\ItemReport;
use CleantalkSP\SpbctWP\VulnerabilityAlarm\Dto\PluginReport;
use CleantalkSP\SpbctWP\UsersPassCheckModule\UsersPassCheckHandler;

class VulnerabilityAlarmView
{
    const ASSETS_URL = SPBC_PATH . '/lib/CleantalkSP/SpbctWP/VulnerabilityAlarm/Assets';
    const RESEARCH_SITE_URL = 'https://research.cleantalk.org';

    /**
     * @param string $plugin_file
     * @param PluginReport $plugin_report
     *
     * @return string
     */
    public static function showPluginAlarm($plugin_file, $plugin_report)
    {
        $active = is_plugin_active($plugin_file) ? 'active' : 'inactive';
        $slug = $plugin_report->slug;
        $id = $plugin_report->id;
        $alarm_text = esc_html__('This version contains a known vulnerability', 'security-malware-firewall');
        $alarm_cve_url = $plugin_report->CVE;
        $delete_text = esc_html__('It is strongly recommended to delete the plugin.', 'security-malware-firewall');
        $update_text = esc_html__('Updating the plugin to a version higher than ', 'security-malware-firewall');

        $vulnerability_text = sprintf(
            $alarm_text . ' ' . $alarm_cve_url . '. %s',
            ! empty($plugin_report->rs_app_version_max)
                ? $update_text . $plugin_report->rs_app_version_max . ' is strongly recommended.'
                : $delete_text
        );

        $layout_file = __DIR__ . '/View/PluginListAlarmLayout.html';

        if ( file_exists($layout_file) ) {
            $replaces = [
                '{{SLUG}}' => esc_attr($slug),
                '{{ACTIVE}}' => esc_attr($active),
                '{{PLUGIN_FILE}}' => esc_attr($plugin_file),
                '{{VULNERABILITY_TEXT}}' => wp_kses($vulnerability_text, 'post'),
                '{{MORE_DETAILS_LINK}}' => static::getMoreDetailsLink($slug, $id)
            ];
            $layout = file_get_contents($layout_file);

            if ($layout) {
                foreach ($replaces as $place_holder => $replace) {
                    $layout = str_replace($place_holder, $replace, $layout);
                }
                return $layout;
            }
        }

        return '<tr><td colspan="4">' . $vulnerability_text . '</td></tr>';
    }

    /**
     * Displays an alarm for a theme identified as vulnerable.
     *
     * This method generates an alarm message for a theme that has been identified as having a known vulnerability.
     * It constructs a message detailing the vulnerability and, depending on the severity, recommends either updating
     * the theme to a specific version or deleting it entirely. The message is then inserted into a predefined HTML
     * layout for themes, if available, or returned as a simple paragraph element.
     *
     * @param object $theme_report An object containing the theme's details, including its slug, ID, CVE, and the maximum
     *                             safe version of the theme, if applicable.
     * @return string The generated alarm message wrapped in either the predefined HTML layout or a paragraph element.
     */
    public static function showThemeAlarm($theme_report)
    {
        $slug = $theme_report->slug;
        $id = $theme_report->id;
        $alarm_text = esc_html__('This theme version contains a known vulnerability', 'security-malware-firewall');
        $alarm_cve_url = $theme_report->CVE;
        $delete_text = esc_html__('It is strongly recommended to delete the theme.', 'security-malware-firewall');
        $update_text = esc_html__('Updating the theme to a version higher than ', 'security-malware-firewall');

        $vulnerability_text = sprintf(
            $alarm_text . ' ' . $alarm_cve_url . '. %s',
            ! empty($theme_report->rs_app_version_max)
                ? $update_text . $theme_report->rs_app_version_max . ' is strongly recommended.'
                : $delete_text
        );

        $layout_file = __DIR__ . '/View/ThemesListAlarmLayout.html';

        if ( file_exists($layout_file) && is_readable($layout_file)) {
            $replaces = [
                '{{SLUG}}' => esc_attr($slug),
                '{{VULNERABILITY_TEXT}}' => wp_kses($vulnerability_text, 'post'),
                '{{MORE_DETAILS_LINK}}' => static::getMoreDetailsLink($slug, $id)
            ];
            $layout = @file_get_contents($layout_file);

            if ($layout) {
                foreach ($replaces as $place_holder => $replace) {
                    $layout = str_replace($place_holder, $replace, $layout);
                }
                return $layout;
            }
        }

        return '<p>' . $vulnerability_text . '</p>';
    }

    /**
     * @param string $module_type
     * @param string $plugin_slug
     * @param string $plugin_id
     * @param string $psc_cert_name
     * @param string $research_url
     * @param bool $placed_on_installed_list
     * @return string
     */
    public static function showSafeBadgePSC($module_type, $plugin_slug, $plugin_id, $psc_cert_name, $research_url, $placed_on_installed_list = false)
    {
        $badge_text = esc_html__('Plugin has received a PSC', 'security-malware-firewall');
        $module_type_translated = esc_html__('plugin', 'security-malware-firewall');

        if ($module_type === 'theme') {
            $badge_text = esc_html__('Theme has received a PSC', 'security-malware-firewall');
            $module_type_translated = esc_html__('theme', 'security-malware-firewall');
        }

        $layout_file = $placed_on_installed_list
            ? __DIR__ . '/View/PluginIsSafeBadgeForInstalledPlugins.html'
            : __DIR__ . '/View/PluginIsSafePSCBadge.html';

        if ( file_exists($layout_file) ) {
            $replaces = [
                '{{ASSETS_URL}}' => self::ASSETS_URL,
                '{{DESCRIPTION}}' => static::getSafeReportChunkPSC(
                    $plugin_slug,
                    $plugin_id,
                    $psc_cert_name,
                    $module_type_translated,
                    $research_url
                ),
                '{{MORE_DETAILS_LINK}}' => static::getVASupportChunk(),
            ];
            $layout = @file_get_contents($layout_file);

            if (false === $layout) {
                $layout = '';
            }

            foreach ($replaces as $place_holder => $replace) {
                $layout = str_replace($place_holder, $replace, $layout);
            }

            return $layout;
        }

        return $badge_text;
    }

    /**
     * @param string $module_slug
     * @param string $module_id
     * @param bool $line_break Should the lines be broken
     *
     * @return string
     */
    private static function getMoreDetailsLink($module_slug = '', $module_id = '', $line_break = false)
    {
        $line_break_layout = $line_break ? '<br>' : ' ';
        return self::getFullReportChunk($module_slug, $module_id)
            . $line_break_layout
            . self::getVASupportChunk();
    }

    /**
     * Returns support link and description.
     * @return string
     */
    private static function getVASupportChunk()
    {
        return sprintf(
            esc_html__('Have questions? Ask us %shere%s.', 'security-malware-firewall'),
            '<a href="https://wordpress.org/support/plugin/security-malware-firewall/" target="_blank">',
            '</a>'
        );
    }

    /**
     * @param $module_slug
     * @param $module_id
     * @return string
     */
    private static function getFullReportChunk($module_slug, $module_id)
    {
        $details_link = static::RESEARCH_SITE_URL;
        if ($module_slug && $module_id) {
            $details_link = esc_attr($details_link . '/reports/app/' . $module_slug . '#' . $module_id);
        }
        $report_text = esc_html__('The full report is %shere%s.', 'security-malware-firewall');
        return sprintf(
            $report_text,
            '<a href="' . $details_link . '" target="_blank">',
            '</a>'
        );
    }

    /**
     * @param $module_slug
     * @param $module_id
     * @param $psc_cert_name
     * @param $module_type_translated
     * @param $research_url
     * @return string
     */
    private static function getSafeReportChunkPSC($module_slug, $module_id, $psc_cert_name, $module_type_translated, $research_url)
    {
        $details_link = static::RESEARCH_SITE_URL;
        if (!empty($research_url)) {
            $details_link = esc_attr($research_url);
        } else if ($module_slug && $module_id) {
            $details_link = esc_attr($details_link . '/reports/app/' . $module_slug . '#' . $module_id);
        }
        $report_text = esc_html__('This version of the %s is security safe and certified as %s%s%s', 'security-malware-firewall');
        return sprintf(
            $report_text,
            $module_type_translated,
            '<a href="' . $details_link . '" target="_blank">',
            $psc_cert_name,
            '</a>'
        );
    }

    /**
     * This method retrieves all vulnerable plugins in the application.
     *
     * It checks each plugin for any known vulnerabilities. If a vulnerability is found,
     * the plugin is considered vulnerable and its report is added to the list of vulnerable plugins.
     *
     * @return ItemReport[] An array containing the reports of all vulnerable plugins.
     */
    private static function getVulnerablePluginsStatic()
    {
        $plugins = get_plugins();
        $vulnerable_plugins = [];

        if ( count($plugins) > 0 ) {
            foreach ( $plugins as $plugin_relative_dir => $plugin_data ) {
                $verified_slug = VulnerabilityAlarm::getPluginSlug($plugin_data, $plugin_relative_dir);
                $verified_version = VulnerabilityAlarm::getPluginVersion($plugin_data);
                $plugin_report = VulnerabilityAlarm::checkPluginVulnerabilityStatic($verified_slug, $verified_version);
                if ( $plugin_report instanceof PluginReport ) {
                    $vulnerable_plugins[] = $plugin_report;
                }
            }
        }
        return $vulnerable_plugins;
    }

    /**
     * This method retrieves all vulnerable themes in the application.
     *
     * It checks each theme for any known vulnerabilities. If a vulnerability is found,
     * the theme is considered vulnerable and its report is added to the list of vulnerable themes.
     *
     * @return ItemReport[] An array containing the reports of all vulnerable themes.
     */
    private static function getVulnerableThemesStatic()
    {
        $themes = wp_get_themes();
        $vulnerable_themes = [];

        if ( count($themes) > 0 ) {
            foreach ( $themes as $theme_data ) {
                $theme_slug = isset($theme_data['slug']) ? $theme_data['slug'] : sanitize_title($theme_data['Name']);
                $theme_version = ! empty($theme_data['Version']) ? $theme_data['Version'] : '';
                $theme_report = VulnerabilityAlarm::checkThemeVulnerabilityStatic($theme_slug, $theme_version);
                if ( $theme_report ) {
                    $vulnerable_themes[] = $theme_report;
                }
            }
        }
        return $vulnerable_themes;
    }


    /**
     * This method retrieves all outdated modules in the application.
     *
     * It checks both plugins and themes for any available updates. If an update is available,
     * it means the module is outdated and it's added to the list of outdated modules.
     *
     * @return array An array containing the names of all outdated modules (both plugins and themes).
     * @psalm-suppress UnusedMethod
     * todo This method is not used now, decision https://doboard.com/1/task/9092
     */
    private static function getOutdatedModules()
    {
        $all_plugins = get_plugins();
        $plugins_updates = get_plugin_updates();
        $all_themes = wp_get_themes();
        $themes_updates = get_theme_updates();

        $outdated_modules = [];

        // Check each plugin
        foreach ( $all_plugins as $plugin_path => $plugin ) {
            // If there is an update for the plugin
            if ( isset($plugins_updates[$plugin_path]) ) {
                $outdated_modules[] = $plugin['Name'];
            }
        }

        // Check each theme
        foreach ( $all_themes as $theme_path => $theme ) {
            // If there is an update for the theme
            if ( isset($themes_updates[$theme_path]) ) {
                $outdated_modules[] = $theme['Name'];
            }
        }

        return $outdated_modules;
    }

    /**
     * Retrieves a list of plugins certified by the Plugin Security Checker (PSC).
     *
     * This method first fetches all installed plugins using the `get_plugins()` function. It then filters these plugins
     * through the `getSafePlugins()` method to identify those that are considered safe. Among the safe plugins, it further
     * filters to identify those that have been specifically certified by the Plugin Security Checker (PSC), indicating
     * a higher level of security assurance. The PSC certification is determined by checking the `app_status` and `psc`
     * properties of each plugin's report data.
     *
     * @return array An array of plugins that are both considered safe and have PSC certification. Each element in the
     *               array is an associative array containing the plugin's ID, slug, and PSC certification status.
     */
    private static function getPscModules()
    {
        return VulnerabilityAlarm::getPSCSafePluginsOnly();
    }

    /**
     * Direct link to the research site report.
     *
     * The report page of the exact certificate is available by its ID (PSC-2024-26398, CVE-2024-1234).
     * If the certificate ID is unknown, the link leads to the list of all the module reports.
     *
     * @param string $module_slug
     * @param string $certificate_id PSC or CVE identifier
     * @param string $module_id Research ID of the module report, used as an anchor of the fallback link
     *
     * @return string
     */
    private static function getResearchReportUrl($module_slug = '', $certificate_id = '', $module_id = '')
    {
        if ( empty($module_slug) ) {
            return LinkConstructor::buildSimpleLink(static::RESEARCH_SITE_URL, 'reports/');
        }

        if ( ! empty($certificate_id) ) {
            return LinkConstructor::buildSimpleLink(
                static::RESEARCH_SITE_URL,
                'reports/search/' . $module_slug . '/' . $certificate_id
            );
        }

        return LinkConstructor::buildSimpleLink(
            static::RESEARCH_SITE_URL,
            'reports/app/' . $module_slug . (! empty($module_id) ? '#' . $module_id : '')
        );
    }

    public static function getCountOfCurrnetlyVulnerableModules()
    {
        return count(static::getVulnerablePluginsStatic()) + count(static::getVulnerableThemesStatic());
    }

    /**
     * Get replacements for last update header.
     * @return array
     */
    public static function getLastUpdateResultReplacements()
    {
        global $spbc;

        $replacements = array();

        //collect total count
        $count_of_plugins = isset($spbc->scan_plugins_info['total_site_plugins_count'])
            ? $spbc->scan_plugins_info['total_site_plugins_count']
            : 0;
        $count_of_themes = isset($spbc->scan_themes_info['total_site_themes_count'])
            ? $spbc->scan_themes_info['total_site_themes_count']
            : 0;
        $total_found_modules = $count_of_plugins + $count_of_themes;

        //collect requested modules count
        $count_of_plugins_requested = isset($spbc->scan_plugins_info['total_site_plugins_count'])
            ? $spbc->scan_plugins_info['plugins_info_requested']
            : 0;
        $count_of_themes_requested = isset($spbc->scan_themes_info['total_site_themes_count'])
            ? $spbc->scan_themes_info['themes_info_requested']
            : 0;
        $total_modules_requested = $count_of_plugins_requested + $count_of_themes_requested;

        // collect vulnerable count
        $count_of_v_plugins = isset($spbc->scan_plugins_info['plugins_found_with_known_vulnerabilities'])
            ? $spbc->scan_plugins_info['plugins_found_with_known_vulnerabilities']
            : 0;
        $count_of_v_themes = isset($spbc->scan_themes_info['themes_found_with_known_vulnerabilities'])
            ? $spbc->scan_themes_info['themes_found_with_known_vulnerabilities']
            : 0;
        $total_found_modules_vulnerable = $count_of_v_plugins + $count_of_v_themes;

        //collect last update timestamp
        $date_ts = isset($spbc->data['spbc_security_check_vulnerabilities_last_call'])
            ? $spbc->data['spbc_security_check_vulnerabilities_last_call']
            : 0;

        //collect current count of modules vulnerabilities
        $current_vul_modules_count = static::getCountOfCurrnetlyVulnerableModules();

        //generate last update result
        if ($total_modules_requested && $date_ts) {
            $date = date('M d Y H:i:s', $date_ts + $spbc->data['site_utc_offset_in_seconds']);
            $replacements['last_update_result'] = sprintf(
                esc_html__('%s plugins and themes found, %s scanned, %s vulnerabilities found at %s on this site.', 'security-malware-firewall'),
                $total_found_modules,
                $total_modules_requested,
                $total_found_modules_vulnerable,
                $date
            );
        } else {
            $replacements['last_update_result'] = esc_html__('No last update data provided. The Security plugin will check plugins and themes soon.', 'security-malware-firewall');
        }

        //generate current state
        if ($current_vul_modules_count) {
            $replacements['current_state_text'] = $current_vul_modules_count;
            $replacements['current_state_class'] = 'spbc-icon-lightbulb-red';
        } else {
            $replacements['current_state_text'] = '';
            $replacements['current_state_class'] = $total_modules_requested ? 'spbc-icon-lightbulb-green' : 'spbc-icon-lightbulb';
        }

        return $replacements;
    }

    /**
     * Collects data for the Critical Updates tab in JSON format
     * @return array
     */
    public static function getTabCriticalUpdatesData()
    {
        // Summary (raw data only)
        global $spbc;
        $last_update = static::getLastUpdateResultReplacements();
        $summary = [
            'total_found_modules' => (isset($spbc->scan_plugins_info['total_site_plugins_count']) ? $spbc->scan_plugins_info['total_site_plugins_count'] : 0)
                + (isset($spbc->scan_themes_info['total_site_themes_count']) ? $spbc->scan_themes_info['total_site_themes_count'] : 0),
            'total_modules_requested' => (isset($spbc->scan_plugins_info['plugins_info_requested']) ? $spbc->scan_plugins_info['plugins_info_requested'] : 0)
                + (isset($spbc->scan_themes_info['themes_info_requested']) ? $spbc->scan_themes_info['themes_info_requested'] : 0),
            'total_found_modules_vulnerable' => (isset($spbc->scan_plugins_info['plugins_found_with_known_vulnerabilities']) ? $spbc->scan_plugins_info['plugins_found_with_known_vulnerabilities'] : 0)
                + (isset($spbc->scan_themes_info['themes_found_with_known_vulnerabilities']) ? $spbc->scan_themes_info['themes_found_with_known_vulnerabilities'] : 0),
            'last_update_date' => (isset($spbc->data['spbc_security_check_vulnerabilities_last_call']) && isset($spbc->data['site_utc_offset_in_seconds']) && $spbc->data['spbc_security_check_vulnerabilities_last_call'])
                ? date('M d Y H:i:s', $spbc->data['spbc_security_check_vulnerabilities_last_call'] + $spbc->data['site_utc_offset_in_seconds'])
                : '',
            'current_vul_modules_count' => static::getCountOfCurrnetlyVulnerableModules(),
            'current_state_class' => isset($last_update['current_state_class']) ? $last_update['current_state_class'] : '',
            'current_state_text' => isset($last_update['current_state_text']) ? $last_update['current_state_text'] : '',
        ];

        // Passwords
        $users_with_leaked_passwords = \CleantalkSP\SpbctWP\UsersPassCheckModule\UsersPassCheckHandler::getUsersWithLeakedPasswords();
        $passwords = [
            'leaked_users' => array_map(function ($user) {
                return [
                    'user_id' => $user->user_id,
                    'user_login' => get_userdata($user->user_id)->user_login,
                ];
            }, $users_with_leaked_passwords)
        ];

        // Vulnerabilities (plugins)
        $vulnerable_plugins = static::getVulnerablePluginsStatic();
        $vulnerabilities = [];
        foreach ($vulnerable_plugins as $plugin) {
            $vulnerabilities[] = [
                'type' => 'plugin',
                'name' => $plugin->app_name ?: $plugin->slug,
                'slug' => $plugin->slug,
                'version_from' => !empty($plugin->rs_app_version_min) ? $plugin->rs_app_version_min : '0.0',
                'version_to' => !empty($plugin->rs_app_version_max) ? $plugin->rs_app_version_max : 'unknown',
                'cve' => !empty($plugin->CVE) ? $plugin->CVE : 'CVE-' . $plugin->id,
                'cve_url' => static::getResearchReportUrl(
                    $plugin->slug,
                    !empty($plugin->CVE) ? $plugin->CVE : '',
                    $plugin->id
                ),
                'recommendation' => !empty($plugin->rs_app_version_max) ? 'update' : 'delete',
            ];
        }
        // Vulnerabilities (themes)
        $vulnerable_themes = static::getVulnerableThemesStatic();
        foreach ($vulnerable_themes as $theme) {
            $vulnerabilities[] = [
                'type' => 'theme',
                'name' => $theme->app_name ?: $theme->slug,
                'slug' => $theme->slug,
                'version_from' => !empty($theme->rs_app_version_min) ? $theme->rs_app_version_min : '0.0',
                'version_to' => !empty($theme->rs_app_version_max) ? $theme->rs_app_version_max : 'unknown',
                'cve' => !empty($theme->CVE) ? $theme->CVE : 'CVE-' . $theme->id,
                'cve_url' => static::getResearchReportUrl(
                    $theme->slug,
                    !empty($theme->CVE) ? $theme->CVE : '',
                    $theme->id
                ),
                'recommendation' => !empty($theme->rs_app_version_max) ? 'update' : 'delete',
            ];
        }

        // PSC
        $psc_modules = static::getPscModules();
        $psc_certificates = ! empty($psc_modules)
            ? PscCertifiedPluginsCache::getCertificatesMap(array_column($psc_modules, 'slug'))
            : array();
        $psc = array_map(function ($module) use ($psc_certificates) {
            $certificate = isset($psc_certificates[$module['slug']]) ? $psc_certificates[$module['slug']] : '';

            return [
                'type' => !empty($module['type']) ? $module['type'] : 'plugin',
                'name' => $module['slug'],
                'version' => $module['version'],
                'psc' => $module['psc'],
                'psc_url' => static::getResearchReportUrl($module['slug'], $certificate),
            ];
        }, $psc_modules);

        // Legend
        $legend = [
            'db_link' => static::getResearchReportUrl(),
        ];

        return [
            'summary' => $summary,
            'passwords' => $passwords,
            'vulnerabilities' => $vulnerabilities,
            'psc' => $psc,
            'legend' => $legend,
        ];
    }

    /**
     * Buffer handler. Modifies plugin list tbody to add PSC badges for safe plugins.
     * @param string $html
     * @param array $safe_plugins_data
     * @return array [error, html]
     */
    public static function addPSCBadgeToInstalledPluginsList($html, $safe_plugins_data)
    {
        $result = array(
            'error' => false,
            'html' => null,
        );

        $prev = libxml_use_internal_errors(true);

        try {
            if (empty($html) || !preg_match('/id=["\']the-list["\']/', $html)) {
                throw new \Exception('No matched content found');
            }

            if (empty($safe_plugins_data)) {
                throw new \Exception('Safe plugins data empty');
            }

            // No TBODY FOUND
            if (!preg_match('#(<tbody[^>]*id=["\']the-list["\'][^>]*>.*?</tbody>)#si', $html, $matches)) {
                throw new \Exception('tbody#the-list not found');
            }

            // work with tbody ONLY, otherwise will be side effects
            $tbody_html = $matches[1];

            $dom = new \DOMDocument();
            $dom->loadHTML('<?xml encoding="utf-8" ?>' . $tbody_html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);

            $xpath = new \DOMXPath($dom);

            $titles = self::getPluginTitleElements($xpath);

            if (empty($titles)) {
                throw new \Exception('No titles found');
            }

            // add badges
            foreach ($titles as $plugin_data) {
                foreach ($safe_plugins_data as $safe_plugin_data) {
                    if ($plugin_data['slug'] !== $safe_plugin_data['slug']) {
                        continue;
                    }

                    self::appendBadgeToTitles($dom, $plugin_data['title_container'], $safe_plugin_data);
                }
            }

            // Set a new body
            $new_tbody = $dom->saveHTML();

            // insert new body to buffer
            $result['html'] = str_replace($tbody_html, $new_tbody, $html);
        } catch (\Exception $e) {
            $error = __CLASS__ . ':' . __METHOD__ . ': ' . $e->getMessage();
            $result['error'] = $error;
        } finally {
            libxml_use_internal_errors($prev);
        }

        return $result;
    }

    /**
     * @param \DOMXPath $xpath
     * @return array of [slug, title_container]
     * @throws \Exception
     */
    public static function getPluginTitleElements($xpath)
    {
        $titles = [];

        //@psalm-suppress WpdbUnsafeMethodsIssue
        $plugins_the_body = $xpath->query("//tbody[@id='the-list']");

        if (!$plugins_the_body instanceof \DOMNodeList) {
            throw new \Exception('no table body found on xpath');
        }

        $plugins_the_body = $plugins_the_body->item(0);

        if (null === $plugins_the_body) {
            throw new \Exception('no table body found ');
        }

        $plugins_tr_list = $xpath->query(".//tr[string-length(@data-slug) > 0]", $plugins_the_body);

        if (!$plugins_tr_list instanceof \DOMNodeList) {
            throw new \Exception('no plugin rows found on xpath');
        }

        if ($plugins_tr_list->count() === 0) {
            throw new \Exception('no plugin rows found on count');
        }

        foreach ($plugins_tr_list as $header_tr) {
            /**
             * @var \DOMElement $header_tr
             */
            $class = $header_tr->getAttribute('class');
            if (strpos($class, 'update-tr') !== false) {
                continue;
            }
            $plugin_title_container = $xpath->query(".//td[contains(@class,'plugin-title')]/strong", $header_tr)->item(0);

            if (!$plugin_title_container instanceof \DOMNode) {
                // no strong element - skip
                continue;
            }

            $slug = $header_tr->getAttribute('data-slug');

            if (empty($slug)) {
                // no slug - skip
                continue;
            }

            $titles[] = ['slug' => $slug, 'title_container' => $plugin_title_container];
        }

        return $titles;
    }

    /**
     * Appends psc badges to the title container
     * @param \DOMDocument $dom
     * @param \DOMElement $title_container
     * @param array $safe_plugin_data
     * @return void
     */
    public static function appendBadgeToTitles($dom, $title_container, $safe_plugin_data)
    {
        $badge = VulnerabilityAlarm::showSafeBadge(
            'plugin',
            $safe_plugin_data['slug'],
            $safe_plugin_data['id'],
            $safe_plugin_data['psc'],
            $safe_plugin_data['report_object']->research_url,
            true
        );
        if (empty($badge)) {
            return;
        }
        $badge_dom = new \DOMDocument();
        $badge_dom->loadHTML('<?xml encoding="utf-8" ?><div>' . $badge . '</div>');

        $badge_container = $badge_dom->getElementsByTagName('div')->item(0);

        foreach ($badge_container->childNodes as $child) {
            $imported = $dom->importNode($child, true);
            $title_container->appendChild($imported);
        }
    }
}
