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('');