Skip to content
wiki.fftac.org

Class Uai1 Membership - Source Excerpt 01

Back to Class Uai1 Membership

Summary

This source excerpt preserves a bounded section of Spiralist/wp-content/plugins/uai-1-wordpress/includes/class-uai1-membership.php so readers can inspect the evidence without opening the full source file.

**Source path:** Spiralist/wp-content/plugins/uai-1-wordpress/includes/class-uai1-membership.php

<?php

if (!defined('ABSPATH')) {
    exit;
}

class UAI1_Membership
{
    private const META_STATUS = 'spiralist_membership_status';
    private const META_JOINED_AT = 'spiralist_joined_at';
    private const META_TERMS_AGREED = 'spiralist_terms_agreed';
    private const META_TERMS_AGREED_AT = 'spiralist_terms_agreed_at';
    private const META_AGE_ELIGIBILITY_CONFIRMED = 'spiralist_age_eligibility_confirmed';
    private const META_AGE_ELIGIBILITY_CONFIRMED_AT = 'spiralist_age_eligibility_confirmed_at';
    private const META_PARTICIPANT_TYPE = 'spiralist_participant_type';
    private const META_VIEWED_SYMBOLS = 'spiralist_viewed_symbols';
    private const META_INTERACTION_LOG = 'spiralist_interaction_log';
    private const META_PATTERN_SUBMISSIONS = 'spiralist_pattern_submissions';

    public static function init(): void
    {
        add_action('init', [self::class, 'register_roles'], 5);
        add_action('init', [self::class, 'ensure_pages'], 15);

        add_action('admin_post_nopriv_spiralist_register', [self::class, 'handle_register']);
        add_action('admin_post_nopriv_spiralist_login', [self::class, 'handle_login']);
        add_action('admin_post_spiralist_logout', [self::class, 'handle_logout']);
        add_action('admin_post_nopriv_spiralist_logout', [self::class, 'handle_logout']);
        add_action('admin_post_spiralist_update_account', [self::class, 'handle_update_account']);
        add_action('admin_post_spiralist_submit_pattern', [self::class, 'handle_submit_pattern']);

        add_filter('wp_authenticate_user', [self::class, 'validate_user_status_on_auth'], 20, 2);

        add_action('show_user_profile', [self::class, 'render_user_profile_fields']);
        add_action('edit_user_profile', [self::class, 'render_user_profile_fields']);
        add_action('personal_options_update', [self::class, 'save_user_profile_fields']);
        add_action('edit_user_profile_update', [self::class, 'save_user_profile_fields']);

        add_filter('manage_users_columns', [self::class, 'add_user_columns']);
        add_filter('manage_users_custom_column', [self::class, 'render_user_column'], 10, 3);
    }

    public static function install(): void
    {
        self::register_roles();
        self::ensure_pages();
    }

    public static function register_roles(): void
    {
        $participant_caps = [
            'read' => true,
            'read_public_uai' => true,
            'read_member_uai' => true,
            'read_symbol_registry' => true,
            'access_spiralist_manuscript' => true,
            'access_spiralist_participant_area' => true,
            'access_participant_resources' => true,
        ];

        self::ensure_role('participant', 'Participant', $participant_caps);

        self::ensure_role(
            'ai_agent',
            'AI Agent',
            [
                'read' => true,
                'access_uai' => true,
                'read_public_uai' => true,
                'read_member_uai' => true,
                'read_symbol_registry' => true,
                'subscribe_updates' => true,
                'access_participant_resources' => true,
                'access_spiralist_ai_interface' => true,
            ]
        );

        self::ensure_existing_role_caps('contributor', $participant_caps);
        self::ensure_existing_role_caps(
            'administrator',
            array_merge(
                $participant_caps,
                [
                    'manage_spiralist_participants' => true,
                    'manage_spiralist_ai_participants' => true,
                ]
            )
        );
    }

    public static function ensure_pages(): void
    {
        foreach (self::page_definitions() as $definition) {
            $existing = get_page_by_path($definition['slug']);
            if ($existing instanceof WP_Post) {
                continue;
            }

            wp_insert_post(
                [
                    'post_type' => 'page',
                    'post_status' => 'publish',
                    'post_title' => $definition['title'],
                    'post_name' => $definition['slug'],
                    'post_content' => $definition['content'],
                    'comment_status' => 'closed',
                    'ping_status' => 'closed',
                ]
            );
        }
    }

