Reference - @xxx@ constructs - else


App: apps - Page: browse
PHP
browse.php
HTML
browse.pad
Result
<?php

/**
* Redact sensitive values from source code before syntax highlighting.
* Replaces values of variables containing sensitive keywords with *REDACTED*
*/
function padColorsFileRedacted($file) {
$source = padFileGet($file);

// Pattern matches: $varName = 'value' or $varName = "value"
// For variables containing sensitive keywords (case-insensitive)
$sensitiveKeywords = [
'password', 'passwd', 'pwd', 'pass',
'secret',
'token',
'apikey', 'api_key',
'appkey', 'app_key',
'credential', 'cred',
'auth',
'private',
'salt',
'hash',
'cipher',
'encrypt',
];

$keywordPattern = implode('|', $sensitiveKeywords);

// Match: $varName = 'value' or $varName = "value" (with optional spaces)
// Captures: 1=variable with $, 2=equals and spaces, 3=quote char, 4=value, 5=closing quote
$pattern = '/(\$\w*(?:' . $keywordPattern . ')\w*)\s*(=\s*)([\'"])(.+?)\3/i';

$source = preg_replace_callback($pattern, function($matches) {
return
$matches[1] . $matches[2] . $matches[3] . '*REDACTED*' . $matches[3];
},
$source);

// Also handle array definitions like 'password'=> '*REDACTED*'
$arrayPattern = '/([\'"](?:' . $keywordPattern . ')[\'"])\s*(=>\s*)([\'"])(.+?)\3/i';

$source = preg_replace_callback($arrayPattern, function($matches) {
return
$matches[1] . $matches[2] . $matches[3] . '*REDACTED*' . $matches[3];
},
$source);

// Apply syntax highlighting to the redacted source. A highlighted PHP source keeps
// its braces, and spliced into a level they read as tags - so they travel escaped,
// the way the .pad walk escapes its own, and the exit puts them back.
if (substr($file, -4) == '.pad') {
return
padColorsString($source);
} else {
return
str_replace(['{', '}'], ['{', '}'], padColorsHighLight($source));
}
}

$title = 'Browse Application';

// Get the apps directory
$appsDir = dirname(APP) . '/';

// Get requested app, directory and file from query string
$app = padRequest ( 'app', '' );
$dir = padRequest ( 'dir', '' );
$file = padRequest ( 'file', '' );

// A list from the query string - app[]=x - names nothing: preg_match got the array and
// the page ended on a TypeError, a 500.
$app = is_string ( $app ) ? $app : '';
$dir = is_string ( $dir ) ? $dir : '';
$file = is_string ( $file ) ? $file : '';

$appPath = '';
$appDirs = [];
$appFiles = [];
$appExts = ['php', 'pad', 'json', 'xml', 'html', 'js', 'css'];
$source = '';
$currentFile = '';
$parentDir = '';

