AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Je Dynamic Visibility Condition

skill-lonsdale201-wp-agent-skills-je-dynamic-visibility-condition · by Lonsdale201

Register a custom Dynamic Visibility condition for

No reviews yet
0 installs
37 views
0.0% view→install

Install

$ agentstack add skill-lonsdale201-wp-agent-skills-je-dynamic-visibility-condition

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-lonsdale201-wp-agent-skills-je-dynamic-visibility-condition)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
2mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Je Dynamic Visibility Condition? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

JetEngine: register a Dynamic Visibility condition

For developers extending JetEngine's Dynamic Visibility module with custom conditions — "is current page the front page", "is the user past N days since registration", "has the visitor purchased a specific product", "is the LearnDash course completed". The module already ships ~40 built-in conditions; this skill is for the custom-condition extension contract that's been stable across JetEngine 3.x.

Misconception this skill corrects

> "I'll add a pre_get_posts filter or a custom shortcode to gate the visibility — same outcome."

Different layer. JetEngine's Dynamic Visibility runs the condition check INSIDE the rendering pipeline of every JE-aware widget/block (Elementor widgets, Gutenberg blocks, Bricks elements, listing items). A custom condition slots into the SAME UI as the built-in ones — site editors pick it from a dropdown, configure it, and it applies wherever JE Dynamic Visibility is enabled.

pre_get_posts only filters main queries; doesn't help with widget-level visibility. Shortcodes don't get the visibility-condition UI. The verified extension contract is:

add_action( 'jet-engine/modules/dynamic-visibility/conditions/register', function ( $manager ) {
    $manager->register_condition( new MyCondition() );
} );

Where MyCondition extends \Jet_Engine\Modules\Dynamic_Visibility\Conditions\Base. Verified at [wp-content/plugins/jet-engine/includes/modules/dynamic-visibility/inc/conditions/base.php:4](base.php).

Other AI-prone misconceptions:

  • "check( $args ) returns true when the visitor SHOULD see the content." Half-true. The semantics is "should the widget be displayed". $args['type'] carries 'show' (user wants to show when condition is true) or 'hide' (user wants to hide when condition is true). Your check() MUST invert the result based on $args['type'] — see the Equal built-in at [conditions/equal.php:37-41](equal.php).
  • "is_for_fields() controls whether the condition is shown in the UI." Wrong — it controls whether the condition is available in the meta-field context (where the user is comparing meta values). Conditions like "is front page" don't compare values, so they return is_for_fields() => false.
  • "I should hardcode get_post_meta( get_the_ID(), ... ) to read meta." Don't — use $this->get_current_value( $args ) from the Base class. It handles current_listing context automatically (WPPost / WPUser / WPTerm / WPComment / listing-macros).

When to use this skill

Trigger when ANY of the following is true:

  • The diff calls register_condition, hooks jet-engine/modules/dynamic-visibility/conditions/register, or extends \Jet_Engine\Modules\Dynamic_Visibility\Conditions\Base.
  • The user asks "how do I add a custom Dynamic Visibility condition for JetEngine".
  • A companion plugin needs role-aware / membership-aware / LMS-progress-aware visibility logic.
  • Reviewing PR code that adds a condition class.

The Base API surface (verified)

All methods live on \Jet_Engine\Modules\Dynamic_Visibility\Conditions\Base ([base.php:4-212](base.php)):

| Method | Default | Purpose | |---|---|---| | get_id() | abstract | Unique slug — used as the form-control key. Pick a stable slug; renaming breaks saved configurations. | | get_name() | abstract | Display label in the editor dropdown. Translatable. | | check( $args = [] ) | abstract | The actual gate. Return true if the widget should display, false to hide. Honor $args['type']. | | get_group() | false | Optional UI group label. false lands the condition in "Other"; otherwise groups conditions visually. | | is_for_fields() | true | Whether the condition appears in the meta-field value-compare UI. Set false for context-only conditions. | | need_value_detect() | true | Whether the UI prompts for a "value" input. Set false for binary "is X" conditions. | | need_type_detect() | false | Whether the UI prompts for a "datatype" (chars/numeric/datetime/date/list). Set true if your condition does numeric/date comparison. | | get_current_value( $args ) | helper | Reads the current value from listing context (post / user / term / comment / macro). Use INSTEAD of raw get_post_meta. | | checkboxes_to_array( $array ) | helper | Convert JE-checkbox {value: bool} to plain list. | | adjust_values_type( $current, $compare, $data_type ) | helper | Cast both sides for comparison per datatype. | | explode_string( $value ) | helper | CSV → array, trims values. | | get_custom_controls() | false | Optional — return an array of UI controls (like Elementor controls) to add per-condition fields. |

The $args shape (verified)

When check( $args ) runs, $args contains:

