'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, '