'Post content (post_content)',
);
// Only non-underscore keys: extraction skips underscore-prefixed meta, so the
// exclude options should match what actually gets extracted.
$meta_keys = $wpdb->get_col($wpdb->prepare(
"SELECT DISTINCT pm.meta_key
FROM {$wpdb->postmeta} pm
INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id
WHERE p.post_type = %s
AND pm.meta_key NOT LIKE %s
ORDER BY pm.meta_key ASC
LIMIT 300",
$post_type,
$wpdb->esc_like('_') . '%'
));
$excluded = ai12z_excluded_meta_keys();
$meta_keys = array_values(array_diff((array) $meta_keys, $excluded));
return array(
'standard' => $standard,
'meta' => $meta_keys,
);
}
// Clean an incoming content-types map: keep only public types, coerce each value
// to either "" (built-in / no config) or a { "exclude": [field keys] } array.
function ai12z_sanitize_content_types_map($map) {
if (!is_array($map)) {
return array();
}
$allowed = get_post_types(array('public' => true));
$clean = array();
foreach ($map as $type => $value) {
$type = sanitize_text_field($type);
if (!in_array($type, $allowed, true)) {
continue;
}
if (is_array($value)) {
$exclude = (isset($value['exclude']) && is_array($value['exclude'])) ? $value['exclude'] : array();
$clean[$type] = array(
'exclude' => array_values(array_map('sanitize_text_field', $exclude)),
);
} else {
$clean[$type] = '';
}
}
return $clean;
}
// Return the field keys to exclude from content extraction for a type, or an
// empty array when the type has no exclusions (built-in, or nothing saved).
function ai12z_get_type_exclusions($post_type) {
$all = get_option('ai12z_content_types', array());
if (is_string($all)) {
$all = json_decode($all, true);
}
if (!is_array($all) || !isset($all[$post_type]) || !is_array($all[$post_type])) {
return array();
}
$entry = $all[$post_type];
return (isset($entry['exclude']) && is_array($entry['exclude'])) ? array_values($entry['exclude']) : array();
}
/**
* Extract the full text content from a Gutenberg or classic post/page.
*
* @param int $post_id The ID of the post or page.
* @param string $format Accepts 'text' for raw unformatted text or 'html' for clean text tags.
* @param array $exclude Field/meta keys (and optionally 'post_content') to skip.
* @return string The fully extracted text content.
*/
// Guarded because the name is unprefixed and could be provided by the theme or
// another plugin; a pre-existing definition wins.
if ( ! function_exists( 'get_full_post_text_content' ) ) {
function get_full_post_text_content( $post_id = null, $format = 'text', $exclude = array() ) {
if ( ! $post_id ) {
$post_id = get_the_ID();
}
if ( ! $post_id ) {
return '';
}
$post = get_post( $post_id );
if ( ! $post ) {
return '';
}
$exclude = is_array( $exclude ) ? $exclude : array();
// Post body, unless the admin excluded it.
$content = in_array( 'post_content', $exclude, true ) ? '' : $post->post_content;
// Collect text from the post's custom fields by reading post meta
// directly — works whether the fields come from ACF, Pods, or plain meta,
// with no ACF plugin dependency. Underscore-prefixed and excluded keys are skipped.
$meta = get_post_meta( $post_id );
if ( is_array( $meta ) ) {
$field_text = [];
$extract_text = function( $value ) use ( &$extract_text, &$field_text ) {
if ( is_string( $value ) ) {
$value = trim( $value );
// Skip empties, ACF field-key pointers (field_...), and pure numbers/ids.
if ( $value === '' || strpos( $value, 'field_' ) === 0 || is_numeric( $value ) ) {
return;
}
$field_text[] = $value;
} elseif ( is_array( $value ) ) {
foreach ( $value as $val ) {
$extract_text( $val );
}
}
};
foreach ( $meta as $key => $values ) {
// Skip protected/internal keys (underscore-prefixed) and excluded fields.
if ( strpos( (string) $key, '_' ) === 0 || in_array( $key, $exclude, true ) ) {
continue;
}
foreach ( (array) $values as $value ) {
$extract_text( maybe_unserialize( $value ) );
}
}
if ( ! empty( $field_text ) ) {
$content .= "\n" . implode( "\n", $field_text );
}
}
// Remove Gutenberg HTML comment delimiters (e.g., ).
$content = preg_replace( '//', '', $content );
if ( 'html' === $format ) {
// Strip structural wrappers but keep text tags (add more like if needed).
return strip_tags( $content, '