[
    'type'      => 'show' | 'hide',          // user's intent — invert your boolean here
    'condition' => 'my-condition-id',         // your get_id() value
    'field'     => 'meta_value_or_macro',     // user-entered, possibly with %macros%
    'field_raw' => 'meta_key',                // raw meta key without macro processing
    'operator'  => 'equal' | 'not-equal' | …, // (for value-compare conditions)
    'value'     => 'value_to_compare',        // user-configured comparison value
    'data_type' => 'chars' | 'numeric' | 'datetime' | 'date' | 'list',
    'context'   => 'default' | 'current_listing',
    // ... plus any keys your get_custom_controls() declared
]

Not all keys are present in every call — depends on the condition's is_for_fields() / need_value_detect() / need_type_detect() flags + get_custom_controls().

Workflow

1. Bootstrap your companion plugin

add_action( 'plugins_loaded', static function (): void {
    if ( ! class_exists( '\Jet_Engine' ) ) {
        return;   // JetEngine not active
    }

    add_action(
        'jet-engine/modules/dynamic-visibility/conditions/register',
        [ \MyPlugin\Visibility\Manager::class, 'register' ]
    );
}, 11 );

Priority 11 so JE's own plugins_loaded:10 has run, making the Conditions\Base class loadable.

Important — do NOT call jet_engine()->modules->is_module_active( 'dynamic-visibility' ) inside this guard. jet_engine()->modules is null until JE's init() callback runs at init priority -999 (verified at [jet-engine.php:164,345](jet-engine.php)) — long after plugins_loaded. A pre-init module-active check fatal-errors with "Call to a member function ismoduleactive() on null". You don't need the gate anyway: the jet-engine/modules/dynamic-visibility/conditions/register action is fired from inside the visibility module's own bootstrap ([conditions/manager.php:77](manager.php)) — if the module is disabled, the action never fires and your callback never runs.

1b. When you DO need to check the module is active

The condition-registration action handles the gating for you, so a module-active check is unnecessary for that path. But there are legitimate uses for is_module_active() — admin notices ("Dynamic Visibility module is off, your conditions won't appear"), conditional asset enqueuing, REST endpoints that depend on visibility state, second registrations on a sibling module. For those, run the check AFTER JE's modules manager exists.

// CORRECT — check on jet-engine/init, fired right after JE finishes its init()
add_action( 'jet-engine/init', static function ( $jet_engine ): void {
    if ( ! $jet_engine->modules->is_module_active( 'dynamic-visibility' ) ) {
        // Module is disabled — surface a notice, skip your asset enqueue, etc.
        add_action( 'admin_notices', static function (): void {
            echo ''
               . esc_html__( 'MyPlugin: enable JetEngine Dynamic Visibility to use the conditions.', 'myplugin' )
               . '';
        } );
        return;
    }

    // Module is active — do whatever needs the module to be on
    MyPlugin\Visibility\AssetLoader::register();
} );

// ALSO CORRECT — same effect via init:-998 (one step after JE's init:-999)
add_action( 'init', static function (): void {
    if ( ! function_exists( 'jet_engine' ) || ! jet_engine()->modules ) {
        return; // JE not loaded at all
    }
    if ( ! jet_engine()->modules->is_module_active( 'dynamic-visibility' ) ) {
        return;
    }
    // ... module-dependent setup
}, -998 );

The jet-engine/init action is the cleanest because the JE instance is passed in and you don't need defensive function_exists() checks. It is fired at [jet-engine.php:375](jet-engine.php).

// WRONG — calling on plugins_loaded (any priority) fatal-errors
add_action( 'plugins_loaded', static function (): void {
    if ( jet_engine()->modules->is_module_active( 'dynamic-visibility' ) ) {  // 🔴 modules is null here
        // ...
    }
}, 11 );

2. Minimal context condition — "Is Front Page"

namespace MyPlugin\Visibility;

use Jet_Engine\Modules\Dynamic_Visibility\Conditions\Base;

class IsFrontPage extends Base {

    public function get_id(): string {
        return 'myplugin-is-front-page';   // namespace-prefix the slug to avoid collisions
    }

    public function get_name(): string {
        return __( 'Is Front Page', 'myplugin' );
    }

    public function get_group(): string {
        return __( 'Page Context', 'myplugin' );   // groups multiple related conditions in the UI
    }

    /**
     * No meta-field comparison — this is a binary context check.
     */
    public function is_for_fields(): bool {
        return false;
    }

    /**
     * No "value" input in the UI — the condition is a yes/no gate.
     */
    public function need_value_detect(): bool {
        return false;
    }

    public function check( $args = [] ): bool {
        $is_front_page = ( (int) get_option( 'page_on_front' ) === (int) get_the_ID() );

        // Honor the user's show/hide intent
        $type = $args['type'] ?? 'show';

        return ( 'hide' === $type ) ? ! $is_front_page : $is_front_page;
    }
}

3. Meta-comparison condition — read listing context correctly

class HighRatedProducts extends Base {

    public function get_id(): string {
        return 'myplugin-high-rated';
    }

    public function get_name(): string {
        return __( 'Product Rating ≥ N', 'myplugin' );
    }

    public function need_type_detect(): bool {
        return true;   // user picks numeric / chars / etc. — we use numeric here
    }

