PluginProbe
Elementor Website Builder – more than just a page builder / 4.3.0-beta3
Elementor Website Builder – more than just a page builder v4.3.0-beta3
4.3.0-beta3 4.3.0-beta2 4.3.0-beta1 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.5 4.2.0-beta2 4.2.0-dev2 4.2.0-beta1 4.1.4 4.1.3 4.1.2 4.1.1 4.1.0 4.1.0-beta3 4.1.0-dev3 4.0.9 4.1.0-beta2 4.1.0-dev2 4.0.8 4.1.0-beta1 4.1.0-dev1 All 452 releases
elementor / modules / mcp / abilities / abstract-ability.php

abstract-ability.php in Elementor Website Builder – more than just a page builder 4.3.0-beta3, at modules/mcp/abilities/abstract-ability.php

175 lines 4.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Elementor\Modules\Mcp\Abilities;
4
5 use Elementor\Modules\Mcp\Utils\Editor_Sync_State;
6 use Elementor\Modules\Mcp\Utils\Mcp_V4_Gate;
7
8 if ( ! defined( 'ABSPATH' ) ) {
9 exit;
10 }
11
12 abstract class Abstract_Ability {
13
14 const KIND_TOOL = 'tool';
15 const KIND_RESOURCE = 'resource';
16 const ABILITY_ID_PREFIX = 'elementor/';
17
18 private ?Ability_Definition $cached_definition = null;
19
20 abstract protected function get_ability_id(): string;
21
22 abstract protected function get_definition(): Ability_Definition;
23
24 abstract public function execute( $input = [] );
25
26 final public function execute_guarded( $input = [] ) {
27 $availability = $this->is_available();
28 if ( is_wp_error( $availability ) ) {
29 return $availability;
30 }
31
32 $is_mutating = self::KIND_TOOL === $this->get_kind() && $this->is_destructive();
33
34 if ( $is_mutating ) {
35 $guard = apply_filters( 'elementor/mcp/pre_execute_guard', null, $input );
36 if ( is_wp_error( $guard ) ) {
37 return $guard;
38 }
39 }
40
41 $result = $this->execute( $input );
42 $post_id = isset( $input['post_id'] ) ? (int) $input['post_id'] : 0;
43
44 $is_failed = is_wp_error( $result ) || ( is_array( $result ) && 'error' === ( $result['status'] ?? '' ) );
45
46 if ( $is_mutating && $post_id > 0 && ! $is_failed ) {
47 Editor_Sync_State::set_mcp_mutation( $post_id );
48 }
49
50 return $result;
51 }
52
53 public function check_permission(): bool {
54 return (bool) call_user_func( $this->definition()->permission_callback );
55 }
56
57 public function register(): void {
58 $definition = $this->definition()->to_array();
59 $definition['execute_callback'] = [ $this, 'execute_guarded' ];
60 $definition['description'] = $this->get_description_for_llm();
61
62 $meta = is_array( $definition['meta'] ?? null ) ? $definition['meta'] : [];
63 $mcp = is_array( $meta['mcp'] ?? null ) ? $meta['mcp'] : [];
64 $mcp['public'] = true;
65 if ( isset( $mcp['description'] ) ) {
66 $mcp['description'] = $this->maybe_append_unavailable_notice( (string) $mcp['description'] );
67 }
68 $meta['mcp'] = $mcp;
69 $meta['show_in_rest'] = true;
70 $definition['meta'] = $meta;
71 wp_register_ability( $this->get_id(), $definition );
72 }
73
74 public function get_id(): string {
75 return $this->get_ability_id();
76 }
77
78 public function get_kind(): string {
79 return $this->is_resource() ? self::KIND_RESOURCE : self::KIND_TOOL;
80 }
81
82 public function get_uri(): ?string {
83 return $this->mcp_meta()['uri'] ?? null;
84 }
85
86 public function get_mime_type(): ?string {
87 return $this->mcp_meta()['mimeType'] ?? null;
88 }
89
90 public function get_resource_description(): ?string {
91 $base = $this->mcp_meta()['description'] ?? $this->definition()->description;
92
93 return $this->maybe_append_unavailable_notice( (string) $base );
94 }
95
96 public function get_description_for_llm(): string {
97 return $this->maybe_append_unavailable_notice( (string) $this->definition()->description );
98 }
99
100 public function get_display_name(): string {
101 return (string) ( $this->mcp_meta()['name'] ?? $this->definition()->label );
102 }
103
104 public function get_proxy_slug(): string {
105 if ( self::KIND_RESOURCE === $this->get_kind() ) {
106 return (string) $this->get_uri();
107 }
108
109 return substr( $this->get_id(), strlen( self::ABILITY_ID_PREFIX ) );
110 }
111
112 /**
113 * Whether this ability may currently run.
114 *
115 * Subclasses may override to add reasons beyond the default Atomic Editor gate
116 * (missing plugin, licence tier, post-type support, ...). Return `true`
117 * when available, or a `\WP_Error` explaining why not. Include a
118 * `description_notice` entry in the error data to add a short hint to
119 * this ability's description in `tools/list` / `elementor/list-resources`.
120 *
121 * @return true|\WP_Error
122 */
123 public function is_available() {
124 return Mcp_V4_Gate::is_available( $this->get_id() );
125 }
126
127 public function is_exposed_via_proxy(): bool {
128 return true;
129 }
130
131 public function is_exposed_on_server(): bool {
132 return true;
133 }
134
135 private function is_resource(): bool {
136 return self::KIND_RESOURCE === ( $this->mcp_meta()['type'] ?? null );
137 }
138
139 private function is_destructive(): bool {
140 return false !== ( $this->definition()->meta['annotations']['destructive'] ?? true );
141 }
142
143 protected function definition(): Ability_Definition {
144 if ( null === $this->cached_definition ) {
145 $this->cached_definition = $this->get_definition();
146 }
147
148 return $this->cached_definition;
149 }
150
151 private function mcp_meta(): array {
152 $meta = $this->definition()->meta;
153
154 return is_array( $meta['mcp'] ?? null ) ? $meta['mcp'] : [];
155 }
156
157 private function maybe_append_unavailable_notice( string $description ): string {
158 $availability = $this->is_available();
159 if ( ! is_wp_error( $availability ) ) {
160 return $description;
161 }
162
163 $data = $availability->get_error_data();
164 $notice = is_array( $data ) && isset( $data['description_notice'] )
165 ? (string) $data['description_notice']
166 : $availability->get_error_message();
167
168 if ( '' === $notice ) {
169 return $description;
170 }
171
172 return '' === $description ? $notice : rtrim( $description ) . ' ' . $notice;
173 }
174 }
175