<?php
/**
 * @package     com_htprotect
 * @copyright   (C) 2026 Pascal Lohmann / website-bereinigung.de
 * @license     GNU General Public License version 2 or later
 *
 * Home-Dashboard-Statuskachel fuer HTProtect (Quickicon). Sitzt im System-Block
 * (neben Global Checkin / Cache / Global Configuration) und zeigt mit einem
 * Schutzschild-Icon, ob der Schutz aktiv & aktuell ist:
 *   gruen  = Schutz aktiv & aktuell
 *   gelb   = Handlung noetig (nicht eingerichtet / veraltet -> "Jetzt absichern")
 *   rot    = echte Bedrohung (verwundbare Erweiterung / aktive Angriffsspur)
 *
 * Versionsneutral J3-J6. Ab Joomla 4 MUSS die Methode das EVENT-OBJEKT bekommen,
 * sonst entpackt Joomla den Context zu einem String und die Kachel landet im
 * falschen Modul. Darum wird `SubscriberInterface` NUR implementiert, wenn es
 * existiert (J4+); auf J3 fehlt es, dort liefert der klassische Dispatcher den
 * String-Context. Erwarteter Context (per Plugin-Parameter ueberschreibbar):
 *   J4+  -> 'system_quickicon' (System-Block des Home-Dashboards)
 *   J3   -> 'mod_quickicon'    (klassisches Control-Panel)
 *
 * Das Schild-Icon ist ein Font-Glyph (`fas fa-shield-alt`, in J4/5/6 vorhanden)
 * -> rendert aus dem (evtl. gecachten) Modul-HTML ohne injiziertes CSS und faerbt
 * sich ueber den `class`-Schluessel automatisch mit dem Status.
 *
 * Faellt nie hart aus (alles in try/catch) - das Dashboard darf nie stoeren.
 */

defined('_JEXEC') or die;

// Basisklasse versionsneutral (J3 JPlugin / J4+ CMSPlugin).
if (!class_exists('HtpQiBaseCMSPlugin')) {
    if (class_exists('Joomla\\CMS\\Plugin\\CMSPlugin')) {
        class_alias('Joomla\\CMS\\Plugin\\CMSPlugin', 'HtpQiBaseCMSPlugin');
    } elseif (class_exists('JPlugin')) {
        class_alias('JPlugin', 'HtpQiBaseCMSPlugin');
    }
}

/**
 * Gemeinsame Logik (von beiden Klassen-Varianten genutzt).
 */
trait HtpQuickiconLogic
{
    /**
     * Quickicon-Modul baut seine Kacheln.
     *
     * @param   mixed  $context  QuickIconsEvent (J4+) ODER String-Context (J3)
     *
     * @return array|void
     */
    public function onGetIcons($context)
    {
        try {
            return $this->htpIcons($context);
        } catch (Exception $e) {
            // still - nie das Dashboard stoeren
        } catch (Throwable $e) {
            // still (PHP 7+)
        }

        return is_object($context) ? null : array();
    }

