PHP Classes

File: README.md

Recommend this page to a friend!
  Packages of Michael Beck   XOOPS helpers   README.md   Download  
File: README.md
Role: Documentation
Content type: text/markdown
Description: Documentation
Class: XOOPS helpers
Helper class functions to use with XOOPS
Author: By
Last change:
Date: 11 days ago
Size: 16,685 bytes
 

Contents

Class file image Download

XOOPS Helpers

Convention-over-configuration utility and service helpers for XOOPS CMS development.

License: GPL v2 PHP 8.2+

41 source files. 151 tests. Zero configuration. XSS-safe HTML by default. One composer require.

What Is This?

XOOPS Helpers is a standalone utility library that makes every XOOPS module safer and shorter by replacing the repetitive, error-prone boilerplate every module developer writes over and over:

// Before ? scattered across every XOOPS module
$escaped  = htmlspecialchars($value, ENT_QUOTES | ENT_HTML5, 'UTF-8');
$url      = XOOPS_URL . '/modules/' . $dirname . '/article.php?id=' . $id;
$path     = XOOPS_ROOT_PATH . '/modules/' . $dirname . '/language/' . $language . '/blocks.php';
$sitename = $GLOBALS['xoopsConfig']['sitename'];

// After
$escaped  = HtmlBuilder::escape($value);
$url      = Url::module($dirname, 'article.php', ['id' => $id]);
$path     = Path::module($dirname, "language/{$language}/blocks.php");
$sitename = Config::get('system.sitename');

> That first line is not just shorter ? it is structurally safer. > Every manual htmlspecialchars() call is a place where a future developer can introduce > a stored XSS vulnerability by forgetting it once. HtmlBuilder escapes all attribute > values and class names automatically ? the source of the vast majority of real-world XSS. > Tag content is your responsibility, intentionally: content can legitimately contain HTML > (rendered markup, trusted template fragments). The safe path is explicit: pass user-supplied > content through HtmlBuilder::escape(). This htmlspecialchars pattern appears 30+ times > in the XOOPS Core alone ? each one a place where this library makes the correct choice the > easiest choice.

Requirements

  • PHP 8.2 or later with the `ext-mbstring` extension
  • No other runtime dependencies

Optional extensions for enhanced functionality: - ext-intl ? locale-aware number and date formatting - ext-apcu ? APCu caching backend - ext-zip ? zip/unzip filesystem operations

Installation

composer require xoops/helpers

Quick Start

use Xoops\Helpers\Utility\HtmlBuilder;
use Xoops\Helpers\Service\Url;
use Xoops\Helpers\Service\Path;
use Xoops\Helpers\Service\Config;
use Xoops\Helpers\Service\Cache;
use Xoops\Helpers\Utility\Arr;
use Xoops\Helpers\Utility\Str;
use Xoops\Helpers\Utility\Number;
use Xoops\Helpers\Utility\Collection;

// HTML ? attribute values escaped automatically; use text() for tag content
HtmlBuilder::attributes(['class' => 'btn', 'disabled' => true, 'data-id' => $userInput]);
HtmlBuilder::classes(['btn', 'btn-primary' => $isPrimary, 'disabled' => false]);
HtmlBuilder::tag('div', ['class' => 'alert'], HtmlBuilder::text($userMessage)); // user text
HtmlBuilder::tag('div', ['class' => 'body'], $renderedHtmlBlock);               // trusted HTML

// URLs ? zero concatenation
Url::module('news', 'article.php', ['id' => 42]);
Url::asset('themes/starter/css/style.css');
Url::theme('starter', 'images/logo.png');

// Paths ? cross-platform, always correct; languageFile() resolves language fallback
Path::module('news', 'language/english/main.php');
Path::languageFile('news', $language, 'main.php'); // tries $language, falls back to english
Path::storage('caches/xmf');
Path::uploads('images/avatars');

