none|active|expiring|expired|invalid|wrong_domain|legacy, 'expires' => Y-m-d, 'days_left' => int, * 'serial' => string, 'url' => admin path of the license screen, 'label' => edition or product name] */ /** Who to contact about licenses and support. Set by the release build; empty means "the seller of your license". */ defined('GATECRAFT_SUPPORT_EMAIL') || define('GATECRAFT_SUPPORT_EMAIL', ''); defined('GATECRAFT_RENEW_URL') || define('GATECRAFT_RENEW_URL', ''); defined('GATECRAFT_DOCS_URL') || define('GATECRAFT_DOCS_URL', ''); defined('GATECRAFT_SECURITY_EMAIL') || define('GATECRAFT_SECURITY_EMAIL', ''); add_filter('gatecraft_admin_routes', fn(array $r) => array_merge($r, [ ['GET', '/admin/about', 'a_about', 'checkin'], ])); add_filter('gatecraft_admin_pages', fn(array $p) => $p + [ 'gatecraft-about' => ['About Gatecraft', 'gatecraft_checkin', '/about'], ]); add_filter('gatecraft_setting_defaults', fn(array $d) => $d + ['license_reminder' => '', 'alert_license_app' => '1', 'alert_license_email' => '1']); add_filter('gatecraft_alert_types', fn(array $t) => license_summary() ? $t + ['license' => 'License ending or ended'] : $t); /* Reminders before the license year ends: about 30 days before, 7 days before, and on the day it ends. Each is sent once per license. */ add_filter('gatecraft_worker_jobs', function (array $r) { $l = license_summary(); if (!$l || $l['serial'] === '') return $r; $stage = match (true) { $l['state'] === 'expired' => 'ended', $l['state'] === 'expiring' && $l['days_left'] <= 7 => '7', $l['state'] === 'expiring' => '30', default => '' }; $mark = $l['serial'] . '|' . $l['expires'] . '|' . $stage; if ($stage === '' || setting('license_reminder') === $mark) return $r; // An installation that only notices after the end (worker off for weeks) sends the ended notice, not a late "ending soon" one. if ($stage === 'ended') notify_staff('license', 'Your Gatecraft license has ended', 'Gatecraft keeps working as it is. Renew to get updates and email support again.', $l['url']); else notify_staff('license', 'Your Gatecraft license ends on ' . fmt_date($l['expires']), plural(max(0, $l['days_left']), 'day') . ' of updates and email support left. Renew to continue without a gap.', $l['url']); setting_save(['license_reminder' => $mark]); $r['license_reminder'] = $stage; return $r; }); function is_business_edition(): bool { return defined('GATECRAFT_EDITION_NAME'); } /** Release notes are listed in the admin by the Business Edition only. The other editions publish them on the website. */ function release_notes_on(): bool { return is_business_edition(); } /** Release notes for this edition; the Business Edition supplies them (see its infopages overlay). */ function release_notes(): array { return []; } /** The license as the license module reports it, or null in an edition without one. */ function license_summary(): ?array { $s = apply_filters('gatecraft_license_summary', null); return is_array($s) ? $s : null; } /** A license module's own status, in the shape of license_summary(). Active with 30 days or fewer left is "expiring". */ function license_summary_from(array $s, string $url, string $label): array { $state = (string)($s['state'] ?? 'none'); $days = (int)($s['days_left'] ?? 0); if ($state === 'active' && $days <= 30) $state = 'expiring'; return ['state' => $state, 'expires' => (string)($s['expires'] ?? ''), 'days_left' => $days, 'serial' => (string)($s['serial'] ?? ''), 'domain' => (string)($s['domain'] ?? ''), 'here' => (string)($s['here'] ?? ($s['domain'] ?? '')), 'client' => (string)($s['client'] ?? ''), 'issued' => (string)($s['issued'] ?? ''), 'url' => $url, 'label' => $label]; } /** Where the license year stands: its first day, and how much of it has passed (exact and rounded to 5 for the bar). */ function license_year(array $l): ?array { if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $l['expires'] ?? '')) return null; $end = (int)strtotime($l['expires'] . ' 23:59:59 UTC'); $start = preg_match('/^\d{4}-\d{2}-\d{2}/', $l['issued'] ?? '') ? (int)strtotime(substr($l['issued'], 0, 10) . ' 00:00:00 UTC') : 0; if ($start <= 0 || $start >= $end) $start = (int)strtotime(gmdate('Y-m-d', $end) . ' 00:00:00 UTC -1 year +1 day'); $pct = max(0, min(100, (int)round((time() - $start) / max(1, $end - $start) * 100))); return ['start' => gmdate('Y-m-d', $start), 'pct' => $pct, 'step' => (int)(round($pct / 5) * 5)]; } /** Words for the license state, for headers and banners. */ function license_state_text(array $s): array { return match ($s['state']) { 'active' => ['green', 'Updates and support until ' . fmt_date($s['expires'])], 'expiring' => ['amber', plural(max(0, (int)$s['days_left']), 'day') . ' of updates and support left'], 'expired' => ['red', 'Updates and support ended on ' . fmt_date($s['expires'])], 'none' => ['grey', 'No license yet'], 'legacy' => ['red', 'Older license key, not accepted'], default => ['red', 'License needs attention'], }; } function a_about(): void { render('admin/about_wp', ['title' => 'About Gatecraft', 'nav' => 'about', 'lic' => license_summary(), 'latest' => release_notes()[0] ?? null]); } /* Dashboard: what's new after an update, and the license when it needs attention. */ add_action('gatecraft_slot_dashboard_top', function () { if (!can('settings')) return; $latest = release_notes()[0] ?? null; $seen = ''; if ($latest) { $seen = setting('release_notes_seen'); if ($seen === '') { setting_save(['release_notes_seen' => GATECRAFT_VERSION]); $seen = GATECRAFT_VERSION; } // a fresh install has nothing new to announce } $lic = license_summary(); $vars = ['showNew' => $latest && $seen !== '' && version_compare($seen, GATECRAFT_VERSION, '<'), 'latest' => $latest, 'lic' => $lic && !is_business_edition() && in_array($lic['state'], ['expiring', 'expired', 'invalid', 'wrong_domain'], true) ? $lic : null]; include_template('admin/partials/dashboard_notices', $vars); });