PluginProbe ʕ •ᴥ•ʔ
Appointment Booking Plugin – LatePoint | Calendar & Scheduling for WordPress / 5.6.10
Appointment Booking Plugin – LatePoint | Calendar & Scheduling for WordPress v5.6.10
5.6.10 5.6.9 5.6.8 5.6.7 5.6.6 5.6.5 5.6.4 5.6.3 5.6.2 5.6.1 5.6.0 5.5.2 5.5.1 5.5.0 5.4.2 trunk 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.1.8 5.1.9 5.1.91 5.1.92 5.1.93 5.1.94 5.2.0 5.2.1 5.2.10 5.2.11 5.2.2 5.2.3 5.2.4 5.2.5 5.2.6 5.2.7 5.2.8 5.2.9 5.3.0 5.3.1 5.3.2 5.4.0 5.4.1
latepoint / lib / abilities / abstract-ability.php
latepoint / lib / abilities Last commit date
activities 5 months ago agents 5 months ago analytics 5 months ago bookings 1 month ago calendar 5 months ago configs 5 months ago customers 1 month ago locations 5 months ago orders 1 month ago services 5 months ago abstract-ability.php 3 weeks ago class-latepoint-abilities.php 1 week ago
abstract-ability.php
197 lines
1 <?php
2 /**
3 * Abstract base class for all LatePoint abilities.
4 *
5 * @package LatePoint\Abilities
6 * @since 5.3.0
7 */
8
9 if ( ! defined( 'ABSPATH' ) ) {
10 exit;
11 }
12
13 abstract class LatePointAbstractAbility {
14
15 protected string $id;
16 protected string $category = 'latepoint';
17 protected string $label = '';
18 protected string $description = '';
19 protected string $permission = 'manage_options';
20 protected bool $read_only = true;
21 protected bool $destructive = false;
22 protected bool $idempotent = false;
23 protected string $role = LATEPOINT_USER_TYPE_ADMIN;
24
25 public function __construct() {
26 $this->configure();
27 }
28
29 /**
30 * Set $id, $label, $description, $permission, $read_only, $destructive.
31 */
32 abstract protected function configure(): void;
33
34 abstract public function get_input_schema(): array;
35
36 abstract public function get_output_schema(): array;
37
38 /**
39 * @param array $args
40 * @return array|\WP_Error
41 */
42 abstract public function execute( array $args );
43
44 public function get_id(): string {
45 return $this->id;
46 }
47
48 public function is_read_only(): bool {
49 return $this->read_only;
50 }
51
52 public function is_destructive(): bool {
53 return $this->destructive;
54 }
55
56 public function check_permission(): bool {
57 // Master gate. In case the ability was registered
58 // while the master toggle was on but has since been disabled.
59 if ( ! OsSettingsHelper::is_on( 'latepoint_abilities_api' ) ) {
60 return false;
61 }
62 if ( $this->destructive && ! OsSettingsHelper::is_on( 'latepoint_abilities_api_delete' ) ) {
63 return false;
64 }
65 if ( ! $this->read_only && ! $this->destructive
66 && ! OsSettingsHelper::is_on( 'latepoint_abilities_api_edit' ) ) {
67 return false;
68 }
69
70 // Role gate: when an ability declares a required $role, only that backend user type may
71 // invoke it (administrators are always allowed). Leave $role empty ('') to allow any
72 // capable backend user. Switch an ability's audience by setting $role in its configure():
73 // $this->role = LATEPOINT_USER_TYPE_ADMIN; // admin only
74 // $this->role = LATEPOINT_USER_TYPE_AGENT; // agents (and admins)
75 if ( ! empty( $this->role )
76 && OsAuthHelper::get_current_user()->backend_user_type !== $this->role ) {
77 return false;
78 }
79
80 return OsRolesHelper::can_user( $this->permission );
81 }
82
83 public function to_definition(): array {
84 return [
85 'label' => $this->label,
86 'description' => $this->description,
87 'category' => $this->category,
88 'permission_callback' => [ $this, 'check_permission' ],
89 'input_schema' => self::normalize_schema( $this->get_input_schema() ),
90 'output_schema' => self::normalize_schema( $this->get_output_schema() ),
91 'execute_callback' => [ $this, 'execute' ],
92 'meta' => $this->build_meta(),
93 ];
94 }
95
96 /**
97 * Ensure a JSON Schema serializes to valid JSON.
98 *
99 * An empty PHP array encodes as a JSON array ([]), but JSON Schema requires
100 * `properties` to be an object ({}). A tool advertising `"properties":[]`
101 * is invalid and causes strict AI clients (e.g. Claude Desktop) to reject the
102 * whole connector. Recursively coerce empty `properties` to objects.
103 *
104 * @param mixed $schema
105 * @return mixed
106 */
107 protected static function normalize_schema( $schema ) {
108 if ( ! is_array( $schema ) ) {
109 return $schema;
110 }
111 if ( array_key_exists( 'properties', $schema ) ) {
112 if ( empty( $schema['properties'] ) ) {
113 $schema['properties'] = new \stdClass();
114 } else {
115 foreach ( $schema['properties'] as $key => $value ) {
116 $schema['properties'][ $key ] = self::normalize_schema( $value );
117 }
118 }
119 }
120 if ( isset( $schema['items'] ) ) {
121 $schema['items'] = self::normalize_schema( $schema['items'] );
122 }
123 return $schema;
124 }
125
126 protected function build_meta(): array {
127
128 // Use the WordPress Abilities API annotation keys (readonly/destructive/
129 // idempotent/openWorldHint), matching SureForms, so the MCP Adapter emits
130 // concrete boolean values. Supplying only the *Hint variants leaves these
131 // base keys as null in the tool output, which strict AI clients (e.g. Claude
132 // Desktop) reject — causing the whole connector to show "no tools available".
133 $annotations = [
134 'readonly' => $this->read_only,
135 'destructive' => false,
136 'idempotent' => $this->idempotent,
137 'openWorldHint' => false,
138 'priority' => $this->read_only ? 1.0 : 2.0 ,
139 ];
140
141 // Destructive overrides everything.
142 if ( $this->destructive ) {
143 $annotations['readonly'] = false;
144 $annotations['destructive'] = true;
145 $annotations['priority'] = 3.0;
146 }
147
148 $meta = [
149 'annotations' => $annotations,
150 'show_in_rest' => true,
151 'mcp' => [
152 'public' => true,
153 'type' => 'tool',
154 ],
155 ];
156
157 return $meta;
158 }
159
160 /**
161 * Per-record ownership/scope check. Returns WP_Error (403) when the current
162 * user is not allowed to act on this specific record. Admins always pass.
163 *
164 * @param OsModel $model
165 * @param string $action one of 'view' | 'edit' | 'delete'
166 * @return true|\WP_Error
167 */
168 protected function authorize_record( OsModel $model, string $action ) {
169 if ( ! OsRolesHelper::can_user_make_action_on_model_record( $model, $action ) ) {
170 return new WP_Error(
171 'forbidden',
172 __( 'You are not allowed to access this record.', 'latepoint' ),
173 [ 'status' => 403 ]
174 );
175 }
176 return true;
177 }
178
179 protected static function pagination(): array {
180 return [
181 'page' => [
182 'type' => 'integer',
183 'default' => 1,
184 'minimum' => 1,
185 'description' => __( 'Page number.', 'latepoint' ),
186 ],
187 'per_page' => [
188 'type' => 'integer',
189 'default' => 20,
190 'minimum' => 1,
191 'maximum' => 100,
192 'description' => __( 'Items per page.', 'latepoint' ),
193 ],
194 ];
195 }
196 }
197