HEX
Server: Apache/2.4.58 (Ubuntu)
System: Linux bsx-1-dev 6.8.0-101-generic #101-Ubuntu SMP PREEMPT_DYNAMIC Mon Feb 9 10:15:05 UTC 2026 x86_64
User: www-data (33)
PHP: 8.3.6
Disabled: NONE
Upload Files
File: /var/www/html/wp-content/plugins/memberpress/app/helpers/MeprPostSetupChecklistHelper.php
<?php

if (!defined('ABSPATH')) {
    die('You are not allowed to call this page directly.');
}

/**
 * Helper class for the Post-Setup Checklist feature.
 *
 * Provides methods to detect progress, calculate completion percentages,
 * and manage checklist state.
 */
class MeprPostSetupChecklistHelper
{
    /**
     * Option key for storing checklist dismissed state.
     *
     * @var string
     */
    public static $dismissed_option = 'mepr_post_setup_checklist_dismissed';

    /**
     * Option key for storing checklist minimized state.
     *
     * @var string
     */
    public static $minimized_option = 'mepr_post_setup_checklist_minimized';

    /**
     * Option key for storing manually completed steps.
     *
     * @var string
     */
    public static $completed_steps_option = 'mepr_post_setup_checklist_completed';

    /**
     * Option key for storing skipped steps.
     *
     * @var string
     */
    public static $skipped_steps_option = 'mepr_post_setup_checklist_skipped';

    /**
     * Get all checklist steps with their current status.
     *
     * @return array Array of step data including id, title, description, completed status, and action URL.
     */
    public static function get_steps()
    {
        $skipped_steps = self::get_skipped_steps();

        $steps = [
            [
                'id'          => 'install_plugin',
                'title'       => __('Install Plugin', 'memberpress'),
                'description' => __('MemberPress plugin is installed and active.', 'memberpress'),
                'completed'   => true, // Always completed since checklist requires MemberPress to be installed.
                'skipped'     => false,
                'skippable'   => false,
                'action_url'  => '',
                'action_text' => '',
                'priority'    => 1,
            ],
            [
                'id'          => 'activate_license',
                'title'       => __('Activate license key', 'memberpress'),
                'description' => __('Enter your license key to enable updates and support.', 'memberpress'),
                'completed'   => MeprUpdateCtrl::is_activated(),
                'skipped'     => in_array('activate_license', $skipped_steps, true),
                'skippable'   => true,
                'action_url'  => admin_url('admin.php?page=memberpress-options#license'),
                'action_text' => __('Activate License', 'memberpress'),
                'priority'    => 2,
            ],
            [
                'id'          => 'connect_payment_gateway',
                'title'       => __('Connect live payment gateway', 'memberpress'),
                'description' => __('Set up Stripe or PayPal to start accepting payments.', 'memberpress'),
                'completed'   => self::is_payment_gateway_connected(),
                'skipped'     => in_array('connect_payment_gateway', $skipped_steps, true),
                'skippable'   => true,
                'action_url'  => admin_url('admin.php?page=memberpress-options#integration'),
                'action_text' => __('Connect Gateway', 'memberpress'),
                'priority'    => 3,
            ],
            [
                'id'          => 'create_membership',
                'title'       => __('Create your first Membership', 'memberpress'),
                'description' => __('Create a membership product for your customers to purchase.', 'memberpress'),
                'completed'   => self::has_membership(),
                'skipped'     => in_array('create_membership', $skipped_steps, true),
                'skippable'   => false,
                'action_url'  => admin_url('post-new.php?post_type=memberpressproduct'),
                'action_text' => __('Create Membership', 'memberpress'),
                'priority'    => 4,
            ],
            [
                'id'          => 'create_rule',
                'title'       => __('Create your first Rule', 'memberpress'),
                'description' => __('Protect your content by setting up access rules.', 'memberpress'),
                'completed'   => self::has_rule(),
                'skipped'     => in_array('create_rule', $skipped_steps, true),
                'skippable'   => true,
                'action_url'  => admin_url('post-new.php?post_type=memberpressrule'),
                'action_text' => __('Create Rule', 'memberpress'),
                'priority'    => 5,
            ],
            [
                'id'          => 'schedule_reminder',
                'title'       => __('Schedule a Reminder', 'memberpress'),
                'description' => __('Set up automated email reminders for your members.', 'memberpress'),
                'completed'   => self::has_reminder(),
                'skipped'     => in_array('schedule_reminder', $skipped_steps, true),
                'skippable'   => true,
                'action_url'  => admin_url('post-new.php?post_type=mp-reminder'),
                'action_text' => __('Create Reminder', 'memberpress'),
                'priority'    => 6,
            ],
            [
                'id'          => 'first_sale',
                'title'       => __('Make your first sale', 'memberpress'),
                'description' => __('Complete a transaction through your live payment gateway.', 'memberpress'),
                'completed'   => self::has_transaction(),
                'skipped'     => in_array('first_sale', $skipped_steps, true),
                'skippable'   => true,
                'action_url'  => '',
                'action_text' => '',
                'priority'    => 7,
            ],
        ];

        return MeprHooks::apply_filters('mepr_post_setup_checklist_steps', $steps);
    }

