generateExcerpt($content); $post_data = [ 'post_title' => sanitize_text_field($title), 'post_content' => $content, 'post_status' => 'publish', 'post_type' => 'post', 'post_excerpt' => sanitize_text_field($excerpt), // Explicitly enable comments on generated posts (#48). Relying on // the global default_comment_status option alone is fragile: some // themes/import paths leave comments closed, which produced the // "comments enabled on only 2 of 7 sites" symptom. Setting it per // post makes engagement-ready comments deterministic. 'comment_status' => 'open', ]; if ($slug !== null) { $post_data['post_name'] = sanitize_title($slug); } if ($post_date !== null) { // sanitizePostDate() renders the instant with gmdate(), so its result // is GMT: post_date_gmt takes it verbatim and the *local* post_date // column is derived from it. Writing the GMT rendering straight into // post_date and then asking get_gmt_from_date() — which reads its // argument as local — for the companion shifted both columns by the // site's UTC offset. This is the direction ContaiAgentActionHandler // already uses for the identical gmdate() value, and the one // CommentsService::commentDatesForPost documents. $post_date_gmt = $this->sanitizePostDate($post_date); $post_data['post_date_gmt'] = $post_date_gmt; $post_data['post_date'] = get_date_from_gmt($post_date_gmt); } $post_id = wp_insert_post($post_data); if (is_wp_error($post_id)) { // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped throw new RuntimeException("Failed to create post: " . $post_id->get_error_message()); } if ($metatitle !== null) { $this->saveMetatitle($post_id, $metatitle); } return $post_id; } /** * Normalizes an incoming date to GMT 'Y-m-d H:i:s'. * * Both paths return GMT. The success path renders the parsed instant with * gmdate(), so the fallback asks current_time() for the GMT clock rather * than the site-local one: a mixed convention would be invisible at the * call site, which derives the local post_date from this value. * * @return string 'Y-m-d H:i:s' in GMT. */ private function sanitizePostDate(string $post_date): string { $timestamp = strtotime($post_date); if ($timestamp === false) { return current_time('mysql', true); } return gmdate('Y-m-d H:i:s', $timestamp); } private function saveMetatitle(int $post_id, string $metatitle): void { update_post_meta($post_id, self::CUSTOM_FIELD_METATITLE, sanitize_text_field($metatitle)); } private function generateExcerpt(string $content): string { $text = wp_strip_all_tags($content); $text = preg_replace('/\s+/', ' ', trim($text)); if (mb_strlen($text) <= self::META_DESCRIPTION_LENGTH) { return $text; } $truncated = mb_substr($text, 0, self::META_DESCRIPTION_LENGTH); $last_space = mb_strrpos($truncated, ' '); if ($last_space !== false) { $truncated = mb_substr($truncated, 0, $last_space); } return $truncated . '...'; } public function assignCategory(int $post_id, int $category_id): void { wp_set_object_terms($post_id, [$category_id], 'category', false); } public function setFeaturedImage(int $post_id, int $attachment_id): void { set_post_thumbnail($post_id, $attachment_id); } public function saveMetadata(int $post_id, array $metadata): void { foreach ($metadata as $key => $value) { update_post_meta($post_id, $key, $value); } } public function getPermalink(int $post_id): string { return get_permalink($post_id); } }