Conventions

PHP conventions

All PHP code in Paradigm CMS extensions follows strict conventions to keep extensions predictable, secure, and AI-friendly.

Procedural code only

Extension code is procedural. PHP classes are not used except in two places:

  • The hook config class in init.php (e.g. class extension_slug_config).
  • PAPI_OBJECT subclasses in the classes/ directory.

Everything else — request handlers, blocks, layouts, helpers — is written as plain procedural PHP.

Request handler pattern

Every file in API/requests/ must use the $request closure pattern. This is the canonical shape for all API request handlers:

<?php

$request = function($props = false) {

    // 1. Validate props exist
    if ($props === false)
        return array(
            'success' => false,
            'errors' => array(
                array('message' => "We're missing information required to complete this request.")
            )
        );

    // 2. Normalize props to object
    if (!is_object($props))
        $props = json_decode(json_encode($props));

    // 3. Validate required properties
    $requiredProperties = array('organization_ID', 'label');

    foreach ($requiredProperties as $requiredProperty) :
        if (!isset($props->$requiredProperty))
            return array(
                'success' => false,
                'errors' => array(
                    array('message' => "We're missing information required to complete this request.")
                )
            );
    endforeach;

    // 4. Auth check
    if (!defined('CURRENT_USER') || intval(CURRENT_USER['id']) === 0) :
        return array(
            'success' => false,
            'errors' => array(
                array('message' => "You must be logged in to make this request.")
            )
        );
    endif;

    // 5. Sanitize inputs
    $props->organization_ID = intval($props->organization_ID);
    $props->label = trim($props->label);

    // 6. Perform action and return
    $createResponse = PAPI_create_object('{ext}:{object}', array(
        'organization_ID' => $props->organization_ID,
        'label' => $props->label,
        'slug' => PAPI_slugify($props->label),
        'status' => 'active'
    ), 'internal');

    if ($createResponse['success'] !== true) :
        return $createResponse;
    endif;

    $item = PAPI_get_object('{ext}:{object}',
        $createResponse['created_object']['id'], 'internal');

    return array(
        'success' => true,
        'item' => $item
    );

};

?>

Key rules for every request handler:

  • Always use $request = function($props = false) { ... }; closure syntax.
  • Always normalize props: if (!is_object($props)) $props = json_decode(json_encode($props));
  • Always validate required properties with a $requiredProperties array and foreach loop.
  • Always sanitize inputs (intval(), trim()) before use.

Authentication checks

Use one of three patterns depending on the access level needed.

Logged-in user check

if (!defined('CURRENT_USER') || intval(CURRENT_USER['id']) === 0) :
    return array(
        'success' => false,
        'errors' => array(
            array('message' => "You must be logged in to make this request.")
        )
    );
endif;

Site admin check

if (PAPI_is_user_logged_in() !== true || CURRENT_USER['is_admin'] !== true) {
    return array(
        'success' => false,
        'errors' => array(
            array('message' => 'You do not have permission to perform this action.')
        )
    );
}

Organization admin check

$orgAdminCheck = PAPI_request('organizations:is_organization_admin', array(
    'organization_ID' => $props->organization_ID
));

if ($orgAdminCheck['success'] !== true && CURRENT_USER['is_admin'] !== true) :
    return array(
        'success' => false,
        'errors' => array(
            array('message' => 'You do not have permission to perform this action.')
        )
    );
endif;

Super admin check

if (PAPI_is_super_admin() !== true) {
    return array(
        'success' => false,
        'errors' => array(
            array('message' => 'You do not have permission to perform this action.')
        )
    );
}

CURRENT_USER constant

CURRENT_USER is a constant (array) available throughout all extension requests, blocks, and layouts. It is not a variable — do not prefix it with $.

Available keys:

  • id — User ID (integer)
  • email — Email address
  • fname — First name
  • lname — Last name
  • is_admin — Boolean, site-level admin flag
  • users — Related user records

API response format

All API handlers return arrays with a success boolean.

Success response

return array(
    'success' => true,
    'items' => $items,
    'totalPages' => $totalPages,
    'currentPage' => $page
);

Error response

return array(
    'success' => false,
    'errors' => array(
        array('message' => 'Descriptive error message here.')
    )
);

Database helpers

PAPI_get_objects — retrieve multiple records

PAPI_get_objects(
    '{extension_slug}:{object_slug}',
    array(
        'conditions' => array(
            array(
                array('{property}', '=', '{value}'),
                array('{property2}', '=', '{value2}')  // OR within same group
            ),
            array(
                array('{property3}', '=', '{value3}')  // AND (separate group)
            )
        ),
        'orderBy' => array('{property}' => 'DESC'),
        'limit' => 10,
        'offset' => 0
    ),
    'internal'
);

Conditions use nested arrays: inner arrays within a group are OR'd together, outer groups are AND'd together. The third parameter is always 'internal' for internal calls.

PAPI_get_object — retrieve a single record

// By ID
$item = PAPI_get_object('{ext}:{object}', $id, 'internal');

// By conditions
$item = PAPI_get_object('{ext}:{object}', array(
    array(array('slug', '=', $slug)),
    array(array('status', '=', 'active'))
), 'internal');

Create, update, delete

// Create
$response = PAPI_create_object('{ext}:{object}', array(
    'label' => $props->label,
    'slug' => PAPI_slugify($props->label),
    'status' => 'active'
), 'internal');
// Returns: array('success' => true, 'created_object' => array('id' => N))

// Update
$response = PAPI_update_object('{ext}:{object}', $id, array(
    'label' => $props->label
), 'internal');

// Delete
$response = PAPI_delete_object('{ext}:{object}', $id, 'internal');

Calling other extension handlers

$response = PAPI_request('{ext}:{action}', array(
    'param' => $value
));

JSON auto-decoding

Text fields containing valid JSON are automatically decoded into arrays when retrieved via PAPI helpers. Never call json_decode() on retrieved object fields.

$item = PAPI_get_object('extension:object', $item_id, 'internal');
// If 'settings' contains '{"theme":"dark"}',
// $item['settings'] is already an array

Helper function scoping

If a helper function is needed, prefix its name with the full directory path to avoid naming conflicts across extensions:

function my_extension_1_0_0_src_API_requests_my_action_helperName() {
    // Helper logic
}

Utility functions

  • PAPI_slugify($string) — Converts a string to a URL-friendly slug.
  • PAPI_is_user_logged_in() — Returns true if a user session is active.
  • PAPI_is_super_admin() — Returns true if the current user is a super admin.