$value) {
if (property_exists($this, (string) $key)) {
$this->{$key} = match ($key) {
'ID', 'post_author', 'post_parent' => (int) $value,
default => (string) $value,
};
}
}
}
}
class WP_Error {
private string $code;
private string $message;
public function __construct($code = '', $message = '', $data = null) {
$this->code = (string) $code;
$this->message = (string) $message;
}
public function get_error_code(): string { return $this->code; }
public function get_error_message(): string { return $this->message; }
}
class WP_REST_Request {
private array $params;
private array $files;
public function __construct(array $params = [], array $files = []) {
$this->params = $params;
$this->files = $files;
}
public function get_param(string $key) { return $this->params[$key] ?? null; }
public function get_file_params(): array { return $this->files; }
}
class WP_REST_Response {
private $data;
private int $status;
public function __construct($data = [], int $status = 200) {
$this->data = $data;
$this->status = $status;
}
public function get_data() { return $this->data; }
public function get_status(): int { return $this->status; }
}
class WP_REST_Server { public const READABLE = 'GET'; }
final class FrontSubmitPendingWpdb {
public string $posts = 'wp_posts';
public string $postmeta = 'wp_postmeta';
public function update($table, array $data, array $where, array $format = [], array $where_format = []) {
$state = &$GLOBALS['front_pending_state'];
if (!empty($state['fail_relation_updates'])) return false;
$id = (int) ($where['ID'] ?? 0);
$post = $state['posts'][$id] ?? null;
if (!$post instanceof WP_Post) return 0;
foreach ($data as $key => $value) {
if ($key === 'post_parent') $post->post_parent = (int) $value;
}
$state['relation_updates'][] = ['id' => $id, 'data' => $data];
return 1;
}
public function prepare(string $query, ...$args): string { return $query; }
public function get_col(string $query): array { return []; }
}
function fp_assert(bool $condition, string $message): void {
if (!$condition) throw new \RuntimeException($message);
}
function fp_reset(): void {
$GLOBALS['front_pending_state'] = [
'user_id' => 7,
'posts' => [],
'post_meta' => [],
'post_thumbnails' => [],
'pay_meta' => [],
'url_map' => [],
'deleted' => [],
'relation_updates' => [],
'meta_writes' => [],
'upload_calls' => [],
'featured_save_calls' => 0,
'fail_relation_updates' => false,
'fail_insert' => false,
'fail_update' => false,
'insert_zero' => false,
'update_zero' => false,
'next_post_id' => 1000,
'next_attachment_id' => 2000,
'failed_js_ids' => [],
];
$GLOBALS['wpdb'] = new FrontSubmitPendingWpdb();
$_FILES = [];
}
function fp_post(int $id, string $type = 'post', int $author = 7, string $status = 'draft', string $content = '', int $parent = 0, string $mime = ''): WP_Post {
$post = new WP_Post([
'ID' => $id,
'post_type' => $type,
'post_author' => $author,
'post_status' => $status,
'post_content' => $content,
'post_parent' => $parent,
'post_mime_type' => $mime,
'post_title' => 'Harness post ' . $id,
]);
$GLOBALS['front_pending_state']['posts'][$id] = $post;
return $post;
}
function fp_attachment(int $id, int $author = 7, string $mime = 'image/jpeg', int $parent = 0, string $status = 'inherit', bool $pending = true, ?string $kind = 'image'): WP_Post {
$post = fp_post($id, 'attachment', $author, $status, '', $parent, $mime);
if ($kind !== null) update_post_meta($id, '_jio_front_submit_media', $kind);
$GLOBALS['front_pending_state']['url_map']['https://example.test/uploads/media-' . $id . '.' . fp_ext($mime)] = $id;
if ($pending) \Jio\Attachment\PendingUploads::mark($id);
return $post;
}
function fp_ext(string $mime): string {
return match ($mime) {
'image/jpeg' => 'jpg', 'image/png' => 'png', 'video/mp4' => 'mp4', 'application/pdf' => 'pdf', default => 'bin',
};
}
function fp_pending(int $id): bool {
return get_post_meta($id, \Jio\Attachment\PendingUploads::META, true) !== '';
}
function fp_article_data(string $content, array $extra = []): array {
return array_merge([
'post_title' => 'Pending lifecycle behavior test',
'post_content' => $content,
'category' => [1],
], $extra);
}
function __($text, $domain = ''): string { return (string) $text; }
function absint($value): int { return abs((int) $value); }
function sanitize_key($value): string { return preg_replace('/[^a-z0-9_\-]/', '', strtolower((string) $value)) ?? ''; }
function sanitize_text_field($value): string { return trim(strip_tags((string) $value)); }
function sanitize_textarea_field($value): string { return trim(strip_tags((string) $value)); }
function sanitize_file_name($value): string { return preg_replace('/[^A-Za-z0-9._\-]/', '', (string) $value) ?? ''; }
function wp_unslash($value) { return $value; }
function wp_strip_all_tags($value): string { return trim(strip_tags((string) $value)); }
function wp_kses_post($value): string { return (string) $value; }
function is_wp_error($value): bool { return $value instanceof WP_Error; }
function get_current_user_id(): int { return (int) $GLOBALS['front_pending_state']['user_id']; }
function current_user_can($capability, ...$args): bool { return false; }
function user_can($user_id, $capability, ...$args): bool { return false; }
function add_filter($tag, $callback, $priority = 10, $accepted_args = 1): bool { return true; }
function remove_filter($tag, $callback, $priority = 10): bool { return true; }
function do_action($tag, ...$args): void { $GLOBALS['front_pending_state']['actions'][] = $tag; }
function current_time($type): string { return '2026-10-30 12:00:00'; }
function wp_set_post_categories($post_id, $categories): void {}
function wp_set_post_tags($post_id, $tags): void {}
function get_permalink($post_id): string { return 'https://example.test/?p=' . (int) $post_id; }
function clean_post_cache($post_id): void {}
function wp_max_upload_size(): int { return 50 * 1024 * 1024; }
function size_format($bytes): string { return (string) $bytes . 'B'; }
function get_allowed_mime_types(): array { return ['pdf' => 'application/pdf']; }
function wp_check_filetype_and_ext($file, $filename, $mimes = []): array {
$ext = strtolower(pathinfo((string) $filename, PATHINFO_EXTENSION));
return ['ext' => $ext, 'type' => $mimes[$ext] ?? ''];
}
function get_post($post_id) {
if ($post_id instanceof WP_Post) return $post_id;
return $GLOBALS['front_pending_state']['posts'][(int) $post_id] ?? null;
}
function get_post_meta($post_id, $key = '', $single = false) {
$value = $GLOBALS['front_pending_state']['post_meta'][(int) $post_id][(string) $key] ?? null;
if ($single) return $value ?? '';
return $value === null ? [] : [$value];
}
function update_post_meta($post_id, $key, $value): bool {
$state = &$GLOBALS['front_pending_state'];
$id = (int) $post_id;
$meta_key = (string) $key;
$state['post_meta'][$id][$meta_key] = $value;
$state['meta_writes'][] = ['id' => $id, 'key' => $meta_key, 'value' => $value];
return true;
}
function delete_post_meta($post_id, $key): bool {
unset($GLOBALS['front_pending_state']['post_meta'][(int) $post_id][(string) $key]);
return true;
}
function get_post_thumbnail_id($post_id): int { return (int) ($GLOBALS['front_pending_state']['post_thumbnails'][(int) $post_id] ?? 0); }
function set_post_thumbnail($post_id, $thumbnail_id): bool {
$GLOBALS['front_pending_state']['post_thumbnails'][(int) $post_id] = (int) $thumbnail_id;
return true;
}
function delete_post_thumbnail($post_id): bool {
unset($GLOBALS['front_pending_state']['post_thumbnails'][(int) $post_id]);
return true;
}
function wp_attachment_is_image($attachment_id): bool {
$post = get_post((int) $attachment_id);
return $post instanceof WP_Post && strpos($post->post_mime_type, 'image/') === 0;
}
function get_posts(array $args = []): array {
$results = [];
foreach ($GLOBALS['front_pending_state']['posts'] as $id => $post) {
if (!$post instanceof WP_Post) continue;
if (($args['post_type'] ?? '') !== $post->post_type) continue;
if (isset($args['post_parent']) && $post->post_parent !== (int) $args['post_parent']) continue;
if (isset($args['author']) && $post->post_author !== (int) $args['author']) continue;
if (isset($args['post_status']) && !in_array($post->post_status, (array) $args['post_status'], true)) continue;
if (!empty($args['meta_key']) && get_post_meta((int) $id, (string) $args['meta_key'], true) === '') continue;
$results[] = !empty($args['fields']) && $args['fields'] === 'ids' ? (int) $id : $post;
}
return $results;
}
function wp_insert_post(array $postarr, $wp_error = false) {
$state = &$GLOBALS['front_pending_state'];
if ($state['fail_insert']) return new WP_Error('insert_failed', 'insert failed');
if ($state['insert_zero']) return 0;
$id = (int) $state['next_post_id']++;
$postarr['ID'] = $id;
$state['posts'][$id] = new WP_Post($postarr + ['post_type' => 'post', 'post_status' => 'draft']);
return $id;
}
function wp_update_post(array $postarr, $wp_error = false) {
$state = &$GLOBALS['front_pending_state'];
$id = (int) ($postarr['ID'] ?? 0);
if ($id <= 0 || !isset($state['posts'][$id])) return new WP_Error('update_missing', 'post missing');
if ($state['fail_update']) return new WP_Error('update_failed', 'update failed');
if ($state['update_zero']) return 0;
$post = $state['posts'][$id];
foreach ($postarr as $key => $value) {
if (property_exists($post, (string) $key)) {
$post->{$key} = in_array($key, ['ID', 'post_author', 'post_parent'], true) ? (int) $value : (string) $value;
}
}
return $id;
}
function jio_get_pay_meta($post_id): array {
return $GLOBALS['front_pending_state']['pay_meta'][(int) $post_id] ?? [];
}
function attachment_url_to_postid($url): int {
return (int) ($GLOBALS['front_pending_state']['url_map'][(string) $url] ?? 0);
}
function wp_get_upload_dir(): array { return ['baseurl' => 'https://example.test/uploads']; }
function set_url_scheme($url, $scheme = null): string {
$url = (string) $url;
return preg_match('#^https?://#i', $url) ? $url : 'https://' . ltrim($url, '/');
}
function wp_parse_url($url, $component = -1) { return parse_url((string) $url, $component); }
function wp_get_attachment_url($attachment_id): string {
foreach ($GLOBALS['front_pending_state']['url_map'] as $url => $id) {
if ((int) $id === (int) $attachment_id) return (string) $url;
}
return 'https://example.test/uploads/media-' . (int) $attachment_id . '.bin';
}
function wp_get_attachment_image_url($attachment_id, $size = 'thumbnail') { return wp_get_attachment_url($attachment_id); }
function wp_prepare_attachment_for_js($attachment_id) {
if (in_array((int) $attachment_id, $GLOBALS['front_pending_state']['failed_js_ids'], true)) return false;
$post = get_post((int) $attachment_id);
if (!$post instanceof WP_Post) return false;
$mime = $post->post_mime_type;
[$type, $subtype] = array_pad(explode('/', $mime, 2), 2, '');
$url = wp_get_attachment_url((int) $attachment_id);
return [
'id' => (int) $attachment_id,
'title' => $post->post_title,
'name' => basename($url),
'filename' => basename($url),
'url' => $url,
'type' => $type,
'subtype' => $subtype,
'mime' => $mime,
'sizes' => [],
'filesizeInBytes' => 1,
];
}
function get_the_title($post_id): string { return (string) (get_post((int) $post_id)->post_title ?? ''); }
function get_attached_file($attachment_id): string { return sys_get_temp_dir() . '/front-submit-' . (int) $attachment_id; }
function wp_basename($path): string { return basename((string) $path); }
function media_handle_upload($file_id, $post_id, $post_data = [], $overrides = []) {
$state = &$GLOBALS['front_pending_state'];
$id = (int) $state['next_attachment_id']++;
$file = $_FILES[$file_id] ?? [];
$state['posts'][$id] = new WP_Post([
'ID' => $id,
'post_type' => 'attachment',
'post_author' => 0,
'post_status' => 'inherit',
'post_parent' => 0,
'post_title' => (string) ($file['name'] ?? 'upload'),
'post_mime_type' => 'application/pdf',
]);
$url = 'https://example.test/uploads/upload-' . $id . '.pdf';
$state['url_map'][$url] = $id;
$state['upload_calls'][] = ['id' => $id, 'overrides' => $overrides];
return $id;
}
function wp_delete_attachment($attachment_id, $force_delete = false) {
$state = &$GLOBALS['front_pending_state'];
$id = (int) $attachment_id;
$state['deleted'][] = $id;
unset($state['posts'][$id], $state['post_meta'][$id]);
return (object) ['ID' => $id];
}
fp_reset();
require_once dirname(__DIR__, 2) . '/src/Attachment/PendingUploads.php';
require_once dirname(__DIR__, 2) . '/src/Core/FrontSubmitSave.php';
require_once dirname(__DIR__, 2) . '/src/Core/FrontSubmitPay.php';
require_once dirname(__DIR__, 2) . '/src/Core/FrontSubmitFeatured.php';
require_once dirname(__DIR__, 2) . '/src/Core/FrontSubmitMedia.php';
require_once dirname(__DIR__, 2) . '/src/Core/FrontSubmitChunk.php';
require_once dirname(__DIR__, 2) . '/src/Core/FrontSubmit.php';
}
namespace Jio\Core {
final class DomainModel {
public const PAY_META_KEY = '_jio_pay';
public const POST_META_KEY = 'jio_post_meta';
}
final class FrontSubmitPendingProbe {
use FrontSubmitSave;
use FrontSubmitMedia;
public function settings(): array {
return [
'enabled' => true,
'default_user_id' => 1,
'title_min' => 3,
'title_max' => 100,
'allowed_cat_ids' => [],
'allow_tags' => false,
'custom_filter_s' => false,
];
}
public function can_submit(?int $user_id = null): bool { return true; }
public function can_edit_post(int $post_id, ?int $user_id = null): bool {
$post = \get_post($post_id);
return $post instanceof \WP_Post && (int) $post->post_author === (int) ($user_id ?? \get_current_user_id());
}
public function user_can_cap(string $cap, int $user_id = 0, ?int $context = null): bool {
return $cap !== 'new_post_audit_no';
}
private function save_pay_meta(int $post_id, array $data, int $user_id, array $settings): array {
if (isset($data['fake_saved_pay_meta']) && is_array($data['fake_saved_pay_meta'])) {
$GLOBALS['front_pending_state']['pay_meta'][$post_id] = $data['fake_saved_pay_meta'];
\update_post_meta($post_id, DomainModel::PAY_META_KEY, $data['fake_saved_pay_meta']);
}
if (!empty($data['fake_pay_failure'])) {
return ['success' => false, 'message' => 'pay validation failed'];
}
return ['success' => true];
}
private function save_featured_meta(int $post_id, array $data, int $user_id): void {
$GLOBALS['front_pending_state']['featured_save_calls']++;
$meta = $data['fake_featured_meta'] ?? null;
if (is_array($meta)) \update_post_meta($post_id, DomainModel::POST_META_KEY, $meta);
$thumbnail = (int) ($data['fake_thumbnail_id'] ?? 0);
if ($thumbnail > 0) \set_post_thumbnail($post_id, $thumbnail);
}
}
}
namespace Jio\User {
final class Restriction {
private static ?self $instance = null;
public static function get_instance(): self { return self::$instance ??= new self(); }
public function is_suspended(int $user_id): bool { return false; }
public function get_active_suspend(int $user_id): array { return []; }
}
}
namespace {
use Jio\Attachment\PendingUploads;
use Jio\Core\FrontSubmit;
use Jio\Core\FrontSubmitPendingProbe;
use Jio\Core\DomainModel;
$submit = new FrontSubmitPendingProbe();
// Successful single-file and sideload persistence both pass through the real
// FrontSubmit normalizer and mark the resulting attachment as pending.
fp_reset();
$tmp = tempnam(sys_get_temp_dir(), 'front-submit-pending-');
file_put_contents($tmp, 'pdf test');
$real_submit = new FrontSubmit();
foreach ([false, true] as $sideload) {
$response = $real_submit->persist_uploaded_file('resource', [
'name' => 'document.pdf', 'tmp_name' => $tmp, 'size' => filesize($tmp), 'error' => UPLOAD_ERR_OK,
], $sideload);
fp_assert($response instanceof \WP_REST_Response && $response->get_status() === 201, 'real upload persistence should return a created response');
$id = (int) ($response->get_data()['attachment_id'] ?? 0);
fp_assert($id > 0 && fp_pending($id), 'each normalized upload must be marked pending');
fp_assert(($GLOBALS['front_pending_state']['post_meta'][$id]['_jio_front_submit_media'] ?? '') === 'resource', 'upload should retain the normalized media type');
}
fp_assert(count($GLOBALS['front_pending_state']['upload_calls']) === 2, 'single and sideload calls should each persist once');
fp_assert(empty($GLOBALS['front_pending_state']['upload_calls'][0]['overrides']['action']), 'ordinary upload should use the upload action');
fp_assert(($GLOBALS['front_pending_state']['upload_calls'][1]['overrides']['action'] ?? '') === 'wp_handle_sideload', 'chunk merge sideload should use the sideload action');
$GLOBALS['front_pending_state']['failed_js_ids'][] = 2002;
$un_normalized = $real_submit->persist_uploaded_file('resource', [
'name' => 'unreadable.pdf', 'tmp_name' => $tmp, 'size' => filesize($tmp), 'error' => UPLOAD_ERR_OK,
]);
fp_assert(is_wp_error($un_normalized), 'upload normalization failure should return an error');
$premature_marks = array_filter($GLOBALS['front_pending_state']['meta_writes'], static fn (array $row): bool => $row['id'] === 2002 && $row['key'] === PendingUploads::META);
fp_assert($premature_marks === [], 'upload must not be marked pending before response normalization succeeds');
@unlink($tmp);
// Draft save covers all persisted reference sources, including more than the
// default twenty URLs in both saved pay metadata and post_content.
fp_reset();
$id_refs = [100, 101, 102, 103, 104, 105, 106, 107, 108];
foreach ($id_refs as $id) {
$mime = in_array($id, [101, 104], true) ? 'video/mp4' : ($id === 102 ? 'application/pdf' : 'image/jpeg');
fp_attachment($id, 7, $mime);
}
$pay_url_ids = [];
$pay_downloads = [
['attachment_id' => 107, 'url' => ''],
['attachment_id' => 108, 'url' => ''],
];
for ($i = 0; $i < 23; $i++) {
$id = 200 + $i;
$url = 'https://example.test/uploads/pay-url-' . $i . '.pdf';
fp_attachment($id, 7, 'application/pdf', 0, 'inherit', true, null);
$GLOBALS['front_pending_state']['url_map'][$url] = $id;
$pay_downloads[] = ['attachment_id' => 0, 'url' => $url];
$pay_url_ids[] = $id;
}
$body_url_ids = [];
$body_urls = [];
for ($i = 0; $i < 23; $i++) {
$id = 300 + $i;
$url = 'https://example.test/uploads/body-url-' . $i . '.jpg';
fp_attachment($id, 7, 'image/jpeg', 0, 'inherit', true, null);
$GLOBALS['front_pending_state']['url_map'][$url] = $id;
$body_urls[] = $url;
$body_url_ids[] = $id;
}
$unreferenced = 399;
fp_attachment($unreferenced, 7, 'image/jpeg');
$content = 'Draft body with enough text for a save. '
. '
'
. ''
. 'resource'
. implode(' ', $body_urls);
$result = $submit->persist(fp_article_data($content, [
'fake_saved_pay_meta' => ['downloads' => $pay_downloads],
'fake_featured_meta' => [
'featured_video_attachment_id' => 104,
'featured_slide' => '105,106',
],
'fake_thumbnail_id' => 103,
]), 'posts_draft');
fp_assert(!empty($result['success']), 'draft with persisted references should save');
$draft_id = (int) ($result['post_id'] ?? 0);
fp_assert(($GLOBALS['front_pending_state']['posts'][$draft_id]->post_status ?? '') === 'draft', 'draft status should be preserved');
$claimed_ids = array_merge($id_refs, $pay_url_ids, $body_url_ids);
foreach ($claimed_ids as $id) fp_assert(!fp_pending($id), 'saved draft reference should be claimed: ' . $id);
foreach ([100, 101, 102, 103, 104, 105, 106, 107, 108] as $id) {
fp_assert(($GLOBALS['front_pending_state']['posts'][$id]->post_parent ?? -1) === $draft_id, 'ID-backed reference should bind to the saved draft: ' . $id);
}
fp_assert(fp_pending($unreferenced), 'newly uploaded but unreferenced media should remain pending');
// Pending submissions also synchronize after a successful post write.
fp_reset();
fp_attachment(410, 7, 'image/jpeg');
$pending = $submit->persist(fp_article_data('
Pending body has sufficient words.
'));
fp_assert(!empty($pending['success']), 'pending post should save');
$pending_id = (int) $pending['post_id'];
fp_assert(($GLOBALS['front_pending_state']['posts'][$pending_id]->post_status ?? '') === 'pending', 'normal submit should retain pending review status');
fp_assert(!fp_pending(410), 'pending-review post body image should be claimed');
// Edit path reuses the production persist and synchronization implementation.
fp_reset();
$edit_post = fp_post(420, 'post', 7, 'publish', 'Old content');
fp_attachment(421, 7, 'video/mp4');
$edited = $submit->persist(fp_article_data('Edited body has enough content.
', ['posts_id' => 420])); fp_assert(!empty($edited['success']) && (int) $edited['post_id'] === 420, 'owned post should edit successfully'); fp_assert($edit_post->post_content !== 'Old content', 'edit should write the new body before synchronization'); fp_assert(!fp_pending(421) && $GLOBALS['front_pending_state']['posts'][421]->post_parent === 420, 'edit body media should be claimed and attached'); // Existing media is reused without creating a new pending marker. fp_reset(); $reuse_post = fp_post(430, 'post', 7, 'draft', ''); fp_attachment(431, 7, 'image/jpeg', 430, 'inherit', false, 'image'); $reuse_writes_before = count(array_filter($GLOBALS['front_pending_state']['meta_writes'], static fn (array $row): bool => $row['id'] === 431 && $row['key'] === PendingUploads::META)); $reuse = $submit->persist(fp_article_data('Reused attachment body text.
', ['posts_id' => 430]), 'posts_draft');
fp_assert(!empty($reuse['success']), 'existing media reuse should save');
$reuse_writes_after = count(array_filter($GLOBALS['front_pending_state']['meta_writes'], static fn (array $row): bool => $row['id'] === 431 && $row['key'] === PendingUploads::META));
fp_assert($reuse_writes_before === $reuse_writes_after && !fp_pending(431), 'reused media must not be marked pending again');
fp_assert($GLOBALS['front_pending_state']['posts'][431]->post_parent === 430, 'reused media should remain attached to its post');
// Removing an edit reference unbinds only; it neither deletes nor re-pends.
fp_reset();
fp_post(440, 'post', 7, 'draft', '');
fp_attachment(441, 7, 'image/jpeg', 440, 'inherit', false, 'image');
$removed = $submit->persist(fp_article_data('Reference removed on edit.
', ['posts_id' => 440]), 'posts_draft'); fp_assert(!empty($removed['success']), 'edit removing a media reference should save'); fp_assert(isset($GLOBALS['front_pending_state']['posts'][441]), 'unreferenced attached media must not be deleted'); fp_assert($GLOBALS['front_pending_state']['posts'][441]->post_parent === 0, 'removed media should be detached from the edited post'); fp_assert(get_post_meta(441, '_jio_front_submit_media', true) === '', 'removed media should lose only its submit relation marker'); fp_assert(!fp_pending(441) && $GLOBALS['front_pending_state']['deleted'] === [], 'removed media must not be deleted or marked pending again'); // A payment validation error happens after the body write. The saved body is // protected, but the request's featured values were never persisted and must // therefore not be considered references. fp_reset(); fp_attachment(450, 7, 'image/jpeg'); fp_attachment(451, 7, 'image/jpeg'); $pay_error = $submit->persist(fp_article_data('Body remains persisted after pay error.
', [
'fake_pay_failure' => true,
'fake_featured_meta' => ['featured_video_attachment_id' => 0, 'featured_slide' => '451'],
'fake_thumbnail_id' => 451,
]));
fp_assert(empty($pay_error['success']) && (int) ($pay_error['post_id'] ?? 0) > 0, 'pay validation should return its error after the post was written');
fp_assert(!fp_pending(450), 'persisted body reference should be claimed even when payment validation fails');
fp_assert(fp_pending(451), 'unsaved featured media from failed request must not be claimed');
fp_assert($GLOBALS['front_pending_state']['featured_save_calls'] === 0, 'featured metadata must not be saved after payment failure');
// Failed insert/update writes cannot claim anything because no saved post is
// synchronized. Test both the create and edit branches.
fp_reset();
fp_attachment(460, 7, 'image/jpeg');
$GLOBALS['front_pending_state']['fail_insert'] = true;
$insert_error = $submit->persist(fp_article_data('Insert error body is never saved.
Update error body is never saved.
Invalid media references must not be claimed.
' . 'Saved content survives relation failure.