    /**
     * Eigentliche Logik (gekapselt fuer das try/catch).
     *
     * @param   mixed  $context  String oder Event-Objekt
     *
     * @return array|void
     */
    private function htpIcons($context)
    {
        $event = (is_object($context) && method_exists($context, 'getContext')) ? $context : null;
        $ctx   = $event !== null ? $event->getContext() : $context;

        // Modernes CMS (J4+): System-Block = Context 'system_quickicon';
        // klassisch (J3): Control-Panel = 'mod_quickicon'. Per Parameter ueberschreibbar.
        $modern   = ($event !== null) || (defined('JVERSION') && version_compare(JVERSION, '4.0', '>='));

        // In der "Updates"-Karte des System-Dashboards (Context 'update_quickicon')
        // einen schlichten Auto-Updates-Link ergaenzen - nur Navigation, kein Status.
        // Gleiche Gruppe wie Joomlas eigene Update-Icons -> erscheint in derselben
        // Karte. Nur >= Joomla 5.4 (dort gibt es das Feature) und nur fuer Berechtigte.
        if ($modern && $event !== null && $ctx === 'update_quickicon') {
            if (defined('JVERSION') && version_compare(JVERSION, '5.4.0', '>=') && $this->htpAuthorised() && !$this->htpStealthHidden()) {
                // Icon der Auto-Update-Kachel im Uhrzeigersinn rotieren + leicht groesser.
                $this->htpInjectAutoUpdateStyle();

                $icon = array(
                    'link'  => 'index.php?option=com_htprotect&sec=backups#htp-autoupdate',
                    'image' => 'icon-refresh',
                    'text'  => 'HTProtect Auto-Updates',
                    'id'    => 'plg_quickicon_htprotect_au',
                    'group' => 'MOD_QUICKICON_MAINTENANCE',
                );
                $result   = $event->getArgument('result', array());
                $result[] = array($icon);
                $event->setArgument('result', $result);
            }

            return;
        }

        $expected = $modern ? 'system_quickicon' : 'mod_quickicon';

        if ($ctx !== $this->params->get('context', $expected)) {
            return $event !== null ? null : array();
        }

        if (!$this->htpAuthorised() || !$this->htpLoadEngine()
            || !class_exists('HtpStatus') || !class_exists('HtpConfig')) {
            return $event !== null ? null : array();
        }

        $cfg = HtpConfig::load();

        // Stealth: fuer ausgeblendete Super-User keine HTProtect-Statuskachel.
        if ($this->htpStealthHidden($cfg)) {
            return $event !== null ? null : array();
        }

        if (function_exists('htp_load_lang')) {
            htp_load_lang();
        }

        $card = HtpStatus::card($cfg);

        if ($modern) {
            // Schutzschild-Glyph; 'class' (success|warning|danger) faerbt den
            // Link serverseitig - genau wie die Joomla-eigenen Statuskacheln.
            $this->htpInjectStyle();

            $icon = array(
                'link'  => $card['link'],
                'image' => 'fas fa-shield-alt',
                'text'  => $card['text'],
                'id'    => 'plg_quickicon_htprotect',
                'group' => 'MOD_QUICKICON_SYSTEM',
                'class' => $card['level'],
            );
        } else {
            // J3-Fallback: Control-Panel-Icon + grosser farbiger Statuspunkt
            // HINTER dem Text (J3 kann die Kachel selbst nicht einfaerben).
            // Gruppe MAINTENANCE -> erscheint unter der vorhandenen Ueberschrift
            // "Wartung" (MOD_QUICKICON_SYSTEM ist auf J3 nicht uebersetzt).
            $this->htpInjectStyle();

            $icon = array(
                'link'  => $card['link'],
                'image' => 'lock',
                'text'  => $card['text'] . ' ' . $this->htpDot($card['level']),
                'id'    => 'plg_quickicon_htprotect',
                'group' => 'MOD_QUICKICON_MAINTENANCE',
            );
        }

        if ($event !== null) {
            // Modern: an das Ergebnis-Array anhaengen (wie plg_quickicon_joomlaupdate).
            $result   = $event->getArgument('result', array());
            $result[] = array($icon);
            $event->setArgument('result', $result);

            return;
        }

        // Legacy (J3): Icon-Array zurueckgeben.
        return array($icon);
    }

    /**
     * Grosser farbiger Statuspunkt (nur J3, da dort die Kachel selbst nicht
     * eingefaerbt werden kann). Bewusst deutlich groesser als der Text, damit
     * der Ampel-Status auf einen Blick erkennbar ist. Rohes HTML aus eigener,
     * fester Quelle.
     *
     * @param   string  $level  success|warning|danger
     *
     * @return string
     */
    private function htpDot($level)
    {
        $colors = array('success' => '#1a9d5a', 'warning' => '#d98a0b', 'danger' => '#cc2b2b');
        $color  = isset($colors[$level]) ? $colors[$level] : '#1a9d5a';

        return '<span style="font-size:3.5em;line-height:0;vertical-align:-0.17em;color:' . $color
            . '" aria-hidden="true">&#9679;</span>';
    }

    /**
     * Schild-/Schloss-Icon der eigenen Kachel etwas groesser stellen. Eng auf die
     * eigene ID begrenzt -> beeinflusst keine andere Quickicon-Kachel. Deckt beide
     * Layouts ab: J4+ (`.quickicon-icon > div`) und J3 (`span.icon-...`). Bewusst
     * ohne Media-Datei (minimal verpacktes, versionsneutrales Plugin). Einmalig.
     *
     * @return void
     */
    private function htpInjectStyle()
    {
        static $done = false;

        if ($done) {
            return;
        }

        $done = true;

        try {
            $doc = null;

            if (class_exists('Joomla\\CMS\\Factory')) {
                $app = Joomla\CMS\Factory::getApplication();
                $doc = ($app && method_exists($app, 'getDocument')) ? $app->getDocument() : null;
            }

            if (!$doc && class_exists('JFactory')) {
                $doc = JFactory::getDocument();
            }

            if ($doc && method_exists($doc, 'addStyleDeclaration')) {
                $doc->addStyleDeclaration(
                    '#plg_quickicon_htprotect .quickicon-icon > div,'
                    . '#plg_quickicon_htprotect span[class^="icon-"]{'
                    . 'font-size:3em;margin-bottom:1px;margin-left:5px;}'
                );
            }
        } catch (Exception $e) {
            // egal - dann hat das Icon nur die Standardgroesse
        }
    }