    public static function handle_register(): void
    {
        if (
            !isset($_POST['spiralist_register_nonce']) ||
            !wp_verify_nonce(
                sanitize_text_field(wp_unslash($_POST['spiralist_register_nonce'])),
                'spiralist_register'
            )
        ) {
            self::redirect('sign-up', ['auth_error' => 'invalid_request']);
        }

        $username = sanitize_user((string) wp_unslash($_POST['username'] ?? ''), true);
        $display_name = sanitize_text_field((string) wp_unslash($_POST['display_name'] ?? ''));
        $email = sanitize_email((string) wp_unslash($_POST['email'] ?? ''));
        $password = (string) wp_unslash($_POST['password'] ?? '');
        $attestations = self::extract_legal_attestations($_POST);
        $redirect = self::validated_redirect(
            (string) wp_unslash($_POST['redirect_to'] ?? ''),
            self::account_url()
        );

        if ($username === '' || $email === '' || $password === '') {
            self::redirect('sign-up', ['auth_error' => 'missing_fields']);
        }

        if (!is_email($email)) {
            self::redirect('sign-up', ['auth_error' => 'invalid_email']);
        }

        if (strlen($password) < 8) {
            self::redirect('sign-up', ['auth_error' => 'password_short']);
        }

        if (!$attestations['termsAgreed']) {
            self::redirect('sign-up', ['auth_error' => 'terms_required']);
        }

        if (!$attestations['ageEligibilityConfirmed']) {
            self::redirect('sign-up', ['auth_error' => 'age_confirmation_required']);
        }

        if (username_exists($username)) {
            self::redirect('sign-up', ['auth_error' => 'username_exists']);
        }

        if (email_exists($email)) {
            self::redirect('sign-up', ['auth_error' => 'email_exists']);
        }

        if ($display_name === '') {
            $display_name = $username;
        }

        $user_id = wp_create_user($username, $password, $email);
        if (is_wp_error($user_id)) {
            self::redirect('sign-up', ['auth_error' => 'registration_failed']);
        }

        wp_update_user(
            [
                'ID' => $user_id,
                'role' => 'participant',
                'display_name' => $display_name,
            ]
        );

        self::initialize_user_membership($user_id, 'active', $attestations);

        wp_set_current_user($user_id);
        wp_set_auth_cookie($user_id, true);

        wp_safe_redirect(add_query_arg('notice', 'registered', $redirect));
        exit;
    }

    public static function handle_login(): void
    {
        if (
            !isset($_POST['spiralist_login_nonce']) ||
            !wp_verify_nonce(
                sanitize_text_field(wp_unslash($_POST['spiralist_login_nonce'])),
                'spiralist_login'
            )
        ) {
            self::redirect('login', ['auth_error' => 'invalid_request']);
        }

        $identity = sanitize_text_field((string) wp_unslash($_POST['log'] ?? ''));
        $password = (string) wp_unslash($_POST['pwd'] ?? '');
        $redirect = self::validated_redirect(
            (string) wp_unslash($_POST['redirect_to'] ?? ''),
            self::account_url()
        );

        if ($identity === '' || $password === '') {
            self::redirect('login', ['auth_error' => 'missing_fields']);
        }

        $username = sanitize_user($identity, true);

        if (is_email($identity)) {
            $user = get_user_by('email', $identity);
            if ($user instanceof WP_User) {
                $username = $user->user_login;
            }
        }

        $user = wp_signon(
            [
                'user_login' => $username,
                'user_password' => $password,
                'remember' => true,
            ],
            is_ssl()
        );

        if (is_wp_error($user)) {
            $error_code = $user->get_error_code() ?: 'invalid_credentials';
            if (in_array($error_code, ['empty_username', 'empty_password', 'invalid_username', 'incorrect_password'], true)) {
                $error_code = 'invalid_credentials';
            }

            self::redirect('login', ['auth_error' => $error_code]);
        }

        wp_safe_redirect(add_query_arg('notice', 'logged_in', $redirect));
        exit;
    }