PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.14.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.14.0
2.14.0 2.13.0 2.12.0 2.11.0 2.10.0 2.9.0 2.8.0 2.7.0 2.6.0 2.5.0 2.4.0 2.3.0 2.2.0 2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.32.0 1.31.0 1.30.0 1.29.0 1.28.0 1.27.0 1.26.0 All 55 releases
thinkrank / includes / abilities / content / class-faq-ability-base.php

class-faq-ability-base.php in ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO 2.14.0, at includes/abilities/content/class-faq-ability-base.php

180 lines 6.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Shared base for the FAQ abilities.
4 *
5 * @package ThinkRank\Abilities\Content
6 */
7
8 declare(strict_types=1);
9
10 namespace ThinkRank\Abilities\Content;
11
12 use ThinkRank\Abilities\Ability_Base;
13 use ThinkRank\SEO\FAQ_Content;
14
15 if ( ! defined( 'ABSPATH' ) ) {
16 exit; // Exit if accessed directly.
17 }
18
19 /**
20 * Post resolution and the shared FAQ item shape.
21 *
22 * A guided FAQ builder has shipped in free since 1.32.0 — the `thinkrank/faq`
23 * block plus the Elementor, Bricks and Beaver modules that mirror it, merged
24 * into one FAQPage by the schema graph. None of it was reachable through the
25 * connector, so an agent asked to add an FAQ could only write raw custom
26 * schema, which duplicates what the block already emits and marks up content
27 * that is not on the page (#767).
28 */
29 abstract class FAQ_Ability_Base extends Ability_Base {
30
31 /**
32 * Resolve a post ID to a post the current user may read.
33 *
34 * @param int $post_id Post ID.
35 * @return \WP_Post|\WP_Error
36 */
37 protected function resolve_post( int $post_id ) {
38 if ( $post_id <= 0 ) {
39 return new \WP_Error(
40 'thinkrank_invalid_post_id',
41 __( 'A valid post ID is required. Use list-content-items to find one.', 'thinkrank' ),
42 [ 'status' => 400 ]
43 );
44 }
45
46 $post = get_post( $post_id );
47
48 if ( ! $post instanceof \WP_Post ) {
49 return new \WP_Error(
50 'thinkrank_post_not_found',
51 __( 'No post was found for the provided ID.', 'thinkrank' ),
52 [ 'status' => 404 ]
53 );
54 }
55
56 return $post;
57 }
58
59 /**
60 * The shape one question is reported in, shared by both abilities.
61 *
62 * @return array<string, mixed>
63 */
64 protected function item_properties(): array {
65 return [
66 'question' => [ 'type' => 'string' ],
67 'answer' => [
68 'type' => 'string',
69 'description' => __( 'The answer as stored. May contain inline HTML, which the block renders.', 'thinkrank' ),
70 ],
71 'source' => [
72 'type' => 'string',
73 'enum' => array_merge(
74 [ FAQ_Content::SOURCE_BLOCK ],
75 FAQ_Content::builders()
76 ),
77 'description' => __( 'Which editor surface holds this question. Only block items can be written by update-faq. An oxygen or breakdance item comes from that builder\'s own accordion rather than a ThinkRank module, so it contributes to FAQPage only while the accordion setting is on.', 'thinkrank' ),
78 ],
79 'schema_enabled' => [
80 'type' => 'boolean',
81 'description' => __( 'False when that producer has its schema toggle off, so the question is on the page but contributes nothing to FAQPage.', 'thinkrank' ),
82 ],
83 'image_id' => [ 'type' => 'integer' ],
84 'image_url' => [ 'type' => 'string' ],
85 'image_alt' => [ 'type' => 'string' ],
86 ];
87 }
88
89 /**
90 * A builder name in the shape the output schema reports.
91 *
92 * Takes the detected value rather than a post ID so a caller detects once
93 * and reports, instead of asking twice and risking two answers.
94 *
95 * @since 2.14.0 Takes the builder, not the post ID.
96 * @param string $builder Builder name from {@see FAQ_Content::builder()}.
97 * @return string One of the builder names, or 'none' for the block editor.
98 */
99 protected function builder_name( string $builder ): string {
100 return '' === $builder ? 'none' : $builder;
101 }
102
103 /**
104 * The builder names a post can report.
105 *
106 * Derived from `FAQ_Content` rather than listed here. The list was written
107 * out by hand and then fell behind the detection it describes, so Oxygen had
108 * no member to report even once it was detected (#831).
109 *
110 * @return string[]
111 */
112 protected function builder_enum(): array {
113 return array_merge( [ 'none' ], FAQ_Content::builders() );
114 }
115
116 /**
117 * A readable name for a builder, for the messages an agent reads.
118 *
119 * @since 2.14.0
120 * @param string $builder Builder key.
121 * @return string
122 */
123 protected function builder_label( string $builder ): string {
124 $labels = [
125 FAQ_Content::SOURCE_ELEMENTOR => __( 'Elementor', 'thinkrank' ),
126 FAQ_Content::SOURCE_BRICKS => __( 'Bricks', 'thinkrank' ),
127 FAQ_Content::SOURCE_BEAVER => __( 'Beaver Builder', 'thinkrank' ),
128 FAQ_Content::BUILDER_OXYGEN => __( 'Oxygen', 'thinkrank' ),
129 FAQ_Content::BUILDER_BREAKDANCE => __( 'Breakdance', 'thinkrank' ),
130 ];
131
132 return $labels[ $builder ] ?? $builder;
133 }
134
135 /**
136 * Why `update-faq` will or will not write this post.
137 *
138 * Four answers, because an agent that cannot tell them apart gives bad
139 * advice. The block editor renders `post_content`, so the write is safe. A
140 * builder with a ThinkRank FAQ module renders something else, and the
141 * questions belong in that module. Oxygen and Breakdance have no module, but
142 * their own accordion is read, so the questions are real and editable in the
143 * builder — and whether they reach the FAQPage is a site setting rather than
144 * a per-element toggle, which the caller has to be told. A builder that is
145 * recognised and not readable at all is the fourth, where a question count of
146 * zero means "nothing readable here" rather than "no FAQ" (#831).
147 *
148 * @since 2.14.0
149 * @param string $builder Builder name, or '' for the block editor.
150 * @return string
151 */
152 protected function writable_reason( string $builder ): string {
153 if ( '' === $builder ) {
154 return __( 'The block editor renders this post, so update-faq can write a FAQ block into its content.', 'thinkrank' );
155 }
156
157 if ( FAQ_Content::builder_has_module( $builder ) ) {
158 return sprintf(
159 /* translators: %s: page builder name. */
160 __( '%s renders this post and replaces its content, so a FAQ block written here would never be shown. Add the questions with the ThinkRank FAQ module for that builder; get-faq reads the questions already stored there, and they reach the FAQPage.', 'thinkrank' ),
161 $this->builder_label( $builder )
162 );
163 }
164
165 if ( FAQ_Content::builder_is_readable( $builder ) ) {
166 return sprintf(
167 /* translators: %s: page builder name. */
168 __( '%s renders this post and replaces its content, so a FAQ block written here would never be shown, and ThinkRank has no FAQ module for it. Its own accordion is read instead, and the questions above are what that accordion holds. Edit them in the builder. They reach the FAQPage only while Publish FAQ schema from page-builder accordions is on in Schema Settings, which is off by default.', 'thinkrank' ),
169 $this->builder_label( $builder )
170 );
171 }
172
173 return sprintf(
174 /* translators: %s: page builder name. */
175 __( '%s renders this post and replaces its content, so a FAQ block written here would never be shown. ThinkRank has no FAQ module for it and cannot read its stored questions either, so this post is recognised but unsupported: add the questions with the builder\'s own accordion, and read a question count of zero as "not readable" rather than "no FAQ".', 'thinkrank' ),
176 $this->builder_label( $builder )
177 );
178 }
179 }
180