<h1>Global Helper Functions</h1>
<p>
    These functions are available <strong>everywhere</strong> in your pp-php project –
    in controllers, views, and custom code. No class prefix is needed; just call them
    directly. They are auto‑loaded from <code>core/helpers.php</code>.
</p>

<h2>Contents</h2>
<ul>
    <li><a href="#recipes"><strong>Recipes – real-world one-liners</strong></a></li>
    <li><a href="#datetime">Date &amp; Time</a></li>
    <li><a href="#string">String Helpers</a></li>
    <li><a href="#array">Array Helpers</a></li>
    <li><a href="#url">URL &amp; Redirect</a></li>
    <li><a href="#encrypt">Encryption &amp; Hashing</a></li>
    <li><a href="#validation">Validation</a></li>
</ul>

<!-- =========================================== -->
<h2 id="recipes">Recipes – real-world one-liners</h2>
<p class="lead">The helpers shine when you combine them. Common jobs, the short way.</p>

<h3>Stamp a record, then show a friendly time</h3>
<pre><code>// on save
pp()->insert('orders', ['ref' => $ref, 'created_at' => now()]);
// in a list/view
echo 'Placed ' . timeAgo($row['created_at']);        // "5 minutes ago"
echo formatDate($row['created_at'], 'M j, Y g:i a');  // "Jun 15, 2025 2:32 pm"</code></pre>

<h3>Auto-slug a title (unique-safe)</h3>
<pre><code>$slug = slug(post('title'));                          // "My First Post!" → "my-first-post"
if (pp()->table('posts')->where('slug', $slug)->exists()) {
    $slug .= '-' . time();                            // keep it unique
}
pp()->insert('posts', ['title' => post('title'), 'slug' => $slug]);</code></pre>

<h3>A clean preview from rich text</h3>
<pre><code>// strip HTML, then cut to length — perfect for a card or list cell
$preview = truncate(plain($row['body']), 120);
// show user-entered notes safely, keeping line breaks
echo nl2brSafe($row['notes']);</code></pre>

<h3>Validate before you save</h3>
<pre><code>$email = post('email', '');
if (!isEmail($email)) { pp()->message('email', 'Enter a valid email', 'error'); return; }
if (!isUrl(post('website'))) { pp()->message('website', 'Must start with http(s)://', 'error'); return; }
pp()->insert('subscribers', ['email' => $email, 'website' => post('website')]);</code></pre>

<h3>Store a secret encrypted at rest</h3>
<pre><code>$key = getenv('APP_SECRET');                          // never hard-code
pp()->insert('integrations', ['api_key' => encrypt(post('api_key'), $key)]);
// later
$plain = decrypt($row['api_key'], $key);</code></pre>

<h3>First / last of a result set, then go</h3>
<pre><code>$newest = first(pp()->table('orders')->orderBy('created', 'desc')->limit(1)->get());
redirect(pp()->url('generated/orders/view.php?id=' . $newest['id']));</code></pre>

<!-- =========================================== -->
<h2 id="datetime">Date &amp; Time</h2>

<h3>now()</h3>
<p>Returns the current datetime in <code>Y-m-d H:i:s</code> format.</p>
<pre><code>echo now();  // 2025-06-15 14:32:05

// Use with database inserts
DB::Insert('orders', [
    'customer' => 'John',
    'created_at' => now()
]);</code></pre>

<h3>today()</h3>
<p>Returns the current date only (<code>Y-m-d</code>).</p>
<pre><code>echo today();  // 2025-06-15

// Select orders placed today (bind the value — no manual quoting)
$rs = dbAll('SELECT * FROM orders WHERE DATE(created_at) = ?', [today()]);</code></pre>

<h3>timeAgo()</h3>
<p>Converts a datetime into a human‑readable “time ago” string.</p>
<pre><code>echo timeAgo('2025-06-15 14:20:00');  // "5 minutes ago"
echo timeAgo('2025-06-10 09:00:00');  // "5 days ago"
echo timeAgo('2024-12-01 00:00:00');  // "6 months ago"

// Use in a view to show when a record was last updated
echo 'Last updated ' . timeAgo($record['updated_at']);</code></pre>

<h3>formatDate()</h3>
<p>Formats a datetime into any custom pattern.</p>
<pre><code>echo formatDate('2025-06-15 14:32:05');               // "Jun 15, 2025, 2:32 pm"
echo formatDate('2025-06-15', 'l, F j, Y');            // "Sunday, June 15, 2025"
echo formatDate($row['created_at'], 'd/m/Y H:i');       // "15/06/2025 14:32"</code></pre>

<!-- =========================================== -->
<h2 id="string">String Helpers</h2>

<h3>slug()</h3>
<p>Creates a URL‑safe slug from any text.</p>
<pre><code>echo slug('Hello World!');      // "hello-world"
echo slug('Product #123 ABC');   // "product-123-abc"
echo slug('  Multiple   Spaces  ');  // "multiple-spaces"

