PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 3.3.2
Visualizer – Tables & Charts Manager with Built-in AI Generator v3.3.2
4.0.8 4.0.7 4.0.6 4.0.5 4.0.4 4.0.3 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 3.1.2 3.1.3 3.10.0 3.10.1 3.10.10 3.10.11 3.10.12 3.10.13 3.10.14 3.10.15 3.10.2 3.10.3 All 149 releases
visualizer / vendor / codeinwp / themeisle-sdk / src / Modules / Endpoint.php

Endpoint.php in Visualizer – Tables & Charts Manager with Built-in AI Generator 3.3.2, at vendor/codeinwp/themeisle-sdk/src/Modules/Endpoint.php

359 lines 8.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * The class that exposes endpoints.
4 *
5 * @package ThemeIsleSDK
6 * @subpackage Rollback
7 * @copyright Copyright (c) 2017, Marius Cristea
8 * @license http://opensource.org/licenses/gpl-3.0.php GNU Public License
9 * @since 1.0.0
10 */
11
12 namespace ThemeisleSDK\Modules;
13
14 // Exit if accessed directly.
15 use ThemeisleSDK\Common\Abstract_Module;
16 use ThemeisleSDK\Loader;
17 use ThemeisleSDK\Product;
18
19 if ( ! defined( 'ABSPATH' ) ) {
20 exit;
21 }
22
23 /**
24 * Expose endpoints for ThemeIsle SDK.
25 */
26 class Endpoint extends Abstract_Module {
27 /**
28 * Endpoint slug.
29 */
30 const SDK_ENDPOINT = 'themeisle-sdk';
31 /**
32 * Endpoint version.
33 */
34 const SDK_ENDPOINT_VERSION = 1;
35 /**
36 * Hash file which contains the checksum.
37 */
38 const HASH_FILE = 'themeisle-hash.json';
39
40 /*
41 * If true, the endpoint will expect a product slug and will return the value only for that.
42 */
43 const PRODUCT_SPECIFIC = false;
44
45 /**
46 * Registers the endpoints.
47 */
48 function rest_register() {
49 register_rest_route(
50 self::SDK_ENDPOINT . '/v' . self::SDK_ENDPOINT_VERSION,
51 '/checksum/' . ( self::PRODUCT_SPECIFIC ? '(?P<slug>.*)/' : '' ),
52 array(
53 'methods' => 'GET',
54 'callback' => array( $this, 'checksum' ),
55 )
56 );
57 }
58
59 /**
60 * The checksum endpoint.
61 *
62 * @param \WP_REST_Request $data the request.
63 *
64 * @return \WP_REST_Response Response or the error
65 */
66 function checksum( \WP_REST_Request $data ) {
67 $products = Loader::get_products();
68 if ( self::PRODUCT_SPECIFIC ) {
69 $params = $this->validate_params( $data, array( 'slug' ) );
70 foreach ( $products as $product ) {
71 if ( $params['slug'] === $product->get_slug() ) {
72 $products = array( $product );
73 break;
74 }
75 }
76 }
77 $response = array();
78 $custom_css = $this->has_custom_css();
79 if ( is_bool( $custom_css ) ) {
80 $response['custom_css'] = $custom_css;
81 }
82
83 $response['child_theme'] = $this->get_theme_properties();
84
85 foreach ( $products as $product ) {
86 $files = array();
87 switch ( $product->get_type() ) {
88 case 'plugin':
89 $files = array();
90 break;
91 case 'theme':
92 $files = array( 'style.css', 'functions.php' );
93 break;
94 }
95
96 $error = '';
97
98 // if any element in the $files array contains a '/', this would imply recursion is required.
99 $diff = $this->generate_diff(
100 $product,
101 $files,
102 array_reduce(
103 $files,
104 array(
105 $this,
106 'is_recursion_required',
107 ),
108 false
109 )
110 );
111 if ( is_wp_error( $diff ) ) {
112 /**
113 * Error returner by the diff checker method.
114 *
115 * @var \WP_Error $diff Error returned.
116 */
117 $error = $diff->get_error_message();
118 $diff = array();
119 }
120
121 $response['products'][] = array(
122 'slug' => $product->get_slug(),
123 'version' => $product->get_version(),
124 'diffs' => $diff,
125 'error' => $error,
126 );
127 }
128
129 return new \WP_REST_Response( array( 'checksum' => $response ) );
130 }
131
132 /**
133 * Validates the parameters to the API
134 *
135 * @param \WP_REST_Request $data the request.
136 * @param array $params the parameters to validate.
137 *
138 * @return array of parameter name=>value
139 */
140 private function validate_params( \WP_REST_Request $data, $params ) {
141 $collect = array();
142 foreach ( $params as $param ) {
143 $value = sanitize_text_field( $data[ $param ] );
144 if ( empty( $value ) ) {
145 return rest_ensure_response(
146 new \WP_Error(
147 'themeisle_' . $param . '_invalid',
148 sprintf( 'Invalid %', $param ),
149 array(
150 'status' => 403,
151 )
152 )
153 );
154 } else {
155 $collect[ $param ] = $value;
156 }
157 }
158
159 return $collect;
160 }
161
162 /**
163 * Check if custom css has been added to the theme.
164 *
165 * @return bool Whether custom css has been added to the theme.
166 */
167 private function has_custom_css() {
168 $query = new \WP_Query(
169 array(
170 'post_type' => 'custom_css',
171 'post_status' => 'publish',
172 'numberposts' => 1,
173 'update_post_meta_cache' => false,
174 'update_post_term_cache' => false,
175 )
176 );
177
178 if ( $query->have_posts() ) {
179 $query->the_post();
180 $content = get_the_content();
181
182 // if the content contains a colon, a CSS rule has been added.
183 return strpos( $content, ':' ) === false ? false : true;
184 }
185
186 return false;
187 }
188
189 /**
190 * Get the current theme properties.
191 *
192 * @return mixed Properties of the current theme.
193 */
194 function get_theme_properties() {
195 if ( ! is_child_theme() ) {
196 return false;
197 }
198
199 $properties = array();
200 $theme = wp_get_theme();
201 // @codingStandardsIgnoreStart
202 $properties['name'] = $theme->Name;
203 // @codingStandardsIgnoreEnd
204
205 // get the files in the child theme.
206 require_once( ABSPATH . 'wp-admin/includes/file.php' );
207 WP_Filesystem();
208 global $wp_filesystem;
209 $path = str_replace( ABSPATH, $wp_filesystem->abspath(), get_stylesheet_directory() );
210 $list = $wp_filesystem->dirlist( $path, false, false );
211 if ( $list ) {
212 $list = array_keys( self::flatten_dirlist( $list ) );
213 $properties['files'] = $list;
214 }
215
216 return $properties;
217 }
218
219 /**
220 * Flatten the results of WP_Filesystem::dirlist() for iterating over.
221 *
222 * @access private
223 *
224 * @param array $nested_files Array of files as returned by WP_Filesystem::dirlist().
225 * @param string $path Relative path to prepend to child nodes. Optional.
226 *
227 * @return array $files A flattened array of the $nested_files specified.
228 */
229 private static function flatten_dirlist( $nested_files, $path = '' ) {
230 $files = array();
231 foreach ( $nested_files as $name => $details ) {
232 $files[ $path . $name ] = $details;
233 // Append children recursively.
234 if ( ! empty( $details['files'] ) ) {
235 $children = self::flatten_dirlist( $details['files'], $path . $name . '/' );
236 // Merge keeping possible numeric keys, which array_merge() will reindex from 0..n.
237 $files = $files + $children;
238 }
239 }
240
241 return $files;
242 }
243
244 /**
245 * Generate the diff of the files.
246 *
247 * @param Product $product Themeisle Product.
248 * @param array $files Array of files.
249 * @param bool $recurse Whether to recurse or not.
250 *
251 * @return mixed Diff data.
252 */
253 private function generate_diff( $product, $files, $recurse = false ) {
254 require_once( ABSPATH . 'wp-admin/includes/file.php' );
255 WP_Filesystem();
256 global $wp_filesystem;
257
258 $diff = array();
259 $path = str_replace( ABSPATH, $wp_filesystem->abspath(), plugin_dir_path( $product->get_basefile() ) );
260 $list = $wp_filesystem->dirlist( $path, false, $recurse );
261 // nothing found.
262 if ( ! $list ) {
263 return $diff;
264 }
265 $list = array_keys( self::flatten_dirlist( $list ) );
266
267 // now let's get the valid files that actually exist.
268 if ( empty( $files ) ) {
269 $files = $list;
270 } else {
271 $files = array_intersect( $files, $list );
272 }
273
274 // fetch the calculated hashes.
275 if ( ! $wp_filesystem->is_readable( $path . '/' . self::HASH_FILE ) ) {
276 return new \WP_Error( 'themeisle_sdk_hash_not_found', sprintf( '%s not found', self::HASH_FILE ) );
277 }
278
279 $hashes = json_decode( $wp_filesystem->get_contents( $path . '/' . self::HASH_FILE ), true );
280 ksort( $hashes );
281
282 $diff = array();
283 foreach ( $files as $file ) {
284 // file does not exist in the hashes.
285 if ( ! array_key_exists( $file, $hashes ) ) {
286 continue;
287 }
288 $new = md5( $wp_filesystem->get_contents( $path . $file ) );
289 $old = $hashes[ $file ];
290
291 // same hash, bail.
292 if ( $new === $old ) {
293 continue;
294 }
295 $diff[] = $file;
296 }
297
298 return $diff;
299 }
300
301 /**
302 * Check if recursion needs to be enabled on the WP_Filesystem by reducing the array of files to a boolean.
303 *
304 * @param string $carry Value of the previous iteration.
305 * @param string $item Value of the current iteration.
306 *
307 * @return bool Whether to recurse or not.
308 */
309 function is_recursion_required( $carry, $item ) {
310 if ( ! $carry ) {
311 return ( strpos( $item, '/' ) !== false );
312 }
313
314 return $carry;
315 }
316
317 /**
318 * Load module for this product.
319 *
320 * @param Product $product Product to check.
321 *
322 * @return bool Should we load this?
323 */
324 public function can_load( $product ) {
325 return true;
326 }
327
328 /**
329 * Load module logic.
330 *
331 * @param Product $product Product to load.
332 */
333 public function load( $product ) {
334 $this->setup_endpoints();
335
336 return $this;
337 }
338
339 /**
340 * Setup endpoints.
341 */
342 private function setup_endpoints() {
343 global $wp_version;
344 if ( version_compare( $wp_version, '4.4', '<' ) ) {
345 // no REST support.
346 return;
347 }
348
349 $this->setup_rest();
350 }
351
352 /**
353 * Setup REST endpoints.
354 */
355 private function setup_rest() {
356 add_action( 'rest_api_init', array( $this, 'rest_register' ) );
357 }
358 }
359