// Validate app name (security: prevent directory traversal). A nested application -
// regression/main, which the index links - is name segments joined by /, still with no
// dot and no empty segment, so it can not climb out.
if ($app && preg_match('/^[a-zA-Z0-9_-]+(\/[a-zA-Z0-9_-]+)*$/', $app)) {

$appPath = $appsDir . $app . '/';

if (
is_dir($appPath)) {

$title = "Browse: $app";

// Validate and set current directory
$currentDir = $appPath;
if (
$dir && preg_match('/^[a-zA-Z0-9_\-\/]+$/', $dir)) {
$testPath = $appPath . $dir . '/';
if (
is_dir($testPath) && strpos(realpath($testPath), realpath($appPath)) === 0) {
$currentDir = $testPath;
$title = "Browse: $app/$dir";

// Calculate parent directory
if (strpos($dir, '/') !== false) {
$parentDir = dirname($dir);
} else {
$parentDir = '';
}
}
} elseif (
$dir) {
// Invalid dir, reset
$dir = '';
}

// Scan current directory for files and subdirectories
$items = scandir($currentDir);

foreach (
$items as $item) {
// Skip hidden files and . / ..
if ($item[0] === '.') continue;

$itemPath = $currentDir . $item;
$relativePath = ($dir ? $dir . '/' : '') . $item;

if (
is_dir($itemPath)) {
$appDirs[] = [
'name' => $item,
'path' => $relativePath
];
} else {
$ext = pathinfo($item, PATHINFO_EXTENSION);
// Only show source files
if (in_array($ext, $appExts)) {
$appFiles[] = [
'path' => $relativePath,
'name' => $item,
'ext' => $ext
];
}
}
}

// Sort directories and files by name
$appDirs = padArrSortBy ( $appDirs, fn ( $one ) => strtolower ( $one ['name'] ) );
$appFiles = padArrSortBy ( $appFiles, fn ( $one ) => strtolower ( $one ['name'] ) );

// If a file is requested, load its source - a file the listing shows, and no other:
// a name with a part that starts with a dot is never listed, yet .env - where padEnv
// finds an application's passwords and keys - was shown in full to anyone who
// typed it, as was a file of any other kind.
if ($file && preg_match('/^[a-zA-Z0-9_\-\/\.]+$/', $file) && !preg_match('#(^|/)\.#', $file)
&&
in_array(pathinfo($file, PATHINFO_EXTENSION), $appExts)) {
$filePath = $appPath . $file;

if (
file_exists($filePath) && strpos(realpath($filePath), realpath($appPath)) === 0) {
$currentFile = $file;
$source = padColorsFileRedacted($filePath);
}
}
}
}

$dirCount = count($appDirs);
$fileCount = count($appFiles);
$hasParent = ($dir !== '');