    /**
     * Get optional checklist steps.
     *
     * These steps don't count toward progress and can be completed in any order.
     *
     * @return array Array of optional step data.
     */
    public static function get_optional_steps()
    {
        $steps = [
            [
                'id'          => 'create_coupon',
                'title'       => __('Create a coupon', 'memberpress'),
                'description' => __('Create a discount coupon for promotions.', 'memberpress'),
                'completed'   => self::has_coupon(),
                'action_url'  => admin_url('post-new.php?post_type=memberpresscoupon'),
                'action_text' => __('Create Coupon', 'memberpress'),
            ],
            [
                'id'           => 'create_course',
                'title'        => __('Create a course', 'memberpress'),
                'description'  => __('Build your first course with MemberPress Courses.', 'memberpress'),
                'completed'    => self::has_course(),
                'action_url'   => self::get_courses_action_url(),
                'action_text'  => self::get_courses_action_text(),
                'requires'     => 'memberpress-courses',
                'installed'    => self::is_courses_addon_installed(),
                'active'       => self::is_courses_addon_active(),
                'installable'  => self::is_addon_installable('memberpress-courses'),
                'plugin_file'  => 'memberpress-courses/main.php',
                'download_url' => self::get_addon_download_url('memberpress-courses'),
                'addon_type'   => 'addon',
            ],
            [
                'id'           => 'setup_directory',
                'title'        => __('Set up ClubDirectory™', 'memberpress'),
                'description'  => __('Create a member directory for your community.', 'memberpress'),
                'completed'    => self::has_directory(),
                'action_url'   => self::get_directory_action_url(),
                'action_text'  => self::get_directory_action_text(),
                'requires'     => 'memberpress-directory',
                'installed'    => self::is_directory_addon_installed(),
                'active'       => self::is_directory_addon_active(),
                'installable'  => self::is_addon_installable('memberpress-directory'),
                'plugin_file'  => 'memberpress-directory/main.php',
                'download_url' => self::get_addon_download_url('memberpress-directory'),
                'addon_type'   => 'addon',
            ],
            [
                'id'           => 'setup_circles',
                'title'        => __('Set up ClubCircles™', 'memberpress'),
                'description'  => __('Create private community spaces for your members.', 'memberpress'),
                'completed'    => self::has_circle(),
                'action_url'   => self::get_circles_action_url(),
                'action_text'  => self::get_circles_action_text(),
                'requires'     => 'memberpress-circles',
                'installed'    => self::is_circles_addon_installed(),
                'active'       => self::is_circles_addon_active(),
                'installable'  => self::is_addon_installable('memberpress-circles'),
                'plugin_file'  => 'memberpress-circles/main.php',
                'download_url' => self::get_addon_download_url('memberpress-circles'),
                'addon_type'   => 'addon',
            ],
            [
                'id'           => 'setup_downloads',
                'title'        => __('Set up Digital Downloads', 'memberpress'),
                'description'  => __('Protect and deliver downloadable files to your members.', 'memberpress'),
                'completed'    => self::has_download(),
                'action_url'   => self::get_downloads_action_url(),
                'action_text'  => self::get_downloads_action_text(),
                'requires'     => 'memberpress-downloads',
                'installed'    => self::is_downloads_addon_installed(),
                'active'       => self::is_downloads_addon_active(),
                'installable'  => self::is_addon_installable('memberpress-downloads'),
                'plugin_file'  => 'memberpress-downloads/main.php',
                'download_url' => self::get_addon_download_url('memberpress-downloads'),
                'addon_type'   => 'addon',
            ],
            [
                'id'           => 'configure_email',
                'title'        => __('Configure Email Deliverability', 'memberpress'),
                'description'  => __('Set up SMTP to ensure emails reach your members.', 'memberpress'),
                'completed'    => self::is_smtp_configured(),
                'action_url'   => self::get_smtp_action_url(),
                'action_text'  => self::get_smtp_action_text(),
                'requires'     => 'wp-mail-smtp',
                'installed'    => self::is_wp_mail_smtp_installed(),
                'active'       => self::is_wp_mail_smtp_active(),
                'plugin_file'  => 'wp-mail-smtp/wp_mail_smtp.php',
                'download_url' => 'https://downloads.wordpress.org/plugin/wp-mail-smtp.latest-stable.zip',
                'addon_type'   => 'plugin',
            ],
            [
                'id'           => 'install_aioseo',
                'title'        => __('Set up SEO', 'memberpress'),
                'description'  => __('Optimize your site for search engines with All in One SEO.', 'memberpress'),
                'completed'    => self::is_aioseo_active(),
                'action_url'   => self::get_aioseo_action_url(),
                'action_text'  => self::get_aioseo_action_text(),
                'requires'     => 'all-in-one-seo-pack',
                'installed'    => self::is_aioseo_installed(),
                'active'       => self::is_aioseo_active(),
                'plugin_file'  => 'all-in-one-seo-pack/all_in_one_seo_pack.php',
                'download_url' => 'https://downloads.wordpress.org/plugin/all-in-one-seo-pack.latest-stable.zip',
                'addon_type'   => 'plugin',
            ],
            [
                'id'           => 'install_optinmonster',
                'title'        => __('Grow your email list', 'memberpress'),
                'description'  => __('Convert visitors into subscribers with OptinMonster.', 'memberpress'),
                'completed'    => self::is_optinmonster_active(),
                'action_url'   => self::get_optinmonster_action_url(),
                'action_text'  => self::get_optinmonster_action_text(),
                'requires'     => 'optinmonster',
                'installed'    => self::is_optinmonster_installed(),
                'active'       => self::is_optinmonster_active(),
                'plugin_file'  => 'optinmonster/optin-monster-wp-api.php',
                'download_url' => 'https://downloads.wordpress.org/plugin/optinmonster.latest-stable.zip',
                'addon_type'   => 'plugin',
            ],
        ];

        return MeprHooks::apply_filters('mepr_post_setup_checklist_optional_steps', $steps);
    }