    public function check( $args = [] ): bool {
        // get_current_value() handles the listing context for us:
        // - WP_Post (incl. WC_Product) → get_post_meta
        // - WP_User                     → get_user_meta
        // - WP_Term                     → get_term_meta
        // - WP_Comment                  → get_comment_meta
        // - macros (%dynamic_value%)    → resolved via macros engine
        $current = (float) $this->get_current_value( $args );
        $threshold = (float) ( $args['value'] ?? 0 );

        $type = $args['type'] ?? 'show';
        $matches = $current >= $threshold;

        return ( 'hide' === $type ) ? ! $matches : $matches;
    }
}

4. Custom UI controls — get_custom_controls()

For conditions that need bespoke inputs beyond the default field/value pair (like "select days of week", "select user roles", repeater of items):

class WeekDays extends Base {

    public function get_id(): string {
        return 'myplugin-week-days';
    }

    public function get_name(): string {
        return __( 'Day of Week', 'myplugin' );
    }

    public function is_for_fields(): bool {
        return false;   // not a meta-comparison
    }

    public function need_value_detect(): bool {
        return false;   // value input is custom, declared below
    }

    public function get_custom_controls(): array {
        global $wp_locale;
        return [
            'week_days' => [
                'label'    => __( 'Days of Week', 'myplugin' ),
                'type'     => 'select2',
                'multiple' => true,
                'default'  => [],
                'options'  => $wp_locale->weekday,   // [0 => 'Sunday', 1 => 'Monday', ...]
            ],
        ];
    }

    public function check( $args = [] ): bool {
        $allowed = $args['week_days'] ?? [];   // matches the key from get_custom_controls
        $today = (int) current_time( 'w' );

        $matches = in_array( $today, array_map( 'intval', $allowed ), true );
        $type = $args['type'] ?? 'show';

        return ( 'hide' === $type ) ? ! $matches : $matches;
    }
}

The control type field accepts the same values JE uses elsewhere: text, number, select, select2, checkbox, media, date, time, datetime. Verified usage in WeekDays, TimePeriod, ListingOdd built-ins.

5. Register all conditions

namespace MyPlugin\Visibility;

class Manager {
    public static function register( $manager ): void {
        $manager->register_condition( new IsFrontPage() );
        $manager->register_condition( new HighRatedProducts() );
        $manager->register_condition( new WeekDays() );

        // Conditional — only if dependency plugin is active
        if ( class_exists( '\WooCommerce' ) ) {
            $manager->register_condition( new \MyPlugin\Visibility\Wc\PurchasedProducts() );
        }
        if ( function_exists( 'wc_memberships' ) ) {
            $manager->register_condition( new \MyPlugin\Visibility\Wc\MemberAccess() );
        }
    }
}

Conditional registration based on third-party plugin presence is the canonical pattern — verified in the existing dynamic-elementor-extension plugin's VisibilityManager::register_conditions().

6. Listing context awareness

When the visibility check runs INSIDE a JE listing (a card in a listing grid, for example), $args['context'] is 'current_listing' and get_current_value() reads from the LISTING's current object — NOT the parent page. The Base helper handles this:

// Inside check( $args ):
$value = $this->get_current_value( $args );

// For 'current_listing' context, this resolves to:
// - if listing iterates WP_Post → get_post_meta( $current_post_id, $field_raw, true )
// - if listing iterates WP_User → get_user_meta( $current_user_id, $field_raw, true )
// - …etc.

// Don't manually re-read with get_post_meta( get_the_ID() ) — get_the_ID() in
// listing context returns the LISTING'S parent page, not the iterated item.

Verified at [base.php:69-120](base.php) — the resolver dispatches by get_class( $object ).

7. Group conditions in the UI

public function get_group(): string {
    return __( 'My Plugin', 'myplugin' );
}

All conditions returning the same string land in a labeled section in the editor's condition dropdown. Useful for plugins shipping multiple conditions — keeps them visually clustered. Without get_group(), the condition lands in the default "Other" bucket.

8. Checkbox values — convert before use

JE checkboxes return { value1: true, value2: false } shape (so the user can keep all keys but only some are "on"). Convert to a plain list:

$selected = $this->checkboxes_to_array( $args['my_checkbox_field'] ?? [] );
// Now $selected is ['value1', 'value3', ...]

Critical rules

  • get_id() is stable. Renaming breaks saved visibility configurations on every site that uses the condition.
  • Namespace-prefix the slug (myplugin-is-front-page not is-front-page) to avoid collisions with built-ins or other companion plugins.
  • check() MUST honor $args['type']. Invert the boolean when type === 'hide'. Otherwise the user's "hide if true" UI choice does the opposite of what they expect.
  • Use $this->get_current_value( $args ) for meta reads — handles the listing context automatically. Don't get_post_meta( get_the_ID(), ... ) directly.
  • is_for_fields() => false when the condition isn't comparing a meta field value. Cleaner UI for the editor.

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.