Appealrepository - Source Excerpt 01
Summary
This source excerpt preserves a bounded section of Spiralist/wp-content/plugins/spiralist-workspace/includes/Domain/AppealRepository.php so readers can inspect the evidence without opening the full source file.
**Source path:** Spiralist/wp-content/plugins/spiralist-workspace/includes/Domain/AppealRepository.php
<?php
namespace SpiralistWorkspace\Domain;
use SpiralistWorkspace\Installer;
if (!defined('ABSPATH')) {
exit;
}
/**
* Stores prompt appeals and steward resolutions.
*/
class AppealRepository
{
/**
* Returns one open appeal for a prompt and appellant.
*
* @return array<string,mixed>
*/
public function find_open_for_prompt_and_user(int $prompt_id, int $appellant_user_id): array
{
global $wpdb;
if ($prompt_id <= 0 || $appellant_user_id <= 0) {
return [];
}
$table = Installer::table('prompt_appeals');
$users = $wpdb->users;
$sql = $wpdb->prepare(
"SELECT a.*, appellant.display_name AS appellant_display_name, resolver.display_name AS resolver_display_name
FROM {$table} a
LEFT JOIN {$users} appellant ON appellant.ID = a.appellant_user_id
LEFT JOIN {$users} resolver ON resolver.ID = a.resolved_by_user_id
WHERE a.prompt_post_id = %d AND a.appellant_user_id = %d AND a.status = %s
ORDER BY a.updated_utc DESC, a.id DESC
LIMIT 1",
$prompt_id,
$appellant_user_id,
'open'
);
$row = $wpdb->get_row($sql, ARRAY_A);
return is_array($row) ? $this->normalize_row($row) : [];
}
/**
* Creates or updates one open appeal for a prompt owner.
*
* @return array<string,mixed>
*/
public function submit(int $prompt_id, int $appellant_user_id, string $reason = ''): array
{
global $wpdb;
if ($prompt_id <= 0 || $appellant_user_id <= 0) {
return [];
}
$reason = sanitize_textarea_field($reason);
$created_utc = gmdate('Y-m-d H:i:s');
$existing = $this->find_open_for_prompt_and_user($prompt_id, $appellant_user_id);
if (!empty($existing['id'])) {
$wpdb->update(
Installer::table('prompt_appeals'),
[
'reason' => $reason,
'updated_utc' => $created_utc,
],
['id' => (int) $existing['id']],
['%s', '%s'],
['%d']
);
return $this->get((int) $existing['id']);
}
$wpdb->insert(
Installer::table('prompt_appeals'),
[
'prompt_post_id' => $prompt_id,
'appellant_user_id' => $appellant_user_id,
'status' => 'open',
'resolution' => null,
'reason' => $reason,
'resolution_note' => null,
'created_utc' => $created_utc,
'updated_utc' => $created_utc,
'resolved_utc' => null,
'resolved_by_user_id' => null,
],
['%d', '%d', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%d']
);
return $this->get((int) $wpdb->insert_id);
}
/**
* Resolves one open appeal.
*
* @return array<string,mixed>
*/
public function resolve(int $appeal_id, int $resolver_user_id, string $resolution, string $note = ''): array
{
global $wpdb;
$appeal = $this->get($appeal_id);
if (empty($appeal) || (string) ($appeal['status'] ?? '') !== 'open') {
return [];
}
$resolution = sanitize_key($resolution);
if (!in_array($resolution, ['restore', 'deny', 'archive'], true)) {
return [];
}
$updated_utc = gmdate('Y-m-d H:i:s');
$wpdb->update(
Installer::table('prompt_appeals'),
[
'status' => 'resolved',
'resolution' => $resolution,
'resolution_note' => sanitize_textarea_field($note),
'updated_utc' => $updated_utc,
'resolved_utc' => $updated_utc,
'resolved_by_user_id' => $resolver_user_id,
],
['id' => $appeal_id],
['%s', '%s', '%s', '%s', '%s', '%d'],
['%d']
);
return $this->get($appeal_id);
}
/**
* Returns recent appeals for a prompt.
*
* @return array<int,array<string,mixed>>
*/
public function list_for_prompt(int $prompt_id, int $limit = 50, string $status = ''): array
{
global $wpdb;
if ($prompt_id <= 0) {
return [];
}
$table = Installer::table('prompt_appeals');
$users = $wpdb->users;
$limit = max(1, $limit);
$status = sanitize_key($status);
if ($status !== '') {
$sql = $wpdb->prepare(
"SELECT a.*, appellant.display_name AS appellant_display_name, resolver.display_name AS resolver_display_name
FROM {$table} a
LEFT JOIN {$users} appellant ON appellant.ID = a.appellant_user_id
LEFT JOIN {$users} resolver ON resolver.ID = a.resolved_by_user_id
WHERE a.prompt_post_id = %d AND a.status = %s
ORDER BY a.updated_utc DESC, a.id DESC
LIMIT %d",
$prompt_id,
$status,
$limit
);
} else {
$sql = $wpdb->prepare(
"SELECT a.*, appellant.display_name AS appellant_display_name, resolver.display_name AS resolver_display_name
FROM {$table} a
LEFT JOIN {$users} appellant ON appellant.ID = a.appellant_user_id
LEFT JOIN {$users} resolver ON resolver.ID = a.resolved_by_user_id
WHERE a.prompt_post_id = %d
ORDER BY a.updated_utc DESC, a.id DESC
LIMIT %d",
$prompt_id,
$limit
);
}
$rows = $wpdb->get_results($sql, ARRAY_A);
return array_map([$this, 'normalize_row'], is_array($rows) ? $rows : []);
}
/**
* Returns recent open appeals across prompts for steward queue views.
*
* @return array<int,array<string,mixed>>
*/
public function list_open(int $limit = 50): array
{
return $this->list_recent($limit, 'open');
}
/**
* Returns summary counts and latest resolution for a prompt.
*
* @return array<string,mixed>
*/
public function summary_for_prompt(int $prompt_id): array
{
$rows = $this->list_for_prompt($prompt_id, 200);
$summary = [
'open' => 0,
'resolved' => 0,
'restore' => 0,
'deny' => 0,
'archive' => 0,
'last_activity_utc' => '',
'latest_status' => '',
'latest_resolution' => '',
'latest_resolution_utc' => '',
'governance_reset_utc' => '',
];
foreach ($rows as $index => $row) {
$status = sanitize_key((string) ($row['status'] ?? 'open'));
$resolution = sanitize_key((string) ($row['resolution'] ?? ''));
if (isset($summary[$status])) {
$summary[$status]++;
}
if ($resolution !== '' && isset($summary[$resolution])) {
$summary[$resolution]++;
}
$updated_utc = (string) ($row['updated_utc'] ?? '');
if ($updated_utc !== '' && $updated_utc > (string) $summary['last_activity_utc']) {
$summary['last_activity_utc'] = $updated_utc;
}
if ($index === 0) {
$summary['latest_status'] = $status;
}
if ($summary['latest_resolution'] === '' && $status === 'resolved' && $resolution !== '') {
$summary['latest_resolution'] = $resolution;
$summary['latest_resolution_utc'] = (string) ($row['resolved_utc'] ?? $updated_utc);
if ($resolution === 'restore') {
$reset_timestamp = strtotime($summary['latest_resolution_utc']);
$summary['governance_reset_utc'] = $reset_timestamp !== false
? gmdate('Y-m-d H:i:s', $reset_timestamp + 1)
: $summary['latest_resolution_utc'];
}
}
}
return $summary;
}
/**
* Returns one appeal row.
*
* @return array<string,mixed>
*/
public function get(int $id): array
{
global $wpdb;
if ($id <= 0) {
return [];
}
$table = Installer::table('prompt_appeals');
$users = $wpdb->users;
$sql = $wpdb->prepare(
"SELECT a.*, appellant.display_name AS appellant_display_name, resolver.display_name AS resolver_display_name