    /**
     * Check if at least one coupon exists.
     *
     * @return boolean True if a coupon exists.
     */
    public static function has_coupon()
    {
        $counts = wp_count_posts(MeprCoupon::$cpt);

        return (int) ($counts->publish ?? 0) > 0;
    }

    /**
     * Check if the MemberPress Courses addon is installed.
     *
     * @return boolean True if installed.
     */
    public static function is_courses_addon_installed()
    {
        return is_dir(WP_PLUGIN_DIR . '/memberpress-courses');
    }

    /**
     * Check if the MemberPress Courses addon is active.
     *
     * @return boolean True if active.
     */
    public static function is_courses_addon_active()
    {
        return MeprUtils::is_addon_active(MeprUtils::ADDON_COURSES);
    }

    /**
     * Check if at least one course exists.
     *
     * @return boolean True if a course exists.
     */
    public static function has_course()
    {
        if (!self::is_courses_addon_active()) {
            return false;
        }

        // Check if the course post type exists and has posts.
        if (!post_type_exists('mpcs-course')) {
            return false;
        }

        $counts = wp_count_posts('mpcs-course');

        return (int) ($counts->publish ?? 0) > 0;
    }

    /**
     * Get the action URL for courses step.
     *
     * @return string Action URL.
     */
    public static function get_courses_action_url()
    {
        if (self::is_courses_addon_active()) {
            return admin_url('post-new.php?post_type=mpcs-course');
        }

        return admin_url('admin.php?page=memberpress-addons');
    }

