Mojolicious-Plugin-Fondation-Menu

 view release on metacpan or  search on metacpan

lib/Mojolicious/Plugin/Fondation/Menu/Helpers.pm  view on Meta::CPAN

package Mojolicious::Plugin::Fondation::Menu::Helpers;
$Mojolicious::Plugin::Fondation::Menu::Helpers::VERSION = '0.04';
# ABSTRACT: Menu helpers — server-side cache, rendering, breadcrumb

use strict;
use warnings;

use Mojo::Base -strict, -signatures;

# ── Register all helpers ────────────────────────────────────────────────

sub register_all ($class, $app) {

    # ── _menus_cache ──────────────────────────────────────────────────
    # Returns the server-side cache hashref (lazy, shared across users)
    my $_menus_cache;
    my $_menus_loaded = 0;

    my $_get_cache = sub {
        return $_menus_cache if $_menus_loaded;
        return undef;
    };

    my $_invalidate_cache = sub {
        $_menus_loaded = 0;
        undef $_menus_cache;
    };

    my $_ensure_cache;

    # ── breadcrumb ─────────────────────────────────────────────────────
    $app->helper(breadcrumb => sub ($c) {
        $_ensure_cache->($c);
        my $menus = $_get_cache->();
        return [] unless $menus && $menus->{_by_path};

        my $path = $c->req->url->path->to_abs_string // '/';
        $path = "/$path" unless $path =~ m|^/|;

        return [] if $path eq '/';

        my $menu = $menus->{_by_path}{$path};
        unless ($menu) {
            $menu = _find_menu_by_path_prefix($path, $menus->{_by_path});
        }

        return [] unless $menu;

        my @trail = ($menu);
        while ($menu->{parent_id}) {
            $menu = $menus->{_by_id}{$menu->{parent_id}};
            last unless $menu;
            unshift @trail, $menu;
        }

        return \@trail;
    });

    # ── menu_by_name ────────────────────────────────────────────────────
    $app->helper(menu_by_name => sub ($c, $name) {
        $_ensure_cache->($c);
        my $menus = $_get_cache->();
        return [] unless $menus && $menus->{_by_name};
        return $menus->{_by_name}{$name} // [];
    });

    # ── menu_by_id ──────────────────────────────────────────────────────
    $app->helper(menu_by_id => sub ($c, $id) {
        my $menus = $_get_cache->();
        return undef unless $menus && $menus->{_by_id};
        return $menus->{_by_id}{$id};
    });

    # ── menus ───────────────────────────────────────────────────────────
    $app->helper(menus => sub ($c, $init = undef) {
        my $cache = $_get_cache->();
        return $cache if $cache && !$init;
        return _build_menus_cache($c, \$_menus_cache, \$_menus_loaded);
    });

    # ── render_menu ──────────────────────────────────────────────
    $app->helper(render_menu => sub ($c, $name) {
        my $menus = $c->menu_by_name($name);
        return '' unless @$menus;
        return $c->render_to_string(
            template => 'menu/navbar',
            menus    => $menus,
        );
    });

    # ── check_menu_condition ────────────────────────────────────────────
    $app->helper(check_menu_condition => sub ($c, $condition) {
        return 1 unless defined $condition && length $condition;

        # Support comma-separated compound conditions (AND logic)
        for my $cond (split /\s*,\s*/, $condition) {
            return 0 unless _check_single_menu_condition($c, $cond);
        }
        return 1;
    });

    # ── render_menu_breadcrumb ──────────────────────────────────────────
    $app->helper(render_menu_breadcrumb => sub ($c) {
        my $trail = $c->breadcrumb;
        return '' unless @$trail;
        return $c->render_to_string(
            template => 'menu/breadcrumb',
            trail    => $trail,
        );
    });

    # ── menu_cache_invalidate ───────────────────────────────────────────
    # Called by CRUD operations to clear the cache.
    $app->helper(menu_cache_invalidate => sub { $_invalidate_cache->() });

    # ── _ensure_cache ───────────────────────────────────────────────────
    # Lazy-init cache on first access (non-blocking, no hook needed).
    $_ensure_cache = sub ($c) {
        return if $_menus_loaded;
        _build_menus_cache($c, \$_menus_cache, \$_menus_loaded);
    };
}

# ══════════════════════════════════════════════════════════════════════════
# Internal
# ══════════════════════════════════════════════════════════════════════════

# Check a single menu condition (no comma splitting).
sub _check_single_menu_condition ($c, $cond) {
    if ($cond eq 'auth') {
        return $c->has_helper('is_user_authenticated')
            ? $c->is_user_authenticated : 0;
    }
    if ($cond eq '!auth') {
        return $c->has_helper('is_user_authenticated')
            ? !$c->is_user_authenticated : 1;
    }
    if ($cond =~ /^group:(.+)$/) {
        return $c->check_group($1);
    }
    if ($cond =~ /^perm:(.+)$/) {
        return $c->check_perm($1);
    }
    if ($cond =~ /^mode:!(.+)$/) {
        return $c->app->mode ne $1;
    }
    if ($cond =~ /^mode:(.+)$/) {
        return $c->app->mode eq $1;
    }
    $c->app->log->warn("Unknown menu condition format: $cond");
    return 0;
}

sub _build_menus_cache ($c, $cache_ref, $loaded_ref) {
    my $schema = $c->schema;
    return undef unless $schema;

    my $results = $schema->await(
        $schema->resultset('Menu')->search(
            undef,
            { order_by => { -asc => 'sort_order' } },
        )->all
    );

    my @plain;
    for my $m (@$results) {
        push @plain, {
            id          => $m->id,
            title       => $m->title,
            link        => $m->link,
            icon        => $m->icon,
            icon_color  => $m->icon_color,
            name        => $m->name,
            condition   => $m->condition,



( run in 0.638 second using v1.01-cache-2.11-cpan-ad19def0cd9 )