    /**
     * Das Icon der eigenen "HTProtect Auto-Updates"-Kachel im Uhrzeigersinn rotieren
     * lassen - gibt der Kachel ein sichtbares "laeuft automatisch"-Gefuehl und faellt
     * besser auf. STRIKT auf die eigene Auto-Update-ID begrenzt
     * (#plg_quickicon_htprotect_au) -> beruehrt KEINE andere Kachel, insbesondere NICHT
     * die Schild-Status-Kachel (#plg_quickicon_htprotect). Ruhiges, stetiges Tempo (16 s
     * pro Umdrehung), Originalgroesse. Respektiert reduzierte Bewegung (Barrierefreiheit). Einmalig,
     * fail-soft, ohne Media-Datei. Deckt beide Icon-Auspraegungen ab (J4+ `.quickicon-icon
     * > div` und `span.icon-...`) - identisches Selektor-Muster wie htpInjectStyle.
     *
     * @return void
     */
    private function htpInjectAutoUpdateStyle()
    {
        static $done = false;

        if ($done) {
            return;
        }

        $done = true;

        try {
            $doc = null;

            if (class_exists('Joomla\\CMS\\Factory')) {
                $app = Joomla\CMS\Factory::getApplication();
                $doc = ($app && method_exists($app, 'getDocument')) ? $app->getDocument() : null;
            }

            if (!$doc && class_exists('JFactory')) {
                $doc = JFactory::getDocument();
            }

            if ($doc && method_exists($doc, 'addStyleDeclaration')) {
                $sel = '#plg_quickicon_htprotect_au .quickicon-icon > div,'
                    . '#plg_quickicon_htprotect_au span[class^="icon-"]';

                $doc->addStyleDeclaration(
                    '@keyframes htpQiAuSpin{to{transform:rotate(360deg)}}'
                    . $sel . '{display:inline-block;transform-origin:50% 50%;'
                    . 'animation:htpQiAuSpin 16s linear infinite;}'
                    . '@media (prefers-reduced-motion:reduce){' . $sel . '{animation:none;}}'
                );
            }
        } catch (Exception $e) {
            // egal - dann dreht sich das Icon eben nicht
        } catch (Throwable $e) {
            // egal
        }
    }

    /**
     * Darf der aktuelle Benutzer HTProtect verwalten? (Kachel sonst ausblenden.)
     *
     * @return boolean
     */
    private function htpAuthorised()
    {
        try {
            $user = null;

            // Robust: ZUERST ueber die globale Factory-Anwendung. Die ist auf
            // J4-6 in jedem Request verfuegbar - unabhaengig davon, ob die
            // Anwendung in DIESE Plugin-Instanz injiziert wurde.
            // $this->getApplication() wirft auf manchen J5/J6-Setups (je nach
            // Plugin-Mix/-Ladereihenfolge) eine Exception, wenn die App nicht
            // gesetzt ist -> die Kachel verschwaende dann still. Darum nur als
            // Rueckfallebene, danach noch der J3-Weg ueber JFactory.
            if (class_exists('Joomla\\CMS\\Factory')) {
                $app = Joomla\CMS\Factory::getApplication();

                if ($app && method_exists($app, 'getIdentity')) {
                    $user = $app->getIdentity();
                }
            }

            if (!$user && method_exists($this, 'getApplication')) {
                $app = $this->getApplication();

                if ($app && method_exists($app, 'getIdentity')) {
                    $user = $app->getIdentity();
                }
            }

            if (!$user && class_exists('JFactory')) {
                $user = JFactory::getUser();
            }

            if (!$user || !method_exists($user, 'authorise')) {
                return false;
            }

            // "Nur fuer Super-User" (su_only) bestimmt, welche Berechtigung reicht: Standard = an
            // (nur Super-User, 'core.admin'); ausgeschaltet -> auch normale Admins ('core.manage').
            // Deckungsgleich mit dem Zugriffs-Gate der Komponente (HtpApp::accessAction). Im Zweifel
            // (Engine/Config nicht ladbar) fail-closed auf 'core.admin' -> nur Super-User sehen die
            // Kachel. So folgt die Kachel dem Schalter auch dann, wenn der ACL-Hebel mal nicht griff.
            $action = 'core.admin';

            if ($this->htpLoadEngine() && class_exists('HtpApp') && class_exists('HtpConfig')) {
                $action = HtpApp::accessAction(HtpConfig::load());
            }

            return $user->authorise($action, 'com_htprotect');
        } catch (Exception $e) {
            return false;
        } catch (Throwable $e) {
            return false;
        }
    }