    /**
     * Get the action text for courses step.
     *
     * @return string Action text.
     */
    public static function get_courses_action_text()
    {
        if (self::is_courses_addon_active()) {
            return __('Create Course', 'memberpress');
        }

        if (self::is_courses_addon_installed()) {
            return __('Activate Add-on', 'memberpress');
        }

        return __('Install Add-on', 'memberpress');
    }

    /**
     * Get the download URL for MemberPress Courses addon.
     *
     * @return string Download URL or empty string if not available.
     */
    public static function get_courses_download_url()
    {
        return self::get_addon_download_url('memberpress-courses');
    }

    /**
     * Check if an addon is installable based on user's license plan.
     *
     * @param string $addon_slug The addon slug.
     *
     * @return boolean True if installable.
     */
    public static function is_addon_installable($addon_slug)
    {
        $addons = MeprUpdateCtrl::addons(true, false, true);

        if (!empty($addons) && isset($addons->{$addon_slug})) {
            return !empty($addons->{$addon_slug}->installable);
        }

        return false;
    }

    /**
     * Get the download URL for an addon.
     *
     * @param string $addon_slug The addon slug.
     *
     * @return string Download URL or empty string if not available.
     */
    public static function get_addon_download_url($addon_slug)
    {
        $addons = MeprUpdateCtrl::addons(true, false, true);

        if (!empty($addons) && isset($addons->{$addon_slug})) {
            return $addons->{$addon_slug}->url ?? '';
        }

        return '';
    }

    /**
     * Check if the MemberPress Directory addon is installed.
     *
     * @return boolean True if installed.
     */
    public static function is_directory_addon_installed()
    {
        return is_dir(WP_PLUGIN_DIR . '/memberpress-directory');
    }

    /**
     * Check if the MemberPress Directory addon is active.
     *
     * @return boolean True if active.
     */
    public static function is_directory_addon_active()
    {
        return is_plugin_active('memberpress-directory/main.php');
    }

    /**
     * Check if at least one directory exists.
     *
     * @return boolean True if a directory exists.
     */
    public static function has_directory()
    {
        if (!self::is_directory_addon_active()) {
            return false;
        }

        if (!post_type_exists('mcdir-directory')) {
            return false;
        }

        $counts = wp_count_posts('mcdir-directory');

        return (int) ($counts->publish ?? 0) > 0;
    }

    /**
     * Get the action URL for directory step.
     *
     * @return string Action URL.
     */
    public static function get_directory_action_url()
    {
        if (self::is_directory_addon_active()) {
            return admin_url('edit.php?post_type=mcdir-directory');
        }

        if (!self::is_addon_installable('memberpress-directory')) {
            return MeprUtils::get_link_url('login_redirect_pricing');
        }

        return admin_url('admin.php?page=memberpress-addons');
    }

    /**
     * Get the action text for directory step.
     *
     * @return string Action text.
     */
    public static function get_directory_action_text()
    {
        if (self::is_directory_addon_active()) {
            return __('Manage Directory', 'memberpress');
        }

        if (self::is_directory_addon_installed()) {
            return __('Activate Add-on', 'memberpress');
        }

        if (!self::is_addon_installable('memberpress-directory')) {
            return __('Upgrade Plan', 'memberpress');
        }

        return __('Install Add-on', 'memberpress');
    }

    /**
     * Check if the MemberPress Circles addon is installed.
     *
     * @return boolean True if installed.
     */
    public static function is_circles_addon_installed()
    {
        return is_dir(WP_PLUGIN_DIR . '/memberpress-circles');
    }