// Auto‑generate slugs for project names
$slug = slug($_POST['project_name']);</code></pre>

<h3>truncate()</h3>
<p>Cuts a string to a maximum length and appends an ellipsis.</p>
<pre><code>echo truncate('This is a very long description that needs to be shortened.', 20);
// "This is a very long…"

echo truncate('Short text', 100);  // "Short text" (unchanged)

// Custom ellipsis
echo truncate('Long text here', 10, '…read more');
// "Long text…read more"</code></pre>

<h3>nl2brSafe()</h3>
<p>Converts newlines to <code>&lt;br&gt;</code> tags, with HTML escaping for security.</p>
<pre><code>echo nl2brSafe("Line 1\nLine 2\nLine 3");
// Line 1<br>Line 2<br>Line 3

// Safe against XSS
echo nl2brSafe("&lt;script&gt;alert('xss')&lt;/script&gt;\nNew line");
// &amp;lt;script&amp;gt;alert('xss')&amp;lt;/script&amp;gt;<br>New line</code></pre>

<h3>plain()</h3>
<p>Strips all HTML tags, optionally truncates the result.</p>
<pre><code>echo plain('&lt;b&gt;Bold text&lt;/b&gt; and &lt;i&gt;italic&lt;/i&gt;');
// "Bold text and italic"

echo plain('&lt;p&gt;A long paragraph&lt;/p&gt;', 10);
// "A long par…"</code></pre>

<!-- =========================================== -->
<h2 id="array">Array Helpers</h2>

<h3>first()</h3>
<p>Returns the first element of an array, or <code>null</code> if empty.</p>
<pre><code>$users = DB::Select('users');
$firstUser = first($users);
echo $firstUser['name'];  // "Alice"</code></pre>

<h3>last()</h3>
<p>Returns the last element of an array, or <code>null</code> if empty.</p>
<pre><code>$logs = DB::Select('activity_log');
$lastEntry = last($logs);
echo $lastEntry['event'];  // "User logged out"</code></pre>

<!-- =========================================== -->
<h2 id="url">URL &amp; Redirect</h2>

<h3>redirect()</h3>
<p>Sends an HTTP redirect and stops execution immediately.</p>
<pre><code>// Redirect to the login page
redirect('/login');

// Redirect to an external URL
redirect('https://example.com/dashboard');

// After a successful form submission
redirect('/projects?created=1');</code></pre>

<h3>get() / post()</h3>
<p>Safely retrieve values from <code>$_GET</code> or <code>$_POST</code> with a default fallback.</p>
<pre><code>$page = get('page', 1);          // current page number, defaults to 1
$search = get('q', '');          // search term, defaults to empty string
$sort = get('sort', 'name ASC'); // sorting, with a default

$email = post('email', '');      // from a form
$remember = post('remember', false);

// Combine with validation
$id = (int) get('id', 0);
if ($id <= 0) {
    redirect('/error?msg=invalid_id');
}</code></pre>

<!-- =========================================== -->
<h2 id="encrypt">Encryption &amp; Hashing</h2>

<h3>encrypt() / decrypt()</h3>
<p>Encrypt/decrypt strings using AES‑256‑CBC. Useful for storing sensitive data
in the database.</p>
<pre><code>$key = 'your-32-byte-secret-key-here!!';  // keep this safe!

// Encrypt a credit card number before storage
$encrypted = encrypt('4111-1111-1111-1111', $key);
DB::Insert('payments', ['card_hash' => $encrypted]);

// Decrypt for display
$card = decrypt($encrypted, $key);
echo 'Card ending in ' . substr($card, -4);   // "Card ending in 1111"</code></pre>

<!-- =========================================== -->
<h2 id="validation">Validation</h2>

<h3>isEmail()</h3>
<p>Checks if a string is a valid email address.</p>
<pre><code>if (!isEmail(post('email'))) {
    echo 'Please enter a valid email address.';
}

// Validate from a form
$email = post('email', '');
if (isEmail($email)) {
    DB::Insert('subscribers', ['email' => $email]);
}</code></pre>

<h3>isUrl()</h3>
<p>Checks if a string is a valid URL.</p>
<pre><code>if (!isUrl(post('website'))) {
    echo 'Please enter a valid URL starting with http:// or https://';
}

// Use in a loop
foreach ($links as $url) {
    if (isUrl($url)) {
        echo '&lt;a href="' . $url . '"&gt;Valid link&lt;/a&gt;';
    }
}</code></pre>

<h3>isJson()</h3>
<p>Checks if a string is valid JSON.</p>
<pre><code>$input = '{"name": "John", "age": 30}';
if (isJson($input)) {
    $data = json_decode($input, true);
    echo $data['name'];  // "John"
} else {
    echo 'Invalid JSON format.';
}</code></pre>

<hr>
<h3>🔗 Related Help Topics</h3>
<p>
    <a href="DB" class="btn btn-sm btn-outline-primary me-1">Database API</a>
    <a href="Security" class="btn btn-sm btn-outline-primary me-1">Security API</a>
</p>