    /**
     * Die HTProtect-Engine laden (einmalig), um HtpStatus/HtpConfig zu nutzen.
     *
     * @return boolean
     */
    private function htpLoadEngine()
    {
        if (defined('HTP_ENGINE_OK')) {
            return HTP_ENGINE_OK;
        }

        $bootstrap = (defined('JPATH_ADMINISTRATOR') ? JPATH_ADMINISTRATOR : JPATH_ROOT . '/administrator')
            . '/components/com_htprotect/core/bootstrap.php';

        if (!is_file($bootstrap)) {
            return false;
        }

        try {
            require_once $bootstrap;
        } catch (Exception $e) {
            return false;
        } catch (Throwable $e) {
            return false;
        }

        return defined('HTP_ENGINE_OK') ? HTP_ENGINE_OK : class_exists('HtpStatus');
    }

    /**
     * Stealth: Ist der aktuelle Benutzer in hidden_su (vor anderen Super-Usern
     * verborgen)? Fail-open (im Zweifel sichtbar); fuer den Normalfall (leere Liste)
     * sofort false.
     *
     * @param   array|null  $cfg  Bereits geladene Config (spart einen Load) oder null.
     *
     * @return boolean
     */
    private function htpStealthHidden($cfg = null)
    {
        try {
            if ($cfg === null) {
                if (!$this->htpLoadEngine() || !class_exists('HtpConfig')) {
                    return false;
                }

                $cfg = HtpConfig::load();
            }

            $hidden = (is_array($cfg) && isset($cfg['hidden_su']) && is_array($cfg['hidden_su'])) ? $cfg['hidden_su'] : array();

            if (empty($hidden)) {
                return false;
            }

            $uid = $this->htpCurrentUserId();

            return $uid > 0 && in_array((string) $uid, array_map('strval', $hidden), true);
        } catch (Exception $e) {
            return false;
        } catch (Throwable $e) {
            return false;
        }
    }

    /**
     * Aktuelle Benutzer-ID (0, wenn nicht ermittelbar).
     *
     * @return integer
     */
    private function htpCurrentUserId()
    {
        try {
            $user = null;

            if (class_exists('Joomla\\CMS\\Factory')) {
                $app = Joomla\CMS\Factory::getApplication();

                if ($app && method_exists($app, 'getIdentity')) {
                    $user = $app->getIdentity();
                }
            }

            if (!$user && class_exists('JFactory')) {
                $user = JFactory::getUser();
            }

            return ($user && isset($user->id)) ? (int) $user->id : 0;
        } catch (Exception $e) {
            return 0;
        } catch (Throwable $e) {
            return 0;
        }
    }
}

// Ab J4 liefert nur ein "echter" Event-Subscriber das Event-Objekt (sonst
// entpackt Joomla den Context zu einem String -> falsches Modul). Auf J3 fehlt
// das Interface; dort genuegt die klassische Methode mit String-Context.
if (interface_exists('Joomla\\Event\\SubscriberInterface') && is_file(__DIR__ . '/htprotect_subscriber.php')) {

    // J4+: Subscriber-Klasse AUSGELAGERT - sie enthaelt den Rueckgabetyp
    // ": array" (PHP 7.0+). So wird diese Syntax auf PHP < 7.0 (Joomla 3 auf
    // PHP 5.6) NIE geparst und die Datei bricht dort nicht mit einem
    // Parse-Fehler ab (ein Inline-Block wuerde trotz interface_exists schon
    // beim Kompilieren scheitern). Erst hier, zur Laufzeit, eingebunden.
    require_once __DIR__ . '/htprotect_subscriber.php';

} elseif (class_exists('HtpQiBaseCMSPlugin')) {

    class PlgQuickiconHtprotect extends HtpQiBaseCMSPlugin
    {
        use HtpQuickiconLogic;

        protected $autoloadLanguage = true;
    }

}
