PluginProbe
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! / 3.8.0
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! v3.8.0
3.8.0 3.7.5 3.7.4 3.7.3 3.7.2 1-final 3.7.1 3.7.0 3.6.8 3.6.7 3.6.6 3.6.5 3.6.4 3.6.3 3.6.2 3.6.1 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 All 112 releases
templately / modules / full-site-import / Abilities / InspectFullSitePackAbility.php

InspectFullSitePackAbility.php in Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! 3.8.0, at modules/full-site-import/Abilities/InspectFullSitePackAbility.php

246 lines 8.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * `templately/inspect-full-site-pack` — pre-import summary for a full-site pack
4 * (spec 042 US3, FR-009/008). Pure read: no mutation, no loopback.
5 *
6 * Calls the same pack-info API the browser uses
7 * (`Helper::make_api_get_request("v2/import/info/pack/{id}")`, as in
8 * `Ajax\PackInfoController::import_info`) directly in-process, then computes
9 * per-plugin installed/active readiness against the site.
10 *
11 * @package Templately\Modules\FullSiteImport\Abilities
12 */
13
14 namespace Templately\Modules\FullSiteImport\Abilities;
15
16 use Templately\Modules\McpCore\Registry\ToolDescriptor;
17 use Templately\Modules\McpCore\Support\Permissions;
18 use Templately\Utils\Helper;
19 use WP_Error;
20
21 class InspectFullSitePackAbility {
22
23 const ID = 'templately/inspect-full-site-pack';
24
25 public static function descriptor(): array {
26 return [
27 'id' => self::ID,
28 'label' => __( 'Inspect Templately Full Site Pack', 'templately' ),
29 'description' => __( 'Preview a full-site pack before importing: its required plugins with installed/active status and a summary of the content it will import. Performs no changes.', 'templately' ),
30 'input_schema' => [
31 'type' => 'object',
32 'properties' => [
33 'id' => [ 'type' => 'integer', 'description' => __( 'Full-site pack id.', 'templately' ) ],
34 'platform' => [ 'type' => 'string', 'enum' => [ 'elementor', 'gutenberg' ], 'default' => 'elementor' ],
35 ],
36 'required' => [ 'id' ],
37 'additionalProperties' => false,
38 ],
39 'output_schema' => [
40 'type' => 'object',
41 'properties' => [
42 'id' => [ 'type' => 'integer' ],
43 'name' => [ 'type' => 'string' ],
44 'platform' => [ 'type' => 'string' ],
45 'required_plugins' => [ 'type' => 'array' ],
46 'unmet_dependencies' => [ 'type' => 'array' ],
47 'content_summary' => [ 'type' => 'object' ],
48 ],
49 ],
50 'execute_callback' => [ self::class, 'execute' ],
51 'permission_callback' => [ Permissions::class, 'can_use_abilities' ],
52 'access_level' => ToolDescriptor::ACCESS_READ,
53 'annotations' => [ 'readonly' => true, 'destructive' => false, 'idempotent' => true ],
54 ];
55 }
56
57 /**
58 * @param array $input
59 * @return array|WP_Error
60 */
61 public static function execute( array $input ) {
62 $id = (int) ( $input['id'] ?? 0 );
63 if ( empty( $id ) ) {
64 return new WP_Error( 'pack_not_found', __( 'A full-site pack id is required.', 'templately' ) );
65 }
66
67 $response = Helper::make_api_get_request( "v2/import/info/pack/{$id}", [], [], 30 );
68 if ( is_wp_error( $response ) ) {
69 return new WP_Error( 'not_connected', $response->get_error_message() );
70 }
71 if ( 200 !== (int) wp_remote_retrieve_response_code( $response ) ) {
72 return new WP_Error( 'pack_not_found', __( 'The pack could not be found or is not accessible with this account.', 'templately' ) );
73 }
74
75 $data = json_decode( wp_remote_retrieve_body( $response ), true );
76 if ( ! is_array( $data ) || isset( $data['error'] ) ) {
77 $message = is_array( $data ) && isset( $data['error'] ) ? $data['error'] : __( 'The pack info could not be read.', 'templately' );
78 return new WP_Error( 'pack_not_found', $message );
79 }
80
81 $pack = $data['data'] ?? [];
82 $manifest = self::decode( $pack['manifest'] ?? null );
83
84 $required_plugins = self::resolve_plugin_readiness( $manifest );
85 $unmet = array_values( array_filter( $required_plugins, static function ( $p ) {
86 return empty( $p['installed'] ) || empty( $p['active'] );
87 } ) );
88
89 return [
90 'id' => $id,
91 'name' => (string) ( $pack['name'] ?? '' ),
92 'platform' => (string) ( $manifest['platform'] ?? ( $input['platform'] ?? 'elementor' ) ),
93 'required_plugins' => $required_plugins,
94 'unmet_dependencies' => $unmet,
95 'content_summary' => self::content_summary( $manifest ),
96 ];
97 }
98
99 /**
100 * Normalize the manifest's required-plugin list.
101 *
102 * **The manifest key is `dependencies`, NOT `plugins`.** Reading `plugins`
103 * made this return an empty list for EVERY pack, so `unmet_dependencies` was
104 * always `[]` and the pre-import readiness report was silently useless — it
105 * reported a pack as ready even with its builder deactivated. Verified live
106 * against pack 481, whose manifest carries:
107 *
108 * "dependencies": ["elementor","essential-addons-for-elementor-lite",
109 * "essential-addons-elementor","fluent-forms"]
110 *
111 * i.e. a flat array of SLUGS, not objects. `plugins` is still accepted first
112 * because packs that do ship a richer object list (name/plugin_file/is_pro)
113 * carry more detail than a bare slug.
114 *
115 * @param array $manifest
116 * @return array List of plugin descriptors (associative arrays).
117 */
118 private static function manifest_plugins( array $manifest ): array {
119 $plugins = $manifest['plugins'] ?? [];
120
121 if ( is_array( $plugins ) && ! empty( $plugins ) ) {
122 return $plugins;
123 }
124
125 $dependencies = $manifest['dependencies'] ?? [];
126
127 if ( ! is_array( $dependencies ) || empty( $dependencies ) ) {
128 return [];
129 }
130
131 $normalized = [];
132
133 foreach ( $dependencies as $dependency ) {
134 // Already an object/array form — pass through.
135 if ( is_array( $dependency ) || is_object( $dependency ) ) {
136 $normalized[] = (array) $dependency;
137 continue;
138 }
139
140 $slug = (string) $dependency;
141
142 if ( '' === $slug ) {
143 continue;
144 }
145
146 $normalized[] = [
147 'name' => $slug,
148 'slug' => $slug,
149 'plugin_original_slug' => $slug,
150 ];
151 }
152
153 return $normalized;
154 }
155
156 /**
157 * Build the required-plugin list with installed/active flags for this site.
158 *
159 * @param array $manifest
160 * @return array
161 */
162 private static function resolve_plugin_readiness( array $manifest ): array {
163 $plugins = self::manifest_plugins( $manifest );
164 if ( empty( $plugins ) ) {
165 return [];
166 }
167
168 if ( ! function_exists( 'get_plugins' ) ) {
169 require_once ABSPATH . 'wp-admin/includes/plugin.php';
170 }
171 $installed = array_keys( get_plugins() );
172
173 $result = [];
174 foreach ( $plugins as $plugin ) {
175 $plugin = (array) $plugin;
176 $plugin_file = (string) ( $plugin['plugin_file'] ?? $plugin['plugin_original_slug'] ?? $plugin['slug'] ?? '' );
177
178 $is_installed = false;
179 $is_active = false;
180 if ( '' !== $plugin_file ) {
181 // The plugin DIRECTORY is the reliable identity. A bare slug
182 // cannot be turned into a file name by guessing "{slug}/{slug}.php":
183 // essential-addons-for-elementor-lite's real entry file is
184 // essential_adons_elementor.php, so that guess misses it and the
185 // plugin is reported not-installed while sitting right there.
186 $wanted_dir = ( false !== strpos( $plugin_file, '/' ) )
187 ? dirname( $plugin_file )
188 : $plugin_file;
189
190 foreach ( $installed as $file ) {
191 if ( $file === $plugin_file || dirname( $file ) === $wanted_dir ) {
192 $is_installed = true;
193 $is_active = is_plugin_active( $file );
194 break;
195 }
196 }
197 }
198
199 $result[] = [
200 'name' => (string) ( $plugin['name'] ?? $plugin_file ),
201 'slug' => (string) ( $plugin['slug'] ?? $plugin['plugin_original_slug'] ?? '' ),
202 'plugin_file' => $plugin_file,
203 'is_pro' => ! empty( $plugin['is_pro'] ),
204 'installed' => $is_installed,
205 'active' => $is_active,
206 ];
207 }
208
209 return $result;
210 }
211
212 /**
213 * Coarse content-scope summary from the manifest (counts of array buckets).
214 *
215 * @param array $manifest
216 * @return array
217 */
218 private static function content_summary( array $manifest ): array {
219 $summary = [];
220 foreach ( [ 'plugins', 'templates', 'content', 'pages', 'posts', 'attachments' ] as $bucket ) {
221 if ( isset( $manifest[ $bucket ] ) && is_array( $manifest[ $bucket ] ) ) {
222 $summary[ $bucket ] = count( $manifest[ $bucket ] );
223 }
224 }
225 if ( isset( $manifest['name'] ) ) {
226 $summary['pack_name'] = (string) $manifest['name'];
227 }
228 return $summary;
229 }
230
231 /**
232 * @param mixed $value JSON string or already-decoded array.
233 * @return array
234 */
235 private static function decode( $value ): array {
236 if ( is_array( $value ) ) {
237 return $value;
238 }
239 if ( is_string( $value ) ) {
240 $decoded = json_decode( $value, true );
241 return is_array( $decoded ) ? $decoded : [];
242 }
243 return [];
244 }
245 }
246