    /**
     * Check if the MemberPress Circles addon is active.
     *
     * @return boolean True if active.
     */
    public static function is_circles_addon_active()
    {
        return is_plugin_active('memberpress-circles/main.php');
    }

    /**
     * Check if at least one circle exists.
     *
     * @return boolean True if a circle exists.
     */
    public static function has_circle()
    {
        if (!self::is_circles_addon_active()) {
            return false;
        }

        if (!post_type_exists('mp-circle')) {
            return false;
        }

        $counts = wp_count_posts('mp-circle');

        return (int) ($counts->publish ?? 0) > 0;
    }

    /**
     * Get the action URL for circles step.
     *
     * @return string Action URL.
     */
    public static function get_circles_action_url()
    {
        if (self::is_circles_addon_active()) {
            return admin_url('edit.php?post_type=mp-circle');
        }

        if (!self::is_addon_installable('memberpress-circles')) {
            return MeprUtils::get_link_url('login_redirect_pricing');
        }

        // Circles requires Directory - redirect to addons page.
        return admin_url('admin.php?page=memberpress-addons');
    }

    /**
     * Get the action text for circles step.
     *
     * @return string Action text.
     */
    public static function get_circles_action_text()
    {
        if (self::is_circles_addon_active()) {
            return __('Manage Circles', 'memberpress');
        }

        if (self::is_circles_addon_installed()) {
            return __('Activate Add-on', 'memberpress');
        }

        if (!self::is_addon_installable('memberpress-circles')) {
            return __('Upgrade Plan', 'memberpress');
        }

        return __('Install Add-on', 'memberpress');
    }

    /**
     * Check if the MemberPress Downloads addon is installed.
     *
     * @return boolean True if installed.
     */
    public static function is_downloads_addon_installed()
    {
        return is_dir(WP_PLUGIN_DIR . '/memberpress-downloads');
    }

    /**
     * Check if the MemberPress Downloads addon is active.
     *
     * @return boolean True if active.
     */
    public static function is_downloads_addon_active()
    {
        return is_plugin_active('memberpress-downloads/main.php');
    }

    /**
     * Check if at least one download file exists.
     *
     * @return boolean True if a download file exists.
     */
    public static function has_download()
    {
        if (!self::is_downloads_addon_active()) {
            return false;
        }

        if (!post_type_exists('mcdl-file')) {
            return false;
        }

        $counts = wp_count_posts('mcdl-file');

        return (int) ($counts->publish ?? 0) > 0;
    }

    /**
     * Get the action URL for downloads step.
     *
     * @return string Action URL.
     */
    public static function get_downloads_action_url()
    {
        if (self::is_downloads_addon_active()) {
            return admin_url('edit.php?post_type=mcdl-file');
        }

        if (!self::is_addon_installable('memberpress-downloads')) {
            return MeprUtils::get_link_url('login_redirect_pricing');
        }

        return admin_url('admin.php?page=memberpress-addons');
    }

    /**
     * Get the action text for downloads step.
     *
     * @return string Action text.
     */
    public static function get_downloads_action_text()
    {
        if (self::is_downloads_addon_active()) {
            return __('Manage Downloads', 'memberpress');
        }

        if (self::is_downloads_addon_installed()) {
            return __('Activate Add-on', 'memberpress');
        }

        if (!self::is_addon_installable('memberpress-downloads')) {
            return __('Upgrade Plan', 'memberpress');
        }

        return __('Install Add-on', 'memberpress');
    }

    /**
     * Check if WP Mail SMTP plugin is installed.
     *
     * @return boolean True if installed.
     */
    public static function is_wp_mail_smtp_installed()
    {
        return is_dir(WP_PLUGIN_DIR . '/wp-mail-smtp');
    }

    /**
     * Check if WP Mail SMTP plugin is active.
     *
     * @return boolean True if active.
     */
    public static function is_wp_mail_smtp_active()
    {
        return is_plugin_active('wp-mail-smtp/wp_mail_smtp.php');
    }

