PluginProbe
Elementor Website Builder – more than just a page builder / 4.3.3
Elementor Website Builder – more than just a page builder v4.3.3
4.3.3 4.3.2 4.3.1 4.3.0 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 All 456 releases
elementor / modules / mcp / abilities / abstract-ability.php

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

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