// Config ? dot notation, auto-cached
Config::get('system.sitename', 'XOOPS');
Config::get('news.items_per_page', 10);

// Cache ? compute-and-cache in one call
$articles = Cache::remember('news_latest', 3600, fn() => loadArticles());

// Arrays ? dot notation, pluck, group, filter
$value   = Arr::get($config, 'database.host', 'localhost');
$names   = Arr::pluck($users, 'uname', 'uid');
$grouped = Arr::groupBy($articles, 'category_id');

// Strings ? slug, validation, case conversion
Str::slug('Hello World');        // "hello-world"
Str::isEmail('a@example.com');   // true
Str::camel('module_config');     // "moduleConfig"
Str::limit($body, 150);          // "First 150 chars..."
Str::random(32);                 // cryptographically secure

// Numbers ? human-readable formatting
Number::fileSize(1572864);       // "1.50 MB"
Number::forHumans(2300000);      // "2.3M"
Number::ordinal(21);             // "21st"
Number::currency(99.99, 'EUR', 'de_DE');

// Collections ? fluent data transformation
Collection::make($items)
    ->filter(fn($item) => $item['active'])
    ->sortBy('name')
    ->pluck('title', 'id')
    ->all();

Library Contents

Tier 0 ? Utility (Pure PHP, zero XOOPS dependency)

These work anywhere ? CLI scripts, cron jobs, unit tests ? no XOOPS boot required.

| Class | Purpose | |-------|---------| | HtmlBuilder | XSS-safe HTML: attributes, classes, tag, escape, text, stylesheet, script, meta ? attribute values escaped automatically; use text() to explicitly escape tag content | | Arr | Array helpers with dot notation: get, set, has, pluck, groupBy, sortBy, where, flatten, dot/undot, only/except, first/last, wrap, collapse | | Str | String helpers: slug, camel/snake/studly/kebab, limit, random, contains/startsWith/endsWith, between, mask, isEmail/isUrl/isIp/isJson/isHexColor | | Number | Number formatting: format, fileSize, forHumans, percentage, ordinal, currency, clamp | | Date | Date helpers with injectable time source: now, range, diff, isValid, addDays/subDays, isWeekend/isToday/isPast/isFuture, reformat, age | | Value | Value resolution: value (Closure resolver), blank/filled, optional (null-safe access), once (memoization), missing (sentinel) | | Collection | Fluent array wrapper: map, filter, reject, reduce, pluck, groupBy, sortBy, first/last, chunk, take/skip, sum/avg/min/max, when, pipe, tap | | Pipeline | Data transformation chains: Pipeline::send($v)->pipe(fn)->pipe(fn)->thenReturn() | | Stringable | Fluent string builder: Stringable::of($s)->trim()->lower()->slug()->toString() | | Filesystem | File operations: readJson/putJson, mimeType, isImage, mkdir, deleteDirectory, copyDirectory, zip/unzip, readChunked | | Environment | Runtime detection: isProduction/isDevelopment/isTesting, get/require/has | | Benchmark | Profiling: measure (time + memory), time, average (multi-iteration) | | Encoding | URL-safe base64: base64UrlEncode/base64UrlDecode | | Data | Conversion: toArray, toObject, toQueryString, fromQueryString | | Retry | Error recovery: retry (with backoff), rescue (with fallback) | | ThrowHelper | Guard clauses: throwIf, throwUnless | | Transform | Conditional transforms: transform (if filled), when (predicate-based) | | Tap | Side-effect helper: call callback, return original value |

Tier 1 ? Contracts (Interfaces)

| Interface | Purpose | |-----------|---------| | PathLocatorInterface | Filesystem path resolution | | UrlGeneratorInterface | URL generation | | CacheInterface | Cache operations | | ConfigProviderInterface | Configuration loading | | DateTimeProviderInterface | Clock abstraction for testing |

Tier 2 ? Service Facades (Zero-config, XOOPS-aware)