    /**
     * Check if SMTP is configured (WP Mail SMTP is active and configured).
     *
     * @return boolean True if SMTP is configured.
     */
    public static function is_smtp_configured()
    {
        if (!self::is_wp_mail_smtp_active()) {
            return false;
        }

        // Check if WP Mail SMTP has a mailer configured (not default PHP mail).
        $wp_mail_smtp_options = get_option('wp_mail_smtp', []);

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

        $mailer = $wp_mail_smtp_options['mail']['mailer'] ?? 'mail';

        // 'mail' is the default PHP mail, consider configured if using any other mailer.
        return $mailer !== 'mail';
    }

    /**
     * Get the action URL for email deliverability step.
     *
     * @return string Action URL.
     */
    public static function get_smtp_action_url()
    {
        if (self::is_wp_mail_smtp_active()) {
            return admin_url('admin.php?page=wp-mail-smtp');
        }

        // Open WP Mail SMTP plugin details modal within WP admin.
        return admin_url('plugin-install.php?tab=plugin-information&plugin=wp-mail-smtp&TB_iframe=true');
    }

    /**
     * Get the action text for email deliverability step.
     *
     * @return string Action text.
     */
    public static function get_smtp_action_text()
    {
        if (self::is_wp_mail_smtp_active()) {
            return __('Configure SMTP', 'memberpress');
        }

        if (self::is_wp_mail_smtp_installed()) {
            return __('Activate Plugin', 'memberpress');
        }

        return __('Install Plugin', 'memberpress');
    }

    /**
     * Check if All in One SEO plugin is installed.
     *
     * @return boolean True if installed.
     */
    public static function is_aioseo_installed()
    {
        return is_dir(WP_PLUGIN_DIR . '/all-in-one-seo-pack');
    }

    /**
     * Check if All in One SEO plugin is active.
     *
     * @return boolean True if active.
     */
    public static function is_aioseo_active()
    {
        return is_plugin_active('all-in-one-seo-pack/all_in_one_seo_pack.php');
    }

    /**
     * Get the action URL for All in One SEO step.
     *
     * @return string Action URL.
     */
    public static function get_aioseo_action_url()
    {
        if (self::is_aioseo_active()) {
            return admin_url('admin.php?page=aioseo');
        }

        // Open AIOSEO plugin details modal within WP admin.
        return admin_url('plugin-install.php?tab=plugin-information&plugin=all-in-one-seo-pack&TB_iframe=true');
    }

    /**
     * Get the action text for All in One SEO step.
     *
     * @return string Action text.
     */
    public static function get_aioseo_action_text()
    {
        if (self::is_aioseo_active()) {
            return __('Configure SEO', 'memberpress');
        }

        if (self::is_aioseo_installed()) {
            return __('Activate Plugin', 'memberpress');
        }

        return __('Install Plugin', 'memberpress');
    }

    /**
     * Check if OptinMonster plugin is installed.
     *
     * @return boolean True if installed.
     */
    public static function is_optinmonster_installed()
    {
        return is_dir(WP_PLUGIN_DIR . '/optinmonster');
    }

    /**
     * Check if OptinMonster plugin is active.
     *
     * @return boolean True if active.
     */
    public static function is_optinmonster_active()
    {
        return is_plugin_active('optinmonster/optin-monster-wp-api.php');
    }

    /**
     * Get the action URL for OptinMonster step.
     *
     * @return string Action URL.
     */
    public static function get_optinmonster_action_url()
    {
        if (self::is_optinmonster_active()) {
            return admin_url('admin.php?page=optin-monster-dashboard');
        }

        // Open OptinMonster plugin details modal within WP admin.
        return admin_url('plugin-install.php?tab=plugin-information&plugin=optinmonster&TB_iframe=true');
    }

    /**
     * Get the action text for OptinMonster step.
     *
     * @return string Action text.
     */
    public static function get_optinmonster_action_text()
    {
        if (self::is_optinmonster_active()) {
            return __('Configure OptinMonster', 'memberpress');
        }

        if (self::is_optinmonster_installed()) {
            return __('Activate Plugin', 'memberpress');
        }

        return __('Install Plugin', 'memberpress');
    }

