true], 'names'); unset($types['attachment']); if ($only_type) { $types = in_array($only_type, $types, true) ? [$only_type] : []; if (!$types) { WP_CLI::error("Post type '{$only_type}' is not a public post type on this site."); return; } } /** * A trimmed length that ignores empty-ish content: an Elementor page often has * a post_content of a few whitespace characters or a single wrapper, which is * not real editable prose. Anything under this many characters is treated as * "effectively empty" for the purpose of "can the scanner find text here". */ $MEANINGFUL = 40; WP_CLI::log(''); WP_CLI::log('AI Content Bridge — content storage diagnostic'); WP_CLI::log('Where does each post type keep its content, and can the scanner read it?'); WP_CLI::log(str_repeat('-', 78)); $rows = []; foreach ($types as $type) { $ids = get_posts([ 'post_type' => $type, 'post_status' => 'publish', 'posts_per_page' => 200, // a sample is enough to characterise a type 'fields' => 'ids', 'orderby' => 'date', 'order' => 'DESC', 'suppress_filters' => true, ]); $n = count($ids); if ($n === 0) { $rows[] = [$type, 0, '-', '-', '-', '-', 'no published posts']; continue; } $has_content = 0; // post_content holds meaningful text $has_elementor = 0; // _elementor_data present and non-empty $both = 0; // both of the above $content_links = 0; // at least one = $MEANINGFUL; $e = ($elementor !== '' && $elementor !== '[]' && $elementor !== 'null'); if ($c) $has_content++; if ($e) $has_elementor++; if ($c && $e) $both++; if (stripos($content, ' $n ? round(($k / $n) * 100) : 0; $rows[] = [ $type, $n, $pct($has_content) . '%', $pct($has_elementor) . '%', $pct($both) . '%', $pct($content_links) . '%', $pct($would_skip) . '% skipped', ]; } if (class_exists('WP_CLI\\Utils\\format_items') || function_exists('WP_CLI\\Utils\\format_items')) { // preferred: a real table \WP_CLI\Utils\format_items( 'table', array_map(function ($r) { return [ 'post type' => $r[0], 'sample' => $r[1], 'text in content' => $r[2], 'has elementor' => $r[3], 'both fields' => $r[4], 'links in content'=> $r[5], 'as a source' => $r[6], ]; }, $rows), ['post type', 'sample', 'text in content', 'has elementor', 'both fields', 'links in content', 'as a source'] ); } else { foreach ($rows as $r) { WP_CLI::log(sprintf( "%-16s n=%-4s content:%-5s elementor:%-5s both:%-5s links:%-5s %s", $r[0], $r[1], $r[2], $r[3], $r[4], $r[5], $r[6] )); } } WP_CLI::log(str_repeat('-', 78)); WP_CLI::log('How to read this:'); WP_CLI::log(' text in content — % of posts whose post_content holds real text.'); WP_CLI::log(' This is the field ACB reads. High = ACB sees the content.'); WP_CLI::log(' has elementor — % with _elementor_data. The front end renders from this.'); WP_CLI::log(' both fields — % that keep text in BOTH. For these, ACB can read and'); WP_CLI::log(' insert into post_content even though Elementor displays'); WP_CLI::log(' its own copy. This is the case that explains "it works".'); WP_CLI::log(' as a source — % ACB would SKIP when looking for pages to insert a link'); WP_CLI::log(' INTO, because _elementor_data is present. Note a post can'); WP_CLI::log(' be skipped as a SOURCE yet still work perfectly as a'); WP_CLI::log(' TARGET (something else linking to it).'); WP_CLI::log(''); WP_CLI::log('The key comparison: if "text in content" is high but "as a source" also shows'); WP_CLI::log('most posts skipped, then ACB can SEE your content for counting and targeting'); WP_CLI::log('but is being conservative about writing INTO it. That is a setting worth'); WP_CLI::log('revisiting, not a hard limit.'); WP_CLI::log('');