| 1 |
<?php |
| 2 |
/** |
| 3 |
* Get FAQ ability. |
| 4 |
* |
| 5 |
* @package ThinkRank\Abilities\Content |
| 6 |
*/ |
| 7 |
|
| 8 |
declare(strict_types=1); |
| 9 |
|
| 10 |
namespace ThinkRank\Abilities\Content; |
| 11 |
|
| 12 |
use ThinkRank\Frontend\Schema_Graph; |
| 13 |
use ThinkRank\SEO\FAQ_Content; |
| 14 |
|
| 15 |
if ( ! defined( 'ABSPATH' ) ) { |
| 16 |
exit; // Exit if accessed directly. |
| 17 |
} |
| 18 |
|
| 19 |
/** |
| 20 |
* Reports the FAQ questions on one post, and whether they reach search engines. |
| 21 |
* |
| 22 |
* Three separate facts, because they come apart in practice and an agent that |
| 23 |
* conflates them gives bad advice: the questions that are on the page, which |
| 24 |
* editor surface holds them (only the block can be written back), and whether |
| 25 |
* ThinkRank is actually publishing a FAQPage for the post. The third can be |
| 26 |
* false while the first two are healthy, because the content type has Schema |
| 27 |
* switched off, or because another plugin on the page already publishes its own |
| 28 |
* FAQPage and ThinkRank stands down rather than emit a second one. |
| 29 |
*/ |
| 30 |
class Get_FAQ extends FAQ_Ability_Base { |
| 31 |
|
| 32 |
/** |
| 33 |
* Constructor. |
| 34 |
*/ |
| 35 |
public function __construct() { |
| 36 |
$this->id = 'thinkrank/get-faq'; |
| 37 |
$this->label = __( 'Get ThinkRank FAQ', 'thinkrank' ); |
| 38 |
$this->description = __( 'Read the FAQ questions and answers on one post, where they are stored (the ThinkRank FAQ block, or the Elementor, Bricks or Beaver module), and whether ThinkRank is publishing FAQPage schema for the post. Call this before update-faq: it reports which builder renders the post, and update-faq can only write posts built with the block editor. Note that Google shows FAQ rich results only for well-known, authoritative government and health websites, so for most sites the value of an FAQ is being quotable by answer engines, not a rich result.', 'thinkrank' ); |
| 39 |
} |
| 40 |
|
| 41 |
/** |
| 42 |
* {@inheritDoc} |
| 43 |
* |
| 44 |
* @return array<string, bool|float|string> |
| 45 |
*/ |
| 46 |
public function get_annotations() { |
| 47 |
return [ |
| 48 |
'readonly' => true, |
| 49 |
'destructive' => false, |
| 50 |
'idempotent' => true, |
| 51 |
'priority' => 0.6, |
| 52 |
'openWorldHint' => false, |
| 53 |
]; |
| 54 |
} |
| 55 |
|
| 56 |
/** |
| 57 |
* {@inheritDoc} |
| 58 |
* |
| 59 |
* @return array<string, mixed> |
| 60 |
*/ |
| 61 |
public function get_input_schema() { |
| 62 |
return [ |
| 63 |
'type' => 'object', |
| 64 |
'additionalProperties' => false, |
| 65 |
'required' => [ 'post_id' ], |
| 66 |
'properties' => [ |
| 67 |
'post_id' => [ |
| 68 |
'type' => 'integer', |
| 69 |
'description' => __( 'Post ID from list-content-items.', 'thinkrank' ), |
| 70 |
], |
| 71 |
], |
| 72 |
]; |
| 73 |
} |
| 74 |
|
| 75 |
/** |
| 76 |
* {@inheritDoc} |
| 77 |
* |
| 78 |
* @return array<string, mixed> |
| 79 |
*/ |
| 80 |
public function get_output_schema() { |
| 81 |
return [ |
| 82 |
'type' => 'object', |
| 83 |
'properties' => [ |
| 84 |
'post_id' => [ 'type' => 'integer' ], |
| 85 |
'post_title' => [ 'type' => 'string' ], |
| 86 |
'post_type' => [ 'type' => 'string' ], |
| 87 |
'permalink' => [ 'type' => 'string' ], |
| 88 |
'edit_url' => [ 'type' => 'string' ], |
| 89 |
'builder' => [ |
| 90 |
'type' => 'string', |
| 91 |
'enum' => $this->builder_enum(), |
| 92 |
'description' => __( 'Which page builder renders this post. Anything but "none" means update-faq will refuse it.', 'thinkrank' ), |
| 93 |
], |
| 94 |
'writable' => [ |
| 95 |
'type' => 'boolean', |
| 96 |
'description' => __( 'True when update-faq can write this post. False for builder-rendered posts, where a block would be stored but never shown.', 'thinkrank' ), |
| 97 |
], |
| 98 |
'faqpage_emitted' => [ |
| 99 |
'type' => 'boolean', |
| 100 |
'description' => __( 'Whether ThinkRank currently publishes FAQPage schema for this post. False with questions present means the post is password protected, the content type has Schema off, a producer has its schema toggle off, or another plugin already publishes its own FAQPage here.', 'thinkrank' ), |
| 101 |
], |
| 102 |
'total' => [ 'type' => 'integer' ], |
| 103 |
'items' => [ |
| 104 |
'type' => 'array', |
| 105 |
'items' => [ |
| 106 |
'type' => 'object', |
| 107 |
'properties' => $this->item_properties(), |
| 108 |
], |
| 109 |
], |
| 110 |
], |
| 111 |
]; |
| 112 |
} |
| 113 |
|
| 114 |
/** |
| 115 |
* Execute ability. |
| 116 |
* |
| 117 |
* @param array<string, mixed> $input Ability input payload. |
| 118 |
* @return array<string, mixed>|\WP_Error |
| 119 |
*/ |
| 120 |
public function execute( $input ) { |
| 121 |
$input = (array) $input; |
| 122 |
$post = $this->resolve_post( (int) ( $input['post_id'] ?? 0 ) ); |
| 123 |
|
| 124 |
if ( $post instanceof \WP_Error ) { |
| 125 |
return $post; |
| 126 |
} |
| 127 |
|
| 128 |
$items = FAQ_Content::items( $post ); |
| 129 |
$builder = $this->builder_name( (int) $post->ID ); |
| 130 |
|
| 131 |
return [ |
| 132 |
'post_id' => (int) $post->ID, |
| 133 |
'post_title' => (string) get_the_title( $post ), |
| 134 |
'post_type' => (string) $post->post_type, |
| 135 |
'permalink' => (string) get_permalink( $post ), |
| 136 |
'edit_url' => (string) get_edit_post_link( $post->ID, 'raw' ), |
| 137 |
'builder' => $builder, |
| 138 |
'writable' => 'none' === $builder, |
| 139 |
'faqpage_emitted' => Schema_Graph::will_emit_faqpage( $post ), |
| 140 |
'total' => count( $items ), |
| 141 |
'items' => $items, |
| 142 |
]; |
| 143 |
} |
| 144 |
} |
| 145 |
|