1. System Overview
Widget Storm is a modular widget platform for the web, active since 2008. Widgets are self-contained web components made of three layers (PHP, CSS, JavaScript), stored encrypted on the server and delivered over HTTP endpoints.
| Property | Value |
|---|---|
| Version | 4.0.0 |
| Language | PHP 8.3 |
| Database | MySQL / MariaDB |
| Encryption | RIJNDAEL-256 (Legacy), AES-256-CBC (Modern) |
| Default author | WidgetStormSystem |
| Execution contexts | Local (wgclient.php), Remote (wgcall.php), Client-side (wghandler.php) |
2. Widget Structure
Every widget consists of exactly three files. All three layers have access to the same parameters ($wgparams) and the instance ID ($wgguid).
| Layer | File | Content-Type | Description |
|---|---|---|---|
| PHP | widget_name.php | text/html | Server-side logic and HTML output |
| CSS | widget_name.css | text/css | Styling with an optional PHP bridge |
| JS | widget_name.js | text/javascript | Client-side interaction |
Variables available in the widget code
| Variable | Type | Description |
|---|---|---|
| $wgparams | object|array|string | Widget parameters (parsed from JSON, a semicolon list, or a string) |
| $widgetparams | object|array|string | Alias for $wgparams |
| $wgguid | string | Instance ID for CSS/JS scoping — set by the script tag; in a PHP include, the 5th argument |
PHP layer — example
<?php
if(isset($widgetparams->uid)) $wgguid = $widgetparams->uid;
else $wgguid = "";
if(isset($wgparams)){
$text = isset($wgparams->text) ? $wgparams->text : "Default text";
?>
<div id="mein_widget<?= $wgguid;?>" class="mein-widget">
<p><?= $text;?></p>
</div>
<?php
}
?>
Output modes
| Mode | Output |
|---|---|
| php | HTML only (evalAt Widget Storm, eval() executes exclusively widget code that authenticated authors have stored in the database — never user input. It is the technical foundation of the PHP-in-CSS bridge and of dynamic widget composition. No user input ever reaches eval(). result) |
| css | CSS only (evalThe CSS layer is rendered through eval() to enable the PHP-in-CSS bridge. The code comes exclusively from the database — created by authenticated widget authors. Dynamic selectors and instance-specific styling would not be possible without this mechanism. result) |
| js | JavaScript only (evalThe JS layer also uses eval() for embedded PHP variables (UID scoping, parameters). As with all widget layers: the executed code is author-verified and stored in the database, not user-generated. result) |
| (empty) | <style>CSS</style> PHP <script>JS</script> |
3. Parameter System
Parameters are passed to widgets via wgparams. The system supports three formats:
JSON (recommended)
{"text":"Hello World","color":"#ff0000","items":[1,2,3]}
Parsed into a stdClass object. Access via $wgparams->text.
Semicolon-separated
Value1;Value2;Value3
Parsed into an array. Access via $wgparams[0], $wgparams[1], etc.
Simple string
Any arbitrary text
Passed through unchanged as a string.
URL encoding for HTTP requests
wgparams={"text":"Hello","uid":"abc123"}
// In PHP, for URL parameters:
$packurl = http_build_query(['wgparams' => $paramsArray]);
4. PHP-in-CSS/JS Bridge
CSS and JS files can contain embedded PHP code. Because PHP tags are syntactically invalid in CSS/JS, they are wrapped in comments. The system converts them automatically before execution.
Conversion rules
| In the source code | Becomes |
|---|---|
| /*<?php | <?php |
| /*<?= | <?= |
| ?>*/ | ?> |
CSS example
/*<?php
if(isset($wgparams->uid)) $wgguid = $wgparams->uid;
else $wgguid = "";
?>*/
div#mein_widget/*<?= $wgguid;?>*/ {
background: rgba(170,210,210,0.08);
border: 1px solid rgba(170,210,210,0.2);
padding: 1em;
border-radius: 0.4em;
}
JS example
/*<?php
if(isset($wgparams->uid)) $wgguid = $wgparams->uid;
else $wgguid = "";
?>*/
jQuery(document).ready(function(){
jQuery("#mein_widget/*<?= $wgguid;?>*/").on("click", function(){
jQuery(this).toggleClass("active");
});
});
5. Nesting & Markers
Widgets can load other widgets. This is done via markers in the PHP code, which the server resolves recursively.
Marker syntax
##wgstart##widgetname::author::parameter##wgend##
| Segment | Description |
|---|---|
| widgetname | Name of the widget to embed |
| author | Author of the widget |
| parameter | Parameters as JSON or a semicolon list |
Resolution process
- The server finds all
##wgstart##...##wgend##markers in the PHP code - Extracts the name, author, and parameters from the marker
- Resolves the nested widget recursively
- The nested widget's CSS and JS are appended to the parent widget
- PHP is Base64-encoded and executed via
intern_script_wall()
UID Scoping & Placeholders
| Placeholder | Replacement | Usage |
|---|---|---|
| ##self## | widgetname + md5($wgguid) | Selector of this instance (widget name + md5 of $wgguid). Script tag: every embed on a page gets its own. PHP include: give each repeat of the same widget its own $wgguid (5th argument). |
| ##wgself## | widgetname + md5($wgguid) | Alias for ##self## |
| ##selfreplace## | ##self## | Escape: literal ##self## in the output |
6. Availability Levels
| Level | Access | Billing |
|---|---|---|
| open | All registered users | Free |
| authenticated | Author + users with explicit permission | Optional |
| payment | Author + users with active billing | Required |
Developers set the availability at upload time (parameter wgrmode). The level determines who can retrieve the widget through their wgclient — and whether billing is required.
Access control
1. Widget.availability == 'open' → Access granted
2. Widget.author == RequestUser → Access granted (author)
3. Permission check → Checked against the internal permission table
- Global permission → Applies to all users
- Specific permission → Applies to individual users
Widget-level security
Widgets can bring their own security mechanisms — independent of the host system. The wg_demo_todo widget demonstrates this principle: AES-256-encrypted persistence, key-based authentication with brute-force protection, and automatic preflight checks for dependencies and permissions. You can find more details on the widget overview.
7. HTTP Endpoints
wgclient.php — widget delivery (local)
Serves both as an HTTP endpoint and as a PHP include for server-side integration.
| Parameter | Required | Description |
|---|---|---|
| wgname | Yes | Widget name |
| wgauthor | No | Widget author (default: WidgetStormSystem) |
| wgmode | No | Output mode: php, css, js, or empty (all) |
| wgparams | No | Widget parameters (JSON, semicolon list, or string) |
| wgguid | No | Instance ID for scoping |
Composer — wgclient & wghandler as packages
Both client files can alternatively be installed from a private Composer repository. Authentication uses your Station login (HTTP Basic — Composer prompts for it on first access and stores the credentials in auth.json).
composer config repositories.widget-storm composer https://widget-storm.de/composer
composer require widget-storm/wgclient:^4.1
vendor/bin/wg-deploy public/
Key semantics: repeat installs and composer update re-issue the same key — your installed wgclient stays valid. Only an account's very first Composer install issues a new key once (a wgclient previously downloaded through the Station becomes invalid at that point). A deliberate rotation is possible at any time (wgclientcreator.php?rotate=1); afterwards run composer clear-cache && composer reinstall widget-storm/wgclient and redeploy — without clear-cache, Composer's local package cache would resurrect the old client.
wgcall.php — remote widget delivery
Endpoint for external servers. Requires authentication and encrypts the response.
| Parameter | Required | Description |
|---|---|---|
| wgname | Yes | Widget name (alphanumeric + underscore + hyphen) |
| wgauthor | Yes | Widget author |
| wgmode | Yes | Allowed values: php, css, js, go, c, md |
| requestuser | Yes | Username |
| requestkey | Yes | Authentication key (MD5 hash) |
| wgparams | No | Widget parameters |
| wgguid | No | Instance ID |
| wgfirst | No | true = no nesting resolution |
wgregistration.php — widget upload
Endpoint for uploading new or updated widgets.
| Parameter | Description |
|---|---|
| wgrname | Widget name |
| wgrparam | Layer: php, css, js |
| wgrcode | Encrypted widget code |
| wgrmode | Availability: authenticated, open, payment |
| wgrrequestuser | Username |
| wgrrequestkey | Authentication key |
wgbuilds/ (batch mode), only widgets assigned to your own author account are processed. wgclientcreator.php — client download
Generates a personalized wgclient.php with embedded credentials. Requires an active session (login via the Station). Your requestkey survives repeat downloads; a new one is issued only on the very first download — or on request via ?rotate=1 (key rotation; the old key becomes invalid).
wghandlercreator.php — handler download
Generates a portable wghandler.php class with an embedded RIJNDAEL-256 implementation (pure PHP via phpseclib3, compatible with PHP 8+), local widget caching, and support for nested widgets.
8. Widget Integration
Local integration (same server)
<!-- CSS in the <head> -->
<link rel="stylesheet"
href="wgclient.php?wgname=basic&wgauthor=WidgetStormSystem&wgmode=css&wgparams=..."
type="text/css">
<!-- JavaScript before </body> -->
<script src="wgclient.php?wgname=basic&wgauthor=WidgetStormSystem&wgmode=js&wgparams=..."></script>
<!-- PHP in the <body> -->
<?php
require_once 'wgclient.php';
$wg->get('basic', 'WidgetStormSystem', $params, 'php');
?>
Remote integration (external server)
<?php
// Place wgclient.php and wghandler.php on your own server
require_once 'wgclient.php';
// The credentials are embedded in wgclient.php
$wg->get('basic', 'WidgetStormSystem', $params, 'php');
?>
Setting up the support chat wg_chat after the purchase
The chat runs on your own web server: PHP 7.0 or later with the openssl and curl extensions. After the purchase the same instructions, with your domain, are in the Station under “Your purchased widgets”.
- Download
wgclient.phpandwghandler.phpin the Station and put both into the main folder of your website. Afterwardshttps://your-site.com/wgclient.phpmust be reachable, because the chat calls exactly that address. Downloading again keeps your key. - Put the chat into a PHP page in the same folder, for example
contact.php(code below). The first line goes at the very top of the file, before any other output. - Open the page once with
?wg_chat_key=and an operator key of your choosing, at least 12 characters, at the end of the address. You then see the operator view. Set the chat up before you link the page: until then visitors see a setup notice, and the first key counts. - To answer, open the same address with your key. The operator view stays logged in until 12 hours after it was last used. Visitors open the page without the key.
<?php require_once __DIR__ . '/wgclient.php'; ?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="/wgclient.php?wgname=wg_chat&wgauthor=WidgetStormSystem&wgmode=css">
</head>
<body>
<?php $wg->get('wg_chat', 'WidgetStormSystem', ['uid' => 'support', 'wglang' => 'en'], 'php'); ?>
<script src="/wgclient.php?wgname=wg_chat&wgauthor=WidgetStormSystem&wgmode=js"></script>
</body>
</html>
| Parameter | Meaning |
|---|---|
| uid | Name of the chat. The visitor page and the operator page need the same value. Without uid the host name of your website counts, without www. and port. |
| wglang | en for English buttons and notices; German without it. |
| title | Heading of the chat window (default: Support chat). |
| greeting | Greeting above the conversation. |
| poll | Polling interval in milliseconds, at least 1500 (default 4000). |
| maxlen | Maximum length of a message, 20 to 4000 characters (default 1000). |
wgdata/wg_chat, one level above the main folder. PHP must be allowed to create it; otherwise the chat names the path. The visitor’s browser holds nothing but a signed conversation id.New version: your server stores the widget code at the first request under
wgbuilds/WidgetStormSystem/. With a handler from version 4.2 on (named at the top of wghandler.php) it fetches corrections by itself, within an hour at the latest. With an older handler, download it again or move wg_chat.php, wg_chat.js and wg_chat.css there into another folder; the next page view then loads the current version.Key forgotten: in
wgdata/wg_chat, move your chat’s files (config_…, data_… and rate_… with the same id) into another folder and set the chat up again. The old conversations stay encrypted in the moved files. Execution modes (wgexecutionmode)
The developer uses wgexecutionmode to control whether a widget is rendered locally from the CodeVault or fetched live from the Widget Storm server. The default mode uses the local cache — which means no network latency and maximum loading speed.
| Mode | Behavior | Use case |
|---|---|---|
| (Default) | Local CodeVault — load widgets from the cache | Production: fastest delivery, no server dependency |
| live | Remote fetch — load and execute the widget live from the server | Development: always the latest version, no local file needed. Chosen in your own PHP (sixth argument of $wg->get()); from a URL it is ignored |
| uploadwidgets | Batch upload — upload local widgets in wgbuilds/ | Deployment: sync all your own widgets from the local directory. On the command line only: php wgclient.php uploadwidgets; from a URL it is ignored |
9. Encryption
Communication between client and server is encrypted. The system supports two encryption versions per user: RIJNDAEL-256 (the original method, in use since 2008) and AES-256-CBC (the modern standard for new users). Both coexist through a central EncryptionNegotiator that automatically selects the appropriate version per user.
| Version | Algorithm | Mode | Status |
|---|---|---|---|
| v1_rijndael | RIJNDAEL-256 | ECB | Legacy (since 2008) |
| v2_aes | AES-256-CBC | CBC with a random IV | Standard for new users |
Both directions (server → client and client → server) are encrypted at the application level. Each user has an individual key, which is rotated on every client download.
mcrypt extension was the standard way to do cryptography and openssl_encrypt() was not yet available. The decision matched the state of the art at the time. Since the modernization to PHP 8.3, phpseclib3 is used as a drop-in replacement for the removed mcrypt extension. You can find detailed background on this in the FAQ. 10. Rate Limiting
| Endpoint | Limit | Window | HTTP status when exceeded |
|---|---|---|---|
| wgcall.php | 120 requests | 60 seconds | 429 Too Many Requests |
| wgregistration.php | 30 uploads | 60 seconds | 429 Too Many Requests |
Tracking is done per IP address and endpoint. State files in /tmp/ws-ratelimit/.
11. CodeVault
The CodeVault is the heart of widget delivery. It stores widget code as local files on the client server — which means direct file-system access instead of network round-trips. Widgets load as fast as any other local PHP file, without any dependency on the central Widget Storm server.
Purpose & benefits
| Property | Description |
|---|---|
| Speed | Local file reads instead of HTTP requests — load times in the microsecond range |
| Independence | Widgets keep working even when the Widget Storm server is unreachable |
| Redundancy | Dual storage: database (server) + file system (client) |
| Author isolation | Each author has their own subdirectory — overwriting other authors' widgets is not possible |
Directory structure
wgbuilds/
├─ {Author}/
│ ├─ {widget_name}.php
│ ├─ {widget_name}.css
│ └─ {widget_name}.js
└─ WidgetStormSystem/
├─ basic.php
├─ basic.css
├─ basic.js
├─ wg_nav.php
└─ ...
The directory structure is organized by author. During batch upload (uploadwidgets mode), the wghandler scans the wgbuilds/ directory and automatically uploads changed files to the server — processing only your own widgets. Widgets from other authors remain untouched.
Rendering from the CodeVault
In the default mode, the wgclient reads the widget code directly from wgbuilds/ and executes it locally (evalThe CodeVault code is executed via eval() in the server context. The files come from the authenticated download or your own upload — they are identical to the database original. No external input flows into the execution.). The live mode bypasses the CodeVault and queries the Widget Storm server directly — useful during development, but slower than the local cache.