| Facade | Purpose | Override | |--------|---------|----------| | Path | Path::base(), module(), storage(), uploads(), themes(), languageFile() | Path::use($locator) | | Url | Url::to(), asset(), module(), theme() | Url::use($generator) | | Config | Config::get(), set(), has(), all(), registerLoader() | Config::setProvider($p) | | Cache | Cache::get(), set(), forget(), remember(), flush() | Cache::use($adapter) |

All facades work immediately using XOOPS constants (XOOPS_ROOT_PATH, XOOPS_URL, etc.). Override with ::use() for testing or custom installations. Reset with ::reset().

Tier 3 ? Providers (Default implementations)

| Provider | Purpose | |----------|---------| | DefaultPathLocator | Maps to XOOPS constants | | DefaultUrlGenerator | Uses XOOPS_URL, falls back to $_SERVER | | XoopsCacheAdapter | Auto-detects: XoopsCache, APCu, or file cache | | ArrayCache | In-memory cache for testing | | SystemDateTimeProvider | System clock |

Tier 4 ? Integration (XOOPS-specific)

| Component | Purpose | |-----------|---------| | XoopsCollection | XoopsCollection::fromHandler($handler, $criteria) with pluckVar() for getVar() | | AssetUrlPlugin | Smarty: <{asset_url path="css/style.css"}> | | FormatNumberPlugin | Smarty: <{format_number value=$size type="filesize"}> | | CssClassesPlugin | Smarty: <{css_classes classes=$classArray}> | | PluginRegistrar | Register all Smarty plugins at once |

Cross-cutting

| Component | Purpose | |-----------|---------| | Tappable | Trait adding tap() to any class | | functions.php | Optional global function wrappers (not auto-loaded) |

Architecture

Dependencies flow downward only. Tier 0 classes can be used in any PHP 8.2+ project without XOOPS ? in CLI scripts, cron jobs, and unit tests with no bootstrap required.