    /**
     * Get the current progress percentage.
     *
     * Calculates progress based on the number of completed steps.
     *
     * @return integer Progress percentage (0-100).
     */
    public static function get_progress_percentage()
    {
        $steps           = self::get_steps();
        $total_steps     = count($steps);
        $done_steps      = 0;

        foreach ($steps as $step) {
            // Count both completed and skipped steps toward progress.
            if ($step['completed'] || $step['skipped']) {
                $done_steps++;
            }
        }

        if ($total_steps === 0) {
            return 0;
        }

        return (int) round(($done_steps / $total_steps) * 100);
    }

    /**
     * Get the count of completed or skipped steps.
     *
     * @return integer Number of completed or skipped steps.
     */
    public static function get_completed_count()
    {
        $steps = self::get_steps();
        $count = 0;

        foreach ($steps as $step) {
            // Count both completed and skipped steps.
            if ($step['completed'] || $step['skipped']) {
                $count++;
            }
        }

        return $count;
    }

    /**
     * Get the total number of steps.
     *
     * @return integer Total number of steps.
     */
    public static function get_total_count()
    {
        return count(self::get_steps());
    }

    /**
     * Check if a payment gateway is connected.
     *
     * @return boolean True if a payment gateway is connected.
     */
    public static function is_payment_gateway_connected()
    {
        $mepr_options = MeprOptions::fetch();

        return count($mepr_options->integrations) > 0;
    }

    /**
     * Check if at least one membership product exists.
     *
     * @return boolean True if a membership exists.
     */
    public static function has_membership()
    {
        return MeprProduct::count() > 0;
    }

    /**
     * Check if at least one access rule exists.
     *
     * @return boolean True if a rule exists.
     */
    public static function has_rule()
    {
        return MeprRule::count() > 0;
    }

    /**
     * Check if at least one reminder is configured.
     *
     * @return boolean True if a reminder exists.
     */
    public static function has_reminder()
    {
        $reminders = MeprCptModel::all('MeprReminder');
        return !empty($reminders);
    }

    /**
     * Check if there's at least one completed or confirmed transaction.
     *
     * @return boolean True if a successful transaction exists.
     */
    public static function has_transaction()
    {
        global $wpdb;

        $mepr_db = MeprDb::fetch();

        $count = (int) $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
            $wpdb->prepare(
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safe.
                "SELECT COUNT(*) FROM {$mepr_db->transactions} WHERE status IN (%s, %s)",
                MeprTransaction::$complete_str,
                MeprTransaction::$confirmed_str
            )
        );

