HEX
Server: LiteSpeed
System: Linux cde2.duelhost.dk 4.18.0-553.34.1.lve.el8.x86_64 #1 SMP Thu Jan 9 16:30:32 UTC 2025 x86_64
User: dtptviut (1121)
PHP: 8.0.30
Disabled: exec,system,passthru,shell_exec,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname
Upload Files
File: /home/dtptviut/domains/morsunivers.dk/public_html/wp-content/plugins/pretty-link/src/Admin/Page.php
<?php

declare(strict_types=1);

namespace PrettyLinks\Admin;

use PrettyLinks\Support\HasStaticContainer;
use PrettyLinks\Support\StaticContainerAwareness;

/**
 * Top-level Pretty Links admin menu.
 *
 * The parent slug is `pretty-link`. The landing page renders a dashboard
 * React mount point. Submenus attach to this parent via `add_submenu_page`
 * in their own classes (see Admin\Pages\*).
 */
class Page implements StaticContainerAwareness
{
    use HasStaticContainer;

    public const CAPABILITY = 'manage_options';

    public const SLUG = 'pretty-link';

    /**
     * Translated page title shown in the menu and document title.
     *
     * @return string
     */
    public static function pageTitle(): string
    {
        return esc_html__('Pretty Links', 'pretty-link');
    }

    /**
     * Register the top-level menu and its Dashboard submenu.
     *
     * @return string The menu page hook suffix returned by add_menu_page().
     */
    public static function register(): string
    {
        $hook = add_menu_page(
            self::pageTitle(),
            esc_html__('Pretty Links', 'pretty-link'),
            self::capability(),
            self::SLUG,
            [self::class, 'render'],
            self::menuIcon(),
            100
        );

        // Registering the first submenu with the parent's own slug overrides
        // the auto-duplicated "Pretty Links" label WordPress would otherwise
        // generate from the top-level menu title.
        add_submenu_page(
            self::SLUG,
            self::pageTitle(),
            esc_html__('Dashboard', 'pretty-link'),
            self::capability(),
            self::SLUG,
            [self::class, 'render']
        );

        return $hook;
    }

    /**
     * Resolve the required capability. Pro hooks `prli_admin_capability` to
     * return prlipro_options['min_role'] (v3 parity — was a Pro-only setting).
     *
     * Security note: third-party code MUST NOT reduce this below
     * `manage_options`. Lowering the capability grants full Pretty Link
     * admin access (link CRUD, settings, reports) to those users.
     */
    public static function capability(): string
    {
        return (string) apply_filters('prli_admin_capability', self::CAPABILITY);
    }

    /**
     * Icon value passed to `add_menu_page`.
     *
     * Returns `'none'` when our bundled SVG is on disk — WP then renders
     * an empty `.wp-menu-image` div, and `enqueueMenuIconStyle()` paints
     * the icon via a CSS mask whose color follows the admin color scheme's
     * per-state `:before { color: ... }` rules. This avoids the brief
     * flicker that happens when WP's `svg-painter.js` rewrites a data-URI
     * icon's fill on jQuery-ready, and works on every admin color scheme
     * without hardcoding a base color (Fresh uses #a7aaad, Modern uses
     * #f3f1f1, etc.).
     *
     * Falls back to a built-in dashicon if the bundled SVG is missing.
     */
    public static function menuIcon(): string
    {
        $path = self::getContainer()->get('BASE_PATH') . 'assets/images/menu-icon.svg';
        return is_readable($path) ? 'none' : 'dashicons-admin-links';
    }

    /**
     * Emit the CSS that paints the top-level menu icon. Attached to the
     * always-loaded `admin-menu` stylesheet so it ships on every admin
     * page (the sidebar is always visible).
     *
     * The mask sits on `:before` rather than the `.wp-menu-image` div
     * because WP's color-scheme CSS sets the per-state color on `:before`
     * — `currentColor` on the div would inherit the link text color
     * instead of the icon-specific color.
     */
    public static function enqueueMenuIconStyle(): void
    {
        $path = self::getContainer()->get('BASE_PATH') . 'assets/images/menu-icon.svg';
        if (!is_readable($path)) {
            return;
        }

        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- local bundled asset.
        $svg = (string) file_get_contents($path);
        // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- data-URI encoding.
        $uri = 'data:image/svg+xml;base64,' . base64_encode($svg);

        // WP's base CSS already sets `div.wp-menu-image:before { padding:7px 0 }`
        // which combined with a 20px-tall mask box gives the same 34px total
        // height (7+20+7) that the dashicons font icons use. Don't override
        // padding/margin/position — let WP own the layout.
        $css = sprintf(
            '#adminmenu .toplevel_page_%1$s div.wp-menu-image::before{'
            . 'content:"";display:block;width:20px;height:20px;margin:0 auto;'
            . 'background-color:currentColor;'
            . '-webkit-mask:url("%2$s") center/20px no-repeat;'
            . 'mask:url("%2$s") center/20px no-repeat;'
            . '}',
            self::SLUG,
            $uri
        );

        wp_add_inline_style('admin-menu', $css);
    }

    /**
     * Render the dashboard React mount points.
     *
     * @return void
     */
    public static function render(): void
    {
        echo '<div class="wrap">'
           . '<div id="prli-notices-app"></div>'
           . '<div id="prli-admin-root" data-page="dashboard"></div>'
           . '</div>';
    }

    /**
     * Reorder the Pretty Links submenu deterministically.
     *
     * WordPress's `$position` arg to `add_submenu_page()` is an insertion
     * index, not a sort key — and the resulting `$submenu[$parent]` array
     * gets reindexed (0..N) regardless of the positions passed in. That
     * means registration order wins, which makes cross-plugin ordering
     * fragile (especially once Pro adds pages at the same priority).
     *
     * Hook at admin_menu priority 999 — after every `register()` has run,
     * including third-party integrations like Growth Tools. Slugs not
     * listed in the map fall to the end, preserving their relative order.
     */
    public static function reorderSubmenu(): void
    {
        global $submenu;
        if (!isset($submenu[self::SLUG]) || !is_array($submenu[self::SLUG])) {
            return;
        }

        /**
         * Filter: prli_admin_submenu_order
         *
         * Map of submenu slug => rank (ascending). Lower ranks render first.
         * Pro hooks this to inject its pages (categories, tags, custom
         * reports, public links) between Lite's anchors. Unlisted slugs
         * (third-party additions to our parent menu) sort to the end.
         *
         * @param array<string,int> $order
         */
        $order = (array) apply_filters('prli_admin_submenu_order', [
            // Dashboard (the parent's own slug, registered as first submenu).
            self::SLUG                 => 0,
            'pretty-link-links'        => 10,
            'pretty-link-add-new'      => 20,
            'pretty-link-pay-links'    => 30,
            'pretty-link-clicks'       => 40,
            'pretty-link-options'      => 100,
            'pretty-link-addons'       => 110,
            'pretty-link-growth-tools' => 120,
        ]);

        usort(
            $submenu[self::SLUG],
            static function ($a, $b) use ($order): int {
                $aSlug = isset($a[2]) ? (string) $a[2] : '';
                $bSlug = isset($b[2]) ? (string) $b[2] : '';
                $aRank = $order[$aSlug] ?? PHP_INT_MAX;
                $bRank = $order[$bSlug] ?? PHP_INT_MAX;
                return $aRank <=> $bRank;
            }
        );
    }
}