?>
<style>
.browse-page { text-align: left; display: inline-block; }
.browse-container { display: flex; gap: 20px; }
.file-list { min-width: 150px; }
.file-list ul { list-style: none; padding: 0; margin: 0; }
.file-list li { padding: 2px 0; }
.file-list a { text-decoration: none; }
.file-list a:hover { text-decoration: underline; }
.file-list .current { font-weight: bold; }
.file-list .dir { font-weight: bold; }
.file-list .parent a { font-size: 1.4em; font-weight: bold; }
.source-view { overflow-x: auto; }
.source-view pre { background: #f8f8f8; padding: 10px; border: 1px solid #ddd; margin: 0; }
.back-link { margin-bottom: 10px; }
.path-info { color: #666; margin-bottom: 5px; }
</style>

<div class="browse-page">

{if $app ne ''}

<div class="browse-container">

<div class="file-list">
<ul>
{if $hasParent}
<li class="parent">
{if $parentDir ne ''}
<a href="?browse&app={$app}&dir={$parentDir}">..</a>
@else@
<a href="?browse&app={$app}">..</a>
{/if}
</li>
{/if}
{appDirs}
<li class="dir"><a href="?browse&app={$app}&dir={$path}">{$name}/</a></li>
{/appDirs}
{appFiles}
{if $currentFile eq $path}
<li class="current">{$name}</li>
{/if}
{if $currentFile ne $path}
<li><a href="?browse&app={$app}&dir={$dir}&file={$path}">{$name}</a></li>
{/if}
{/appFiles}
</ul>
</div>

{if $currentFile ne ''}
<div class="source-view">
<h3>{$currentFile}</h3>
{!source}
</div>
{/if}

</div>

{/if}

{if $app eq ''}
<p>No application specified. <a href="?index">Return to applications list</a>.</p>
{/if}
</div>


App: demo - Page: clock
PHP
HTML
clock.pad
Result
{# This page prints the clock, so it says something different every second and can never
match a stored copy - and not only in its digits: AM turns to PM and the weekday changes
name, which digit masking cannot cover. The crawl marks this page uncomparable by name
and only checks that it still answers. #
}

<p>Current date and time information.</p>

<h2>Current Time</h2>

<div class="counter" style="font-size: 36px;">
{clock 'l, F j, Y'}<br>
{clock 'g:i:s A'}
</div>

<h2>Time Formats</h2>

<table>
<tr>
<th>Format</th>
<th>Value</th>
</tr>
<tr>
<td>12-hour format</td>
<td>{clock 'g:i:s A'}</td>
</tr>
<tr>
<td>24-hour format</td>
<td>{clock 'H:i:s'}</td>
</tr>
<tr>
<td>ISO 8601</td>
<td>{clock 'c'}</td>
</tr>
<tr>
<td>RFC 2822</td>
<td>{clock 'r'}</td>
</tr>
<tr>
<td>Unix Timestamp</td>
<td>{clock 'U'}</td>
</tr>
</table>

<h2>Calendar Information</h2>

<table>
<tr>
<th>Property</th>
<th>Value</th>
</tr>
<tr>
<td>Day of Week</td>
<td>{clock 'l'}</td>
</tr>
<tr>
<td>Month</td>
<td>{clock 'F'}</td>
</tr>
<tr>
<td>Year</td>
<td>{clock 'Y'}</td>
</tr>
<tr>
<td>Day of Year</td>
<td>{clock 'z' | + 1} of {if {clock 'L'} eq 1}366@else@365{/if}</td>
</tr>
<tr>
<td>Week of Year</td>
<td>{clock 'W'}</td>
</tr>
<tr>
<td>Days in This Month</td>
<td>{clock 't'}</td>
</tr>
<tr>
<td>Days Remaining in Year</td>
<td>{if {clock 'L'} eq 1}{echo 365 - {clock 'z'}}@else@{echo 364 - {clock 'z'}}{/if}</td>
</tr>
<tr>
<td>Leap Year</td>
<td>{if {clock 'L'} eq 1}Yes@else@No{/if}</td>
</tr>
<tr>
<td>Timezone</td>
<td>{date_default_timezone_get}</td>
</tr>
</table>


App: demo - Page: guestbook
PHP
guestbook.php
HTML
guestbook.pad
Result
<?php

$title
= 'Guestbook';
$dataFile = 'demo/guestbook.json'; // under DATA/, for padFileGet and padFilePut
$error = '';

$entries = json_decode ( padFileGet ( $dataFile ), TRUE ) ?: [];

if (
padRequestIs ( 'POST' ) and padRequest ( 'action' ) == 'add' ) {
$name = padRequest ( 'name', '' );
$comment = padRequest ( 'comment', '' );

// A field posted as a list - name[]=x - is no text: it is asked for again, where
// htmlspecialchars() was handed the array and the post ended on a 500.

if ( is_string ( $name ) and is_string ( $comment ) and padFilled ( $name ) and padFilled ( $comment ) ) {
$entry = [
'name' => htmlspecialchars ( $name ),
'comment' => htmlspecialchars ( $comment ),
'date' => padNow ( 'Y-m-d H:i:s' )
];
array_unshift ( $entries, $entry );
padFilePut ( $dataFile, json_encode ( $entries, JSON_PRETTY_PRINT ) );

// Post, redirect, get: the browser is sent on to a plain GET of this page, so a
// refresh shows the guestbook again instead of signing it a second time. The thanks
// travel as a flash message, shown on that page once.

padFlash ( 'Thank you for signing the guestbook!' );

padRedirect ( 'guestbook' );
}
else
$error = 'Please fill in both name and message.';
}

$hasEntries = count ( $entries ) > 0;

?>
<p>Sign our guestbook and leave a message for other visitors!</p>

{flash}
<div class="message">{$message}</div>
{/flash}

{if $error}
<div class="message error">{$error}</div>
{/if}

<h2>Sign the Guestbook</h2>

<form method="post">
<input type="hidden" name="action" value="add">
<p>
<label for="guestbook-name">Your Name:</label><br>
<input type="text" id="guestbook-name" name="name" required>
</p>
<p>
<label for="guestbook-comment">Your Message:</label><br>
<textarea id="guestbook-comment" name="comment" rows="4" required></textarea>
</p>
<p>
<input type="submit" value="Sign Guestbook">
</p>
</form>

<h2>Guestbook Entries</h2>

{if $hasEntries}
{entries}
<div class="guest-entry">
<div class="guest-name">{$name}</div>
<div class="guest-date">{$date}</div>
<p>{$comment}</p>
</div>
{/entries}
@else@
<p>No entries yet. Be the first to sign!</p>
{/if}


App: demo - Page: highlight
PHP
highlight.php
HTML
highlight.pad
Result
<?php

$title
= 'Syntax Highlighting';

// A query as it would come from a database or a log - a value, coloured by the pipe.

$query = "SELECT name, COUNT(*) AS orders FROM customers JOIN orders USING (customer_id) WHERE country = 'NL' GROUP BY name";

?>
<p>The <code>{ignore}{highlight}{/ignore}</code> tag colours source code on the server - PAD, PHP, HTML,
CSS, JavaScript, JSON, YAML, SQL and Bash. No JavaScript library, and the braces inside need no
<code>&#123;ignore&#125;</code>.</p>

<h2>PHP, with line numbers and a marked line</h2>

{highlight 'php', lines, mark='4'}
<?php

$orders
= db ( "ARRAY * FROM orders WHERE status = 'open'" );
$total = padArrSum ( $orders, 'amount' );

if ( !
$orders )
padRedirect ( 'empty' );

?>
{/highlight}

<p><code>{ignore}{highlight 'php', lines, mark='4'}...{/highlight}{/ignore}</code></p>

<h2>PAD itself</h2>

{highlight 'pad'}
{orders sort='date', first=10}
<li class="{even@orders ? 'even' : 'odd'}">
{$number}: {$total | money}
</li>
@else@
<li>No orders yet</li>
{/orders}
{/highlight}

<h2>CSS and JavaScript</h2>

{highlight 'css'}
.card { padding: 12px 16px; border-radius: 8px; color: #333; }
@media (prefers-color-scheme: dark) { .card { color: #eee; } }
{/highlight}

{highlight 'js'}
// Counts the clicks on every button with a data-count attribute
document.querySelectorAll('[data-count]').forEach(button => {
let clicks = 0;
button.addEventListener('click', () => button.textContent = `Clicked ${++clicks} times`);
});
{/highlight}

<h2>A file of this application</h2>

{highlight file='qr.php'}

<p><code>{ignore}{highlight file='qr.php'}{/ignore}</code> - the language follows the extension.</p>

<h2>A value, through the pipe</h2>

{$query | highlight('sql')}

<p><code>{ignore}{$query | highlight('sql')}{/ignore}</code> - a query kept in a database or a log.</p>


App: demo - Page: todo
PHP
todo.php
HTML
todo.pad
Result
<?php

$title
= 'Todo List';
$dataFile = 'demo/todos.json'; // under DATA/, for padFileGet and padFilePut
$message = $message ?? ''; // todoPost sets it, then restarts this page

// A notice from the request - ?todo&message[]=x - is no text: {if $message} ended the
// page on a 500 over the list.

if ( ! is_string ( $message ) )
$message = '';

$todos = json_decode ( padFileGet ( $dataFile ), TRUE ) ?: [];

$hasTodos = count ( $todos ) > 0;
$doneCount = count ( padArrWhere ( $todos, 'done' ) );
$pendingCount = count ( $todos ) - $doneCount;

?>
<p>Manage your tasks with this simple todo list.</p>

{if $message}
<div class="message">{$message}</div>
{/if}

<h2>Add New Task</h2>

{todo 'add', 'Add Task'}
<input type="text" name="task" aria-label="New task" placeholder="Enter a new task..." required style="flex: 1;">
{/todo}

<h2>Tasks ({$pendingCount} pending, {$doneCount} done)</h2>

{todos}

<table>

<tr>
<th>Task</th>
<th style="width: 120px;">Added</th>
<th style="width: 5%; text-align: center;" colspan="2">Actions</th>
</tr>

@start@

<tr{if $done} class="todo-item done"{/if}>
<td>{$task}</td>
<td style="font-size: 12px; color: #999;">{$date | substr (0, 10)}</td>
<td>
{if ! $done}
{todo 'done', 'Done'}
<input type="hidden" name="id" value="{$id}">
{/todo}
{/if}
</td>
<td>
{todo 'delete', 'Delete'}
<input type="hidden" name="id" value="{$id}">
{/todo}
</td>
</tr>

@end@

</table>

{if $doneCount > 0}
{todo 'clear', 'Clear Completed Tasks'}{/todo}
{/if}

@else@

<p>No tasks yet. Add one above!</p>

{/todos}


App: examples - Page: show
PHP
show.php
HTML
show.pad
Result
<?php

// One example, three panels: the .php and .pad sources coloured by the _common colours
// library, the .html as what it is - the example's rendered output. The name is taken
// from the query, so it is held to the shape a harvest writes and to the store itself.

$exDir = DATA . 'examples/';

// The query travels along on every link, so "back to the search" lands on the results
// the visitor came from - the index runs the same search for a GET as for the form.

$q = padMakeSafe ( $q ?? '', 200 );

$item = padMakeSafe ( $item ?? '', 200 );
$show = '';

if (
$item and preg_match ( '#^[a-zA-Z0-9_/-]+$#', $item ) ) {

$exHtml = realpath ( $exDir . "$item.html" );

if (
$exHtml and str_starts_with ( $exHtml, realpath ( $exDir ) . '/' ) )
$show = $item;

}

$hasPhp = ( $show and file_exists ( $exDir . "$show.php" ) ) ? 1 : 0;
$hasPad = ( $show and file_exists ( $exDir . "$show.pad" ) ) ? 1 : 0;

$srcPhp = ( $hasPhp ) ? padColorsFile ( $exDir . "$show.php" ) : '';
$srcPad = ( $hasPad ) ? padColorsFile ( $exDir . "$show.pad" ) : '';

// The stored render is output as-is, so its braces are escaped the way the colours
// library escapes its own - the exit unescapes them, the level loop never sees them.

$result = ( $show ) ? padEscape ( padFileGet ( $exDir . "$show.html" ) ) : '';

// The example's name is the title, exactly as it is - the common header would space its
// underscores and capitalise it, so the page writes its own.

if ( $show )
$skipTitle = 1;

$title = ( $show ) ? $show : 'Examples';

?>
{if $show eq ''}

<p>Pick an example from the <a href="?index{?q}">search</a>.</p>

@else@

<h1>{$show}</h1>

<p><a href="?index{?q}">back to the search</a></p>

<div class="example-view">

{if $hasPhp eq 1}
<div class="example-panel">
<h4>PHP</h4>
<div class="example-body">{!srcPhp}</div>
</div>
{/if}

{if $hasPad eq 1}
<div class="example-panel">
<h4>PAD</h4>
<div class="example-body">{!srcPad}</div>
</div>
{/if}

<div class="example-panel">
<h4>Result</h4>
<div class="example-body">{!result}</div>
</div>

</div>

{/if}


App: ledger - Page: index
PHP
index.php
HTML
index.pad
Result
<?php

// The month at a glance: what came in, what went out, what is left; six months of it as a
// chart, this month's spending by category, the budgets, the accounts, the latest entries
// - and a quick entry that ledger.js posts as JSON, answered by quick.php.

$totals = ledgerTotals ( $ledgerMonth );
$history = ledgerHistory ( $ledgerMonth );
$spending = ledgerSpending ( $ledgerMonth );
$budgets = ledgerBudgets ( $ledgerMonth );
$accounts = ledgerAccounts ();
$latest = array_slice ( array_values ( array_filter ( ledgerRows ( $ledgerMonth ), fn ( $r ) => $r ['kind'] != 'transfer' ) ), 0, 8 );
$spendable = array_values ( array_filter ( ledgerCategories (), fn ( $c ) => $c ['kind'] == 'expense' ) );
$monthName = ledgerMonthName ( $ledgerMonth );
$today = date ( 'Y-m-d' );

$ledgerPage = $monthName;

?>
<div class="head">
<h1>{$monthName}</h1>
<span class="dim">{$totals.entries} entries</span>
</div>

<section class="kpis" aria-label="This month">
<div class="kpi in"><span>Income</span><b data-kpi="income">{$totals.income | money}</b></div>
<div class="kpi out"><span>Spending</span><b data-kpi="spent">{$totals.spent | money}</b></div>
<div class="kpi"><span>Left</span><b data-kpi="left">{$totals.left | money}</b></div>
<div class="kpi"><span>Kept</span><b data-kpi="kept">{$totals.kept}%</b></div>
</section>

<div class="columns">

<div class="main">

<section class="card">
<h2>Six months</h2>
{chart 'bar', data='history', label='month', value='income, spending', title='Income and spending, six months'}
</section>

<section class="card">
<h2>Where it went</h2>
{if $totals.spent gt 0}
{chart 'donut', data='spending', label='category', value='spent', title='Spending by category'}
{else}
<p class="dim">Nothing spent this month.</p>
{/if}
</section>

<section class="card">
<h2>Budgets <a class="more" href="{$padGo}budgets{?month}">Change</a></h2>
<ul class="budgets">
{budgets}
<li class="{$state}">
<div class="line"><span class="dot" style="background: {$color}"></span><b>{$name}</b><span>{$spent | money} of {$monthly | money}</span></div>
{progress $share, max=100, label=$name, color=$tone}
</li>
@else@
<li class="dim">No budgets yet.</li>
{/budgets}
</ul>
</section>

</div>

<aside class="side">

<section class="card quick">
<h2>Quick entry</h2>
{form 'quick', action='?quick', class='quick-form'}
<input type="hidden" name="month" value="{$ledgerMonth}">
<label for="q-amount">Amount</label>
<input id="q-amount" name="amount" inputmode="decimal" placeholder="12.50" required pattern="[0-9]+([.,][0-9][0-9]?)?">
<label for="q-category">Category</label>
<select id="q-category" name="category">{spendable}<option value="{$id}">{$name}</option>{/spendable}</select>
<label for="q-note">Note</label>
<input id="q-note" name="note" placeholder="Coffee" maxlength="120">
<button class="button primary">Spend it</button>
<p class="said" role="status"></p>
{/form}
</section>

<section class="card">
<h2>Accounts</h2>
<ul class="accounts">
{accounts}<li><span>{$name}</span><b class="{if $balance lt 0}neg{/if}">{$balance | money}</b></li>{/accounts}
</ul>
</section>

<section class="card">
<h2>Latest <a class="more" href="{$padGo}transactions{?month}">All</a></h2>
<ul class="latest">
{latest}<li><span class="day">{$day | date('j M')}</span><span class="what">{$note}<small>{$category}</small></span><b class="{if $amount lt 0}neg{else}pos{/if}">{$amount | money}</b></li>
@else@<li class="dim">No entries this month.</li>{/latest}
</ul>
</section>

</aside>

</div>


App: manual - Page: constructs/else_1


App: manual - Page: constructs/else_2


App: manual - Page: constructs/else_4
PHP
constructs/else_4.php
HTML
constructs/else_4.pad
Result
<?php

$myVar
= FALSE;

?>
{myVar}
true
@else@
false
{/myVar}


App: manual - Page: content/5


App: manual - Page: content/7


App: manual - Page: fragments/collections_2
PHP
fragments/collections_2.php
HTML
fragments/collections_2.pad
Result
<?php

$slug
= 'markdown';

?>
{collection 'news', slug=$slug}
<h3>{$title}</h3>
{!body}
@else@
No such post.
{/collection}


App: manual - Page: fragments/content_3
PHP
HTML
fragments/content_3.pad
Result
{content 'verdict'}Found: @else@Nothing there.{/content}

{data 'hits'}["first","second"]{/data}
{data 'none'}[]{/data}

<p>{hits content='verdict'}{$hits} {/hits}</p>
<p>{none content='verdict'}{$none} {/none}</p>


App: manual - Page: fragments/database_2
PHP
fragments/database_2.php
HTML
fragments/database_2.pad
Result
<?php

$earners
= db ( "array name, salary from staff where salary >= 2000" );

?>
<table border="1">
{earners}
<tr>
<td>{$name}</td>
<td>{$salary}</td>
</tr>
@else@
<tr>
<td>nobody earns that much</td>
</tr>
{/earners}
</table>