graph TD
    T4["Tier 4 · Integration
    Depend on XOOPS classes
    XoopsObject · Smarty"]

    T3["Tier 3 · Provider
    Default implementations
    XOOPS-aware"]

    T2["Tier 2 · Service
    Static facades
    Depend on XOOPS constants"]

    T1["Tier 1 · Contracts
    Interfaces only
    No implementation"]

    T0["Tier 0 · Utility
    Pure PHP · Zero dependencies
    Works anywhere"]

    T4 --> T3
    T3 --> T2
    T2 --> T1
    T1 --> T0

    style T4 fill:#b7e0ff,stroke:#4a90d9,color:#000
    style T3 fill:#c8f0d0,stroke:#3a9a5c,color:#000
    style T2 fill:#fff3b0,stroke:#c8a200,color:#000
    style T1 fill:#ffd6a5,stroke:#d48000,color:#000
    style T0 fill:#ffadad,stroke:#c0392b,color:#000

Optional Global Functions

The file src/functions.php provides short function wrappers like collect(), str(), pipeline(), tap(), retry(), env(), etc. It is not auto-loaded ? opt in explicitly.

Recommended pattern: load it once in your XOOPS bootstrap, not in individual module files. This prevents redundant require calls across a multi-module installation:

// In mainfile.php or a central preload script ? once per request
if (file_exists(XOOPS_ROOT_PATH . '/vendor/xoops/helpers/src/functions.php')) {
    require_once XOOPS_ROOT_PATH . '/vendor/xoops/helpers/src/functions.php';
}

If you are building a single module and do not control the bootstrap, load it in your module's entry point:

require_once 'vendor/xoops/helpers/src/functions.php';

$slug = str('Hello World')->slug()->toString();
$data = collect($items)->filter(fn($i) => $i['active'])->pluck('name')->all();
$value = retry(3, fn() => fetchFromApi(), sleepMs: 500);

All functions are guarded with function_exists() to prevent fatal redeclaration errors.

Compatibility

XOOPS 2.5.x

Fully compatible. Designed for inclusion in XOOPS 2.5.12+.

XMF 1.x (xoops/xmf)

No conflicts. Different namespace (Xoops\Helpers\ vs Xmf\), no shared class names, no shared global functions. Both can be loaded simultaneously via Composer.

Where both libraries offer related functionality, they serve different scopes:

| Area | XMF 1.x | XOOPS Helpers | |------|---------|---------------| | URL/Path | $helper->url() ? module-scoped | Url::module() ? global, works without module context | | Config | $helper->getConfig() ? per-module handler | Config::get('mod.key') ? dot notation, cached | | Cache | Helper\Cache::cacheRead() ? module-prefixed | Cache::remember() ? global, auto-backend | | Random | Random::generateKey() ? SHA512 hash tokens | Str::random() ? URL-safe strings, configurable length | | SEO | Metagen::generateSeoTitle() ? full meta tags | Str::slug() ? pure string transformation |

Migration Strategy

You do not need to refactor existing XMF 1.x code to adopt this library. Both coexist safely. The recommended approach depends on where you are in a project:

Starting a new module ? use XOOPS Helpers exclusively from the first line. There is no legacy to consider and you get the full benefit of automatic escaping, dot-notation config, and fluent collections from day one.

Actively developing an existing module ? use XOOPS Helpers for all new code and any functions you touch during the current sprint. When you open a file to add a feature, convert the XMF 1.x patterns in that file as you go. Do not schedule a dedicated refactoring sprint; let the migration happen organically as the module evolves.

Maintaining a stable module with no active development ? do nothing. The libraries coexist with zero conflicts. The migration cost is not justified by a pure maintenance ticket. If it is not broken, leave it until you have a reason to open the file.

When you do migrate a specific pattern, the Cache facade is the most common conversion:

// XMF 1.x ? before
if (!$data = \XoopsCache::read("{$dirname}_config")) {
    $data = xoops_getModuleConfig($dirname);
    \XoopsCache::write("{$dirname}_config", $data);
}

// XOOPS Helpers ? after
$data = Cache::remember("{$dirname}_config", 3600, fn() => xoops_getModuleConfig($dirname));

XMF 2.0 (xoops/xmf next generation)

Designed as a companion. XMF 2.0 provides the architectural framework (Repository, EventBus, Container, QueryBuilder); XOOPS Helpers provides the day-to-day utilities (Arr, Str, Number, HtmlBuilder, Collection). XMF 2.0 will declare xoops/helpers as a dependency ? requiring XMF 2.0 pulls this library in automatically.

Testing

composer install
vendor/bin/phpunit

All services are mockable for testing:

use Xoops\Helpers\Service\{Path, Url, Config, Cache};
use Xoops\Helpers\Provider\ArrayCache;

// Inject test implementations
Cache::use(new ArrayCache());
Config::registerLoader('mymod', fn() => ['key' => 'value']);

// Reset after tests
Cache::reset();
Config::reset();
Path::reset();
Url::reset();

The Date utility accepts an injectable time provider:

use Xoops\Helpers\Utility\Date;
use Xoops\Helpers\Contracts\DateTimeProviderInterface;

Date::setProvider(new class implements DateTimeProviderInterface {
    public function now(): \DateTimeImmutable {
        return new \DateTimeImmutable('2025-06-15 12:00:00');
    }
});

Date::isToday('2025-06-15'); // true ? deterministic in tests
Date::resetProvider();

Contributing

Contributions are welcome. Please follow XOOPS coding standards: - declare(strict_types=1) in every file - PHP 8.2+ features (readonly, match, named arguments, union types) - Final classes for utility classes - Full type hints on all methods - PHPUnit tests for all new functionality

Documentation

See TUTORIAL.md for a comprehensive guide with before/after comparisons from real XOOPS Core and module code.

License

GNU GPL v2 or later. See LICENSE for details.