        return $count > 0;
    }

    /**
     * Check if the checklist has been dismissed.
     *
     * @return boolean True if dismissed.
     */
    public static function is_dismissed()
    {
        return (bool) get_option(self::$dismissed_option, false);
    }

    /**
     * Dismiss the checklist globally.
     *
     * @return void
     */
    public static function dismiss()
    {
        update_option(self::$dismissed_option, true);
    }

    /**
     * Check if the checklist is minimized (minimized by default).
     *
     * @return boolean True if minimized.
     */
    public static function is_minimized()
    {
        // Check if user just clicked a step link (cookie is set).
        // In this case, keep the checklist expanded.
        if (self::should_keep_expanded()) {
            return false;
        }

        // Default to minimized. Only expanded when explicitly set to false.
        $expanded = get_option(self::$minimized_option, null);

        // If option doesn't exist or is true, sidebar is minimized.
        // If option is explicitly false, sidebar is expanded.
        return $expanded !== false;
    }

    /**
     * Check if the checklist should be kept expanded (user clicked a step link).
     *
     * @return boolean True if the "keep expanded" cookie is set.
     */
    public static function should_keep_expanded()
    {
        return isset($_COOKIE['mepr_checklist_keep_expanded']);
    }

    /**
     * Set the minimized state globally.
     *
     * @param boolean $minimized Whether to minimize.
     *
     * @return void
     */
    public static function set_minimized($minimized)
    {
        if ($minimized) {
            // Minimized state - delete option to use default (minimized).
            delete_option(self::$minimized_option);
        } else {
            // Expanded state - store false to indicate expanded.
            update_option(self::$minimized_option, false);
        }
    }

    /**
     * Get the list of skipped step IDs.
     *
     * @return array Array of skipped step IDs.
     */
    public static function get_skipped_steps()
    {
        $skipped = get_option(self::$skipped_steps_option, []);

        return is_array($skipped) ? $skipped : [];
    }

    /**
     * Skip a step.
     *
     * @param string $step_id The step ID to skip.
     *
     * @return void
     */
    public static function skip_step($step_id)
    {
        $skipped = self::get_skipped_steps();

        if (!in_array($step_id, $skipped, true)) {
            $skipped[] = $step_id;
            update_option(self::$skipped_steps_option, $skipped);
        }
    }

    /**
     * Check if the checklist is complete (all steps done).
     *
     * @return boolean True if all steps are completed.
     */
    public static function is_complete()
    {
        return self::get_completed_count() === self::get_total_count();
    }

    /**
     * Check if we're on a MemberPress core admin page.
     *
     * @return boolean True if on a MemberPress core admin page.
     */
    public static function is_memberpress_core_page()
    {
        if (!is_admin()) {
            return false;
        }

        $screen = get_current_screen();

        if (!$screen) {
            return false;
        }

        // Check for MemberPress core post types (memberpressproduct, memberpressrule, etc.).
        if (!empty($screen->post_type) && preg_match('/^memberpress/', $screen->post_type)) {
            return true;
        }

        // Check for MemberPress core admin pages (memberpress-options, memberpress-reports, etc.).
        if (preg_match('/^memberpress_page_memberpress-/', $screen->id)) {
            return true;
        }

        // Check for MemberPress reminder post type.
        if ($screen->post_type === 'mp-reminder') {
            return true;
        }

        // Check for addon post types on their main listing pages (edit.php screen).
        $addon_post_types = [
            'mpcs-course',    // MemberPress Courses.
            'mcdl-file',      // MemberPress Downloads.
            'mcdir-profile',  // ClubSuite Profiles.
            'mcdir-directory', // ClubSuite Directories.
            'mp-circle',      // ClubSuite Circles.
        ];

        if ($screen->base === 'edit' && in_array($screen->post_type, $addon_post_types, true)) {
            return true;
        }

        return false;
    }

    /**
     * Check if the checklist should be shown.
     *
     * @return boolean True if the checklist should be displayed.
     */
    public static function should_show()
    {
        // Don't show if user doesn't have admin access.
        if (!MeprUtils::is_mepr_admin()) {
            return false;
        }

        // Don't show if dismissed.
        if (self::is_dismissed()) {
            return false;
        }

        // Don't show if already complete.
        if (self::is_complete()) {
            return false;
        }

        // Don't show on the onboarding page.
        if (MeprOnboardingCtrl::is_onboarding_page()) {
            return false;
        }

        // Only show on MemberPress core admin pages.
        if (!self::is_memberpress_core_page()) {
            return false;
        }

        return true;
    }

    /**
     * Get the next incomplete step.
     *
     * @return array|null The next incomplete step or null if all complete.
     */
    public static function get_next_step()
    {
        $steps = self::get_steps();

        foreach ($steps as $step) {
            if (!$step['completed']) {
                return $step;
            }
        }

        return null;
    }

    /**
     * Get grouped steps (completed first, then pending).
     *
     * @return array Array with 'completed' and 'pending' keys.
     */
    public static function get_grouped_steps()
    {
        $steps     = self::get_steps();
        $completed = [];
        $pending   = [];
        $skipped   = [];

        foreach ($steps as $step) {
            if ($step['completed']) {
                $completed[] = $step;
            } elseif ($step['skipped']) {
                $skipped[] = $step;
            } else {
                $pending[] = $step;
            }
        }

        return [
            'completed' => $completed,
            'pending'   => $pending,
            'skipped'   => $skipped,
        ];
    }
}