475765e31f
Add an explicit template renderer with HTML views and partials for the app, bootstrap, package, and catalog pages. Move shared reporting setup into config/reporting.php and relocate stylesheet assets under css/.
526 lines
20 KiB
PHP
526 lines
20 KiB
PHP
<?php
|
|
/*
|
|
* prompts.php
|
|
*
|
|
* User/admin prompt administration page.
|
|
*
|
|
* Normal users:
|
|
* - manage personal prompts
|
|
* - copy global default prompts to their own prompts
|
|
*
|
|
* Admin users:
|
|
* - same as normal users
|
|
* - manage global default prompts
|
|
*/
|
|
|
|
require_once __DIR__ . '/private/auth.php';
|
|
require_once __DIR__ . '/private/header.php';
|
|
require_once __DIR__ . '/private/languagestore.php';
|
|
require_once __DIR__ . '/private/promptstore.php';
|
|
require_once __DIR__ . '/private/usersettings.php';
|
|
require_once __DIR__ . '/private/viewdata.php';
|
|
|
|
require_once __DIR__ . '/config/reporting.php';
|
|
|
|
$DB_FILE = __DIR__ . '/data/racket-sandbox.sqlite';
|
|
|
|
$auth = new RacketSandboxAuth($DB_FILE);
|
|
$user = $auth->requireLoginHtml();
|
|
|
|
$store = new PromptStore($DB_FILE);
|
|
$languageStore = new LanguageStore($DB_FILE);
|
|
$userSettings = new UserSettingsStore($DB_FILE);
|
|
|
|
$message = '';
|
|
$error = '';
|
|
|
|
function h($s)
|
|
{
|
|
return htmlspecialchars((string)$s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
|
}
|
|
|
|
function t($key, $fallback = null, $values = array())
|
|
{
|
|
global $languageStore, $language;
|
|
|
|
return $languageStore->translateFormat($key, $language, $values, $fallback);
|
|
}
|
|
|
|
function post_value($name, $default = '')
|
|
{
|
|
return $_POST[$name] ?? $default;
|
|
}
|
|
|
|
function get_value($name, $default = '')
|
|
{
|
|
return $_GET[$name] ?? $default;
|
|
}
|
|
|
|
function fmt_time($ts)
|
|
{
|
|
if ($ts === null) {
|
|
return '-';
|
|
}
|
|
|
|
return date('Y-m-d H:i:s', (int)$ts);
|
|
}
|
|
|
|
function prompt_to_array($prompt)
|
|
{
|
|
return array(
|
|
'id' => $prompt->id(),
|
|
'name' => $prompt->name(),
|
|
'language' => $prompt->language(),
|
|
'content' => $prompt->content(),
|
|
'default_key' => $prompt->defaultKey(),
|
|
'is_default' => $prompt->isDefault(),
|
|
'created_at' => $prompt->createdAt(),
|
|
'updated_at' => $prompt->updatedAt(),
|
|
);
|
|
}
|
|
|
|
function version_to_array($version)
|
|
{
|
|
return array(
|
|
'id' => $version->id(),
|
|
'prompt_id' => $version->promptId(),
|
|
'version_no' => $version->versionNo(),
|
|
'name' => $version->name(),
|
|
'language' => $version->language(),
|
|
'content' => $version->content(),
|
|
'note' => $version->note(),
|
|
'created_at' => $version->createdAt(),
|
|
);
|
|
}
|
|
|
|
function resolve_user_language($userSettings, $userId, $allowedLanguages)
|
|
{
|
|
$language = isset($_GET['lang'])
|
|
? (string)$_GET['lang']
|
|
: (string)$userSettings->get($userId, 'language', 'en');
|
|
|
|
if (!in_array($language, $allowedLanguages, true)) {
|
|
$language = 'en';
|
|
}
|
|
|
|
$userSettings->set($userId, 'language', $language);
|
|
|
|
return $language;
|
|
}
|
|
|
|
$language = resolve_user_language(
|
|
$userSettings,
|
|
$user->id(),
|
|
$store->supportedLanguages()
|
|
);
|
|
|
|
seed_template_translations($languageStore, 'prompts.html');
|
|
|
|
$mode = get_value('mode', 'personal');
|
|
|
|
if ($mode !== 'personal' && $mode !== 'defaults') {
|
|
$mode = 'personal';
|
|
}
|
|
|
|
if ($mode === 'defaults' && !$user->isAdmin()) {
|
|
$mode = 'personal';
|
|
}
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
$action = post_value('action');
|
|
|
|
try {
|
|
/*
|
|
* User actions.
|
|
*/
|
|
if ($action === 'logout') {
|
|
$auth->logout();
|
|
header('Location: /login.php');
|
|
exit;
|
|
} elseif ($action === 'copy_all_defaults') {
|
|
$language = post_value('language', 'en');
|
|
$created = $store->copyAllDefaultsToUser($user->id(), $language);
|
|
$message = 'Default prompts copied to your prompts: ' . $created;
|
|
$mode = 'personal';
|
|
} elseif ($action === 'copy_default') {
|
|
$defaultId = (int)post_value('default_id');
|
|
$store->copyDefaultPromptToUser($user->id(), $defaultId);
|
|
$message = 'Default prompt copied to your prompts.';
|
|
$mode = 'personal';
|
|
} elseif ($action === 'create_prompt') {
|
|
$language = post_value('language', 'en');
|
|
|
|
$store->createPrompt(
|
|
$user->id(),
|
|
post_value('name'),
|
|
$language,
|
|
post_value('content')
|
|
);
|
|
|
|
$message = 'Prompt created.';
|
|
$mode = 'personal';
|
|
} elseif ($action === 'update_prompt') {
|
|
$promptId = (int)post_value('prompt_id');
|
|
$language = post_value('language', 'en');
|
|
|
|
$store->updatePrompt(
|
|
$user->id(),
|
|
$promptId,
|
|
post_value('name'),
|
|
$language,
|
|
post_value('content'),
|
|
isset($_POST['create_version']),
|
|
post_value('version_note')
|
|
);
|
|
|
|
$message = 'Prompt updated.';
|
|
$mode = 'personal';
|
|
} elseif ($action === 'create_version') {
|
|
$promptId = (int)post_value('prompt_id');
|
|
$store->createVersion($user->id(), $promptId, post_value('version_note'));
|
|
$message = 'Version stored.';
|
|
$mode = 'personal';
|
|
} elseif ($action === 'restore_version') {
|
|
$promptId = (int)post_value('prompt_id');
|
|
$versionNo = (int)post_value('version_no');
|
|
$store->restoreVersion($user->id(), $promptId, $versionNo);
|
|
$message = 'Version restored.';
|
|
$mode = 'personal';
|
|
} elseif ($action === 'delete_version') {
|
|
$promptId = (int)post_value('prompt_id');
|
|
$versionNo = (int)post_value('version_no');
|
|
$store->deleteVersion($user->id(), $promptId, $versionNo);
|
|
$message = 'Version deleted.';
|
|
$mode = 'personal';
|
|
} elseif ($action === 'delete_prompt') {
|
|
$promptId = (int)post_value('prompt_id');
|
|
$store->deletePrompt($user->id(), $promptId);
|
|
$message = 'Prompt deleted.';
|
|
$mode = 'personal';
|
|
}
|
|
|
|
/*
|
|
* Admin-only default prompt actions.
|
|
*/
|
|
elseif ($action === 'create_default' ||
|
|
$action === 'update_default' ||
|
|
$action === 'create_default_version' ||
|
|
$action === 'restore_default_version' ||
|
|
$action === 'delete_default_version' ||
|
|
$action === 'delete_default') {
|
|
|
|
if (!$user->isAdmin()) {
|
|
throw new Exception('Admin rights required.');
|
|
}
|
|
|
|
$mode = 'defaults';
|
|
|
|
if ($action === 'create_default') {
|
|
$language = post_value('language', 'en');
|
|
|
|
$store->createDefaultPrompt(
|
|
post_value('default_key'),
|
|
post_value('name'),
|
|
$language,
|
|
post_value('content')
|
|
);
|
|
|
|
$message = 'Default prompt created.';
|
|
} elseif ($action === 'update_default') {
|
|
$defaultId = (int)post_value('default_id');
|
|
$language = post_value('language', 'en');
|
|
|
|
$store->updateDefaultPrompt(
|
|
$defaultId,
|
|
post_value('default_key'),
|
|
post_value('name'),
|
|
$language,
|
|
post_value('content'),
|
|
isset($_POST['create_version']),
|
|
post_value('version_note')
|
|
);
|
|
|
|
$message = 'Default prompt updated.';
|
|
} elseif ($action === 'create_default_version') {
|
|
$defaultId = (int)post_value('default_id');
|
|
$store->createDefaultVersion($defaultId, post_value('version_note'));
|
|
$message = 'Default prompt version stored.';
|
|
} elseif ($action === 'restore_default_version') {
|
|
$defaultId = (int)post_value('default_id');
|
|
$versionNo = (int)post_value('version_no');
|
|
$store->restoreDefaultVersion($defaultId, $versionNo);
|
|
$message = 'Default prompt version restored.';
|
|
} elseif ($action === 'delete_default_version') {
|
|
$defaultId = (int)post_value('default_id');
|
|
$versionNo = (int)post_value('version_no');
|
|
$store->deleteDefaultVersion($defaultId, $versionNo);
|
|
$message = 'Default prompt version deleted.';
|
|
} elseif ($action === 'delete_default') {
|
|
$defaultId = (int)post_value('default_id');
|
|
$store->deleteDefaultPrompt($defaultId);
|
|
$message = 'Default prompt deleted.';
|
|
}
|
|
} elseif ($action !== '') {
|
|
throw new Exception(t('prompts.unknown_action', 'Unknown action: {{action}}', array('action' => $action)));
|
|
}
|
|
} catch (Throwable $e) {
|
|
$error = $e->getMessage();
|
|
}
|
|
}
|
|
|
|
$personalPrompts = $store->listPrompts($user->id(), $language);
|
|
$defaultPrompts = $store->listDefaultPrompts($language);
|
|
|
|
/*
|
|
* Full prompt data for the modal editor.
|
|
*/
|
|
$allPersonalPrompts = $store->listPrompts($user->id(), null);
|
|
$allDefaultPrompts = $store->listDefaultPrompts(null);
|
|
|
|
$promptData = array(
|
|
'personal' => array(),
|
|
'default' => array(),
|
|
'can_edit_defaults' => $user->isAdmin(),
|
|
);
|
|
|
|
foreach ($allPersonalPrompts as $p) {
|
|
$promptData['personal'][$p->id()] = prompt_to_array($p);
|
|
$promptData['personal'][$p->id()]['versions'] = array();
|
|
|
|
foreach ($store->listVersions($user->id(), $p->id()) as $v) {
|
|
$promptData['personal'][$p->id()]['versions'][] = version_to_array($v);
|
|
}
|
|
}
|
|
|
|
foreach ($allDefaultPrompts as $p) {
|
|
$promptData['default'][$p->id()] = prompt_to_array($p);
|
|
$promptData['default'][$p->id()]['versions'] = array();
|
|
|
|
if ($user->isAdmin()) {
|
|
foreach ($store->listDefaultVersions($p->id()) as $v) {
|
|
$promptData['default'][$p->id()]['versions'][] = version_to_array($v);
|
|
}
|
|
}
|
|
}
|
|
|
|
$headerLanguages = array();
|
|
|
|
foreach ($store->supportedLanguages() as $lang) {
|
|
$headerLanguages[$lang] = $store->languageLabel($lang);
|
|
}
|
|
|
|
$headerNavItems = array(
|
|
array(
|
|
'label' => t('prompts.back', 'Back to Racket sandbox'),
|
|
'url' => '/',
|
|
),
|
|
);
|
|
|
|
if ($user->isAdmin()) {
|
|
$headerNavItems[] = array(
|
|
'label' => t('app.user_management', 'User management'),
|
|
'url' => '/users?lang=' . rawurlencode($language),
|
|
'separator_before' => true,
|
|
);
|
|
$headerNavItems[] = array(
|
|
'label' => t('app.configuration', 'Configuration'),
|
|
'url' => '/admin-config?lang=' . rawurlencode($language),
|
|
'separator_before' => true,
|
|
);
|
|
}
|
|
|
|
$styleVersion = @filemtime(__DIR__ . '/css/styles.css') ?: time();
|
|
$promptEditorVersion = @filemtime(__DIR__ . '/js/prompt-editor.js') ?: time();
|
|
|
|
header('Content-Type: text/html; charset=utf-8');
|
|
|
|
$languageOptionsHtml = '';
|
|
$languageOptionsPlainHtml = '';
|
|
|
|
foreach ($store->supportedLanguages() as $lang) {
|
|
$languageOptionsHtml .= RacketSandboxTemplate::renderFile('partials/select-option.html', array(
|
|
'value' => $lang,
|
|
'selected' => $lang === $language ? ' selected' : '',
|
|
'label' => $store->languageLabel($lang),
|
|
)) . "\n";
|
|
|
|
$languageOptionsPlainHtml .= RacketSandboxTemplate::renderFile('partials/select-option.html', array(
|
|
'value' => $lang,
|
|
'selected' => '',
|
|
'label' => $store->languageLabel($lang),
|
|
)) . "\n";
|
|
}
|
|
|
|
$defaultAdminNoticeHtml = '';
|
|
|
|
if ($user->isAdmin()) {
|
|
$defaultAdminNoticeHtml = RacketSandboxTemplate::renderFile('partials/prompt-admin-notice.html', array(
|
|
'badge' => t('prompts.default_admin_badge', 'Admin default prompts'),
|
|
'hint' => t('prompts.default_admin_hint', 'You are editing global default prompts. Users can copy these to their own prompts.'),
|
|
));
|
|
}
|
|
|
|
$defaultPromptsHtml = '';
|
|
|
|
foreach ($defaultPrompts as $default) {
|
|
$adminDeleteHtml = '';
|
|
|
|
if ($user->isAdmin()) {
|
|
$adminDeleteHtml = RacketSandboxTemplate::renderFile('partials/prompt-default-delete.html', array(
|
|
'language_url' => rawurlencode($language),
|
|
'confirm_json' => json_encode(t('prompts.delete_default_confirm', 'Delete default prompt {{name}}?', array(
|
|
'name' => $default->name(),
|
|
)), JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT),
|
|
'name' => $default->name(),
|
|
'id' => $default->id(),
|
|
'delete_label' => t('prompts.delete', 'delete'),
|
|
));
|
|
}
|
|
|
|
$defaultPromptsHtml .= RacketSandboxTemplate::renderFile('partials/prompt-default-item.html', array(
|
|
'language_url' => rawurlencode($language),
|
|
'id' => $default->id(),
|
|
'name' => $default->name(),
|
|
'default_key' => $default->defaultKey(),
|
|
'metadata' => t('prompts.default_metadata', '{{default_key}} · {{updated_at}}', array(
|
|
'default_key' => '<code>' . h($default->defaultKey()) . '</code>',
|
|
'updated_at' => fmt_time($default->updatedAt()),
|
|
)),
|
|
'copy_label' => t('prompts.copy', 'copy'),
|
|
'admin_delete_html' => $adminDeleteHtml,
|
|
)) . "\n";
|
|
}
|
|
|
|
$noDefaultsHtml = count($defaultPrompts) === 0
|
|
? RacketSandboxTemplate::renderFile('partials/paragraph.html', array(
|
|
'class' => 'empty-state',
|
|
'text' => t('prompts.no_defaults', 'No default prompts for this language yet.'),
|
|
))
|
|
: '';
|
|
|
|
$createDefaultHtml = '';
|
|
|
|
if ($user->isAdmin()) {
|
|
$createDefaultHtml = RacketSandboxTemplate::renderFile('partials/prompt-create-default.html', array(
|
|
'language_url' => rawurlencode($language),
|
|
'create_default_label' => t('prompts.create_default', 'Create default prompt'),
|
|
'default_key_label' => t('prompts.default_key', 'Default key'),
|
|
'name_label' => t('prompts.name', 'Name'),
|
|
'language_label' => t('prompts.language', 'Language'),
|
|
'language_options_html' => $languageOptionsHtml,
|
|
'prompt_content_label' => t('prompts.prompt_content', 'Prompt content'),
|
|
));
|
|
}
|
|
|
|
$personalPromptsHtml = '';
|
|
|
|
foreach ($personalPrompts as $prompt) {
|
|
$personalPromptsHtml .= RacketSandboxTemplate::renderFile('partials/prompt-personal-item.html', array(
|
|
'language_url' => rawurlencode($language),
|
|
'id' => $prompt->id(),
|
|
'name' => $prompt->name(),
|
|
'metadata' => t('prompts.personal_metadata', '{{language}} · {{updated_at}}', array(
|
|
'language' => $store->languageLabel($prompt->language()),
|
|
'updated_at' => fmt_time($prompt->updatedAt()),
|
|
)),
|
|
'confirm_json' => json_encode(t('prompts.delete_prompt_confirm', 'Delete prompt {{name}}?', array(
|
|
'name' => $prompt->name(),
|
|
)), JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT),
|
|
'delete_label' => t('prompts.delete', 'delete'),
|
|
)) . "\n";
|
|
}
|
|
|
|
$noPersonalHtml = count($personalPrompts) === 0
|
|
? RacketSandboxTemplate::renderFile('partials/paragraph.html', array(
|
|
'class' => 'empty-state',
|
|
'text' => t('prompts.no_personal', 'No personal prompts yet for this language.'),
|
|
))
|
|
: '';
|
|
|
|
$promptTextJson = json_encode(array(
|
|
'prompt_not_found' => t('prompts.prompt_not_found', 'Prompt not found'),
|
|
'no_previous_versions' => t('prompts.no_previous_versions', 'No previous versions stored.'),
|
|
'no_lines_for_view' => t('prompts.no_lines_for_view', 'No lines for this view.'),
|
|
'restore_version_confirm' => t('prompts.restore_version_confirm', 'Restore version'),
|
|
'delete_version_confirm' => t('prompts.delete_version_confirm', 'Delete version'),
|
|
'default_prompt_prefix' => t('prompts.default_prompt_prefix', 'Default prompt: '),
|
|
'prompt_prefix' => t('prompts.prompt_prefix', 'Prompt: '),
|
|
'created' => t('prompts.created', 'created'),
|
|
'updated' => t('prompts.updated', 'updated'),
|
|
'default_prompt' => t('prompts.default_prompt', 'default prompt'),
|
|
'version' => t('prompts.version', 'version'),
|
|
'showing_version' => t('prompts.showing_version', 'showing version'),
|
|
'of' => t('prompts.of', 'of'),
|
|
'old' => t('prompts.old', 'old'),
|
|
'new' => t('prompts.new', 'new'),
|
|
), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
|
|
|
$headerHtml = app_header_html(array(
|
|
'title' => t('prompts.title', 'Prompt administration'),
|
|
'nav_items' => $headerNavItems,
|
|
'user' => $user,
|
|
'admin_label' => t('app.admin', 'admin'),
|
|
'language_label' => t('prompts.language', 'Language'),
|
|
'language' => $language,
|
|
'languages' => $headerLanguages,
|
|
'language_action' => '/prompts',
|
|
'language_hidden' => array('mode' => $mode),
|
|
'logout_action' => '/prompts?lang=' . rawurlencode($language) . '&mode=' . rawurlencode($mode),
|
|
'logout_label' => t('app.logout', 'Logout'),
|
|
'message' => $message,
|
|
'error' => $error,
|
|
));
|
|
|
|
echo RacketSandboxTemplate::renderFile('prompts.html', array(
|
|
'language' => $language,
|
|
'language_url' => rawurlencode($language),
|
|
'mode_url' => rawurlencode($mode),
|
|
'title' => t('prompts.title', 'Prompt administration'),
|
|
'style_version' => $styleVersion,
|
|
'prompt_editor_version' => $promptEditorVersion,
|
|
'header_html' => $headerHtml,
|
|
'defaults_tab_active_class' => $mode === 'defaults' ? 'active' : '',
|
|
'defaults_tab_selected' => $mode === 'defaults' ? 'true' : 'false',
|
|
'personal_tab_active_class' => $mode !== 'defaults' ? 'active' : '',
|
|
'personal_tab_selected' => $mode !== 'defaults' ? 'true' : 'false',
|
|
'available_defaults_label' => t('prompts.available_defaults', 'Available default prompts'),
|
|
'your_prompts_label' => t('prompts.your_prompts', 'Your prompts'),
|
|
'default_admin_notice_html' => $defaultAdminNoticeHtml,
|
|
'copy_all_label' => t('prompts.copy_all', 'Copy all'),
|
|
'default_prompts_html' => $defaultPromptsHtml,
|
|
'no_defaults_html' => $noDefaultsHtml,
|
|
'create_default_html' => $createDefaultHtml,
|
|
'personal_prompts_html' => $personalPromptsHtml,
|
|
'no_personal_html' => $noPersonalHtml,
|
|
'create_personal_label' => t('prompts.create_personal', 'Create personal prompt'),
|
|
'name_label' => t('prompts.name', 'Name'),
|
|
'language_label' => t('prompts.language', 'Language'),
|
|
'language_options_html' => $languageOptionsHtml,
|
|
'language_options_plain_html' => $languageOptionsPlainHtml,
|
|
'prompt_content_label' => t('prompts.prompt_content', 'Prompt content'),
|
|
'select_prompt_label' => t('prompts.select_prompt', 'Select a prompt on the left to view it.'),
|
|
'prompt_label' => t('prompts.prompt', 'Prompt'),
|
|
'edit_label' => t('prompts.edit', 'Edit'),
|
|
'back_label' => t('prompts.back', 'Back to Racket sandbox'),
|
|
'close_label' => t('prompts.close', 'Close'),
|
|
'newer_previous_label' => t('prompts.newer_previous', 'newer previous version'),
|
|
'older_previous_label' => t('prompts.older_previous', 'older previous version'),
|
|
'diff_view_label' => t('prompts.diff_view', 'Diff view:'),
|
|
'diff_plain_label' => t('prompts.diff_plain', 'text, no diff'),
|
|
'diff_all_label' => t('prompts.diff_all', 'all diff'),
|
|
'diff_same_label' => t('prompts.diff_same', 'unchanged only'),
|
|
'diff_added_label' => t('prompts.diff_added', 'additions only'),
|
|
'diff_deleted_label' => t('prompts.diff_deleted', 'deletions only'),
|
|
'diff_changed_label' => t('prompts.diff_changed', 'changes only'),
|
|
'default_key_label' => t('prompts.default_key', 'Default key'),
|
|
'previous_version_label' => t('prompts.previous_version', 'Previous version'),
|
|
'store_version_label' => t('prompts.store_version', 'store this edit as a new version'),
|
|
'version_note_label' => t('prompts.version_note', 'Version note:'),
|
|
'save_label' => t('prompts.save', 'Save'),
|
|
'store_snapshot_label' => t('prompts.store_snapshot', 'Store snapshot'),
|
|
'restore_version_label' => t('prompts.restore_version', 'Restore selected version'),
|
|
'delete_version_label' => t('prompts.delete_version', 'Delete selected version'),
|
|
'prompt_data_json' => json_encode($promptData, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
|
|
'prompt_text_json' => $promptTextJson,
|
|
));
|