PluginProbe
Code Snippets / 3.7.0
Code Snippets v3.7.0
3.10.2 3.10.1 3.10.0 3.10.0-beta.2 3.10.0-beta.1 4.0.0-beta.1 3.9.6 trunk 2.10.0 2.10.1 2.12.0 2.12.1 2.13.0 2.13.1 2.13.2 2.13.3 2.14.0 2.14.1 2.14.2 2.14.3 2.14.4 2.14.5 2.14.6 3.0.0 3.0.1 All 64 releases
code-snippets / php / cloud / class-cloud-api.php

class-cloud-api.php in Code Snippets 3.7.0, at php/cloud/class-cloud-api.php

528 lines 13.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Code_Snippets\Cloud;
4
5 use Code_Snippets\Snippet;
6 use WP_Error;
7 use function Code_Snippets\get_snippet_by_cloud_id;
8 use function Code_Snippets\get_snippets;
9 use function Code_Snippets\save_snippet;
10 use function Code_Snippets\update_snippet_fields;
11
12 /**
13 * Functions used to manage cloud synchronisation.
14 *
15 * @package Code_Snippets
16 */
17 class Cloud_API {
18
19 /**
20 * Key used to access the local-to-cloud map transient data.
21 */
22 private const CLOUD_MAP_TRANSIENT_KEY = 'cs_local_to_cloud_map';
23
24 /**
25 * Days to cache data retrieved from API.
26 */
27 private const DAYS_TO_STORE_CS = 1;
28
29 /**
30 * Token used for public API access.
31 *
32 * @var string
33 */
34 private const CLOUD_SEARCH_API_TOKEN = 'csc-1a2b3c4d5e6f7g8h9i0j';
35
36 /**
37 * Cached list of cloud links.
38 *
39 * @var Cloud_Link[]|null
40 */
41 private ?array $cached_cloud_links = null;
42
43 /**
44 * 'Private' status code.
45 */
46 public const STATUS_PRIVATE = 3;
47
48 /**
49 * 'Public' status code.
50 */
51 public const STATUS_PUBLIC = 4;
52
53 /**
54 * 'Public' status code.
55 */
56 public const STATUS_UNVERIFIED = 5;
57
58 /**
59 * 'AI Verified' status code.
60 */
61 public const STATUS_AI_VERIFIED = 6;
62
63 /**
64 * 'Pro Verified' status code.
65 */
66 public const STATUS_PRO_VERIFIED = 8;
67
68 /**
69 * Retrieve the Cloud URL from wp-config or fallback to default.
70 *
71 * @return string
72 *
73 * @noinspection PhpUndefinedConstantInspection
74 */
75 public static function get_cloud_url(): string {
76 return defined( 'CS_CLOUD_URL' )
77 ? CS_CLOUD_URL
78 : 'https://codesnippets.cloud/';
79 }
80
81 /**
82 * Retrieve the Cloud API URL from wp-config or fallback to default.
83 *
84 * @return string
85 *
86 * @noinspection PhpUndefinedConstantInspection
87 */
88 public static function get_cloud_api_url(): string {
89 return defined( 'CS_CLOUD_API_URL' )
90 ? CS_CLOUD_API_URL
91 : self::get_cloud_url() . 'api/v1/';
92 }
93
94 /**
95 * Retrieve the cloud local token.
96 *
97 * @return string
98 */
99 public static function get_local_token(): string {
100 return self::CLOUD_SEARCH_API_TOKEN;
101 }
102
103 /**
104 * Check that the cloud key is valid and verified.
105 *
106 * @return boolean
107 */
108 public static function is_cloud_key_verified(): bool {
109 return false;
110 }
111
112 /**
113 * Check if the API key is set and verified.
114 *
115 * @return boolean
116 */
117 public static function is_cloud_connection_available(): bool {
118 return false;
119 }
120
121 /**
122 * Create local-to-cloud map to keep track of local snippets that have been synced to the cloud.
123 *
124 * @return Cloud_Link[]
125 */
126 private function get_cloud_links(): ?array {
127 // Return the cached data if available.
128 if ( is_array( $this->cached_cloud_links ) ) {
129 return $this->cached_cloud_links;
130 }
131
132 // Fetch data from the stored transient, if available.
133 $transient_data = get_transient( self::CLOUD_MAP_TRANSIENT_KEY );
134 if ( is_array( $transient_data ) ) {
135 $this->cached_cloud_links = $transient_data;
136 return $this->cached_cloud_links;
137 }
138
139 // Otherwise, regenerate the local-to-cloud-map.
140 $this->cached_cloud_links = [];
141
142 // Fetch and iterate through all local snippets to create the map.
143 foreach ( get_snippets() as $local_snippet ) {
144 // Skip snippets that are only stored locally.
145 if ( ! $local_snippet->cloud_id ) {
146 continue;
147 }
148
149 $link = new Cloud_Link();
150 $cloud_id_owner = $this->get_cloud_id_and_ownership( $local_snippet->cloud_id );
151 $cloud_id_int = intval( $cloud_id_owner['cloud_id'] );
152 $link->local_id = $local_snippet->id;
153 $link->cloud_id = $cloud_id_int;
154 $link->is_owner = $cloud_id_owner['is_owner'];
155 // Check if cloud id exists in cloud_id_rev array - this shows if the snippet is in the codevault.
156 $link->in_codevault = $cloud_id_rev[ $cloud_id_int ] ?? false;
157
158 // Get the cloud snippet revision if in codevault get from cloud_id_rev array otherwise get from cloud.
159 if ( $link->in_codevault ) {
160 $cloud_snippet_revision = $cloud_id_rev[ $cloud_id_int ] ?? $this->get_cloud_snippet_revision( $local_snippet->cloud_id );
161 $link->update_available = $local_snippet->revision < $cloud_snippet_revision;
162 }
163
164 $this->cached_cloud_links[] = $link;
165 }
166
167 set_transient(
168 self::CLOUD_MAP_TRANSIENT_KEY,
169 $this->cached_cloud_links,
170 DAY_IN_SECONDS * self::DAYS_TO_STORE_CS
171 );
172
173 return $this->cached_cloud_links;
174 }
175
176 /**
177 * Get ownership and Cloud ID of a snippet.
178 *
179 * @param string $cloud_id Cloud ID.
180 *
181 * @return array<string, mixed>
182 */
183 public function get_cloud_id_and_ownership( string $cloud_id ): array {
184 $cloud_id_owner = explode( '_', $cloud_id );
185
186 return [
187 'cloud_id' => (int) $cloud_id_owner[0] ?? '',
188 'is_owner' => isset( $cloud_id_owner[1] ) && $cloud_id_owner[1],
189 'is_owner_string' => isset( $cloud_id_owner[1] ) && $cloud_id_owner[1] ? '1' : '0',
190 ];
191 }
192
193 /**
194 * Unpack JSON data from a request response.
195 *
196 * @param array|WP_Error $response Response from wp_request_*.
197 *
198 * @return array<string, mixed>|null Associative array of JSON data on success, null on failure.
199 */
200 private static function unpack_request_json( $response ): ?array {
201 $body = wp_remote_retrieve_body( $response );
202 return $body ? json_decode( $body, true ) : null;
203 }
204
205 /**
206 * Search Code Snippets Cloud -> Static Function
207 *
208 * @param string $search_method Search by name of codevault or keyword(s).
209 * @param string $search Search query.
210 * @param integer $page Search result page to retrieve. Defaults to '0'.
211 *
212 * @return Cloud_Snippets Result of search query.
213 */
214 public static function fetch_search_results( string $search_method, string $search, int $page = 0 ): Cloud_Snippets {
215 $api_url = add_query_arg(
216 [
217 's_method' => $search_method,
218 's' => $search,
219 'page' => $page,
220 'site_token' => self::get_local_token(),
221 'site_host' => wp_parse_url( get_site_url(), PHP_URL_HOST ),
222 ],
223 self::get_cloud_api_url() . 'public/search'
224 );
225
226 $results = self::unpack_request_json( wp_remote_get( $api_url ) );
227
228 $results = new Cloud_Snippets( $results );
229 $results->page = $page;
230
231 return $results;
232 }
233
234 /**
235 * Add a new link item to the local-to-cloud map.
236 *
237 * @param Cloud_Link $link Link to add.
238 *
239 * @return void
240 */
241 public function add_cloud_link( Cloud_Link $link ) {
242 $local_to_cloud_map = get_transient( self::CLOUD_MAP_TRANSIENT_KEY );
243 $local_to_cloud_map[] = $link;
244
245 set_transient(
246 self::CLOUD_MAP_TRANSIENT_KEY,
247 $local_to_cloud_map,
248 DAY_IN_SECONDS * self::DAYS_TO_STORE_CS
249 );
250 }
251
252 /**
253 * Delete a snippet from local-to-cloud map.
254 *
255 * @param int $snippet_id Local snippet ID.
256 *
257 * @return void
258 */
259 public function delete_snippet_from_transient_data( int $snippet_id ) {
260 if ( ! $this->cached_cloud_links ) {
261 $this->get_cloud_links();
262 }
263
264 foreach ( $this->cached_cloud_links as $link ) {
265 if ( $link->local_id === $snippet_id ) {
266 // Remove the link from the local_to_cloud_map.
267 $index = array_search( $link, $this->cached_cloud_links, true );
268 unset( $this->cached_cloud_links[ $index ] );
269
270 // Update the transient data.
271 set_transient(
272 self::CLOUD_MAP_TRANSIENT_KEY,
273 $this->cached_cloud_links,
274 DAY_IN_SECONDS * self::DAYS_TO_STORE_CS
275 );
276 }
277 }
278 }
279
280 /**
281 * Retrieve a single cloud snippet from the API.
282 *
283 * @param int $cloud_id Remote cloud snippet ID.
284 *
285 * @return Cloud_Snippet Retrieved snippet.
286 */
287 public static function get_single_snippet_from_cloud( int $cloud_id ): Cloud_Snippet {
288 $url = self::get_cloud_api_url() . sprintf( 'public/getsnippet/%s', $cloud_id );
289 $response = wp_remote_get( $url );
290 $cloud_snippet = self::unpack_request_json( $response );
291 return new Cloud_Snippet( $cloud_snippet['snippet'] );
292 }
293
294 /**
295 * Get the current revision of a single cloud snippet.
296 *
297 * @param string $cloud_id Cloud snippet ID.
298 *
299 * @return string|null Revision number on success, null otherwise.
300 */
301 public static function get_cloud_snippet_revision( string $cloud_id ): ?string {
302 $api_url = self::get_cloud_api_url() . sprintf( 'public/getsnippetrevision/%s', $cloud_id );
303 $body = wp_remote_retrieve_body( wp_remote_get( $api_url ) );
304
305 if ( ! $body ) {
306 return null;
307 }
308
309 $cloud_snippet_revision = json_decode( $body, true );
310 return $cloud_snippet_revision['snippet_revision'] ?? null;
311 }
312
313 /**
314 * Download a snippet from the cloud.
315 *
316 * @param int|string $cloud_id The cloud ID of the snippet as string from query args.
317 * @param string $source Unused in Core.
318 * @param string $action The action to be performed: 'download' or 'update'.
319 *
320 * @return array<string, string|bool> Result of operation: an array with `success` and `error_message` keys.
321 *
322 * @noinspection PhpUnusedParameterInspection
323 */
324 public function download_or_update_snippet( int $cloud_id, string $source, string $action ): array {
325 $cloud_id = intval( $cloud_id );
326 $snippet_to_store = $this->get_single_snippet_from_cloud( $cloud_id );
327
328 switch ( $action ) {
329 case 'download':
330 return $this->download_snippet_from_cloud( $snippet_to_store );
331 case 'update':
332 return $this->update_snippet_from_cloud( $snippet_to_store );
333 default:
334 return [
335 'success' => false,
336 'error' => __( 'Invalid action.', 'code-snippets' ),
337 ];
338 }
339 }
340
341 /**
342 * Download a snippet from the cloud.
343 *
344 * @param Cloud_Snippet $snippet_to_store The snippet to be downloaded.
345 *
346 * @return array The result of the download.
347 */
348 public function download_snippet_from_cloud( Cloud_Snippet $snippet_to_store ): array {
349 $snippet = new Snippet( $snippet_to_store );
350
351 // Set the snippet id to 0 to ensure that the snippet is saved as a new snippet.
352 $ownership = $snippet_to_store->is_owner ? '1' : '0';
353 $snippet->id = 0;
354 $snippet->active = 0;
355 $snippet->cloud_id = $snippet_to_store->id . '_' . $ownership;
356 $snippet->desc = $snippet_to_store->description ? $snippet_to_store->description : '';
357
358 // Save the snippet to the database.
359 $new_snippet = save_snippet( $snippet );
360
361 $link = new Cloud_Link();
362 $link->local_id = $new_snippet->id;
363 $link->cloud_id = $snippet_to_store->id;
364 $link->is_owner = $snippet_to_store->is_owner;
365 $link->in_codevault = false;
366 $link->update_available = false;
367
368 $this->add_cloud_link( $link );
369
370 return [
371 'success' => true,
372 'action' => 'Single Downloaded',
373 'snippet_id' => $new_snippet->id,
374 'link_id' => $link->cloud_id,
375 ];
376 }
377
378 /**
379 * Update a snippet from the cloud.
380 *
381 * @param Cloud_Snippet $snippet_to_store Snippet to be updated.
382 *
383 * @return array The result of the update.
384 */
385 public function update_snippet_from_cloud( Cloud_Snippet $snippet_to_store ): array {
386 $cloud_id = $snippet_to_store->id . '_' . ( $snippet_to_store->is_owner ? '1' : '0' );
387
388 $local_snippet = get_snippet_by_cloud_id( sanitize_key( $cloud_id ) );
389
390 // Only update the code, active and revision fields.
391 $fields = [
392 'code' => $snippet_to_store->code,
393 'active' => false,
394 'revision' => $snippet_to_store->revision,
395 ];
396
397 update_snippet_fields( $local_snippet->id, $fields );
398 $this->clear_caches();
399
400 return [
401 'success' => true,
402 'action' => __( 'Updated', 'code-snippets' ),
403 ];
404 }
405
406 /**
407 * Find the cloud link for a given cloud snippet identifier.
408 *
409 * @param int $cloud_id Cloud ID.
410 *
411 * @return Cloud_Link|null
412 */
413 public function get_link_for_cloud_id( int $cloud_id ): ?Cloud_Link {
414 $cloud_links = $this->get_cloud_links();
415
416 if ( $cloud_links ) {
417 foreach ( $cloud_links as $cloud_link ) {
418 if ( $cloud_link->cloud_id === $cloud_id ) {
419 return $cloud_link;
420 }
421 }
422 }
423
424 return null;
425 }
426
427
428 /**
429 * Find the cloud link for a given cloud snippet.
430 *
431 * @param Cloud_Snippet $cloud_snippet Cloud snippet.
432 *
433 * @return Cloud_Link|null
434 */
435 public function get_link_for_cloud_snippet( Cloud_Snippet $cloud_snippet ): ?Cloud_Link {
436 return $this->get_link_for_cloud_id( $cloud_snippet->id );
437 }
438
439 /**
440 * Translate a snippet scope to a type.
441 *
442 * @param string $scope The scope of the snippet.
443 *
444 * @return string The type of the snippet.
445 */
446 public static function get_type_from_scope( string $scope ): string {
447 switch ( $scope ) {
448 case 'global':
449 return 'php';
450 case 'site-css':
451 return 'css';
452 case 'site-footer-js':
453 return 'js';
454 case 'content':
455 return 'html';
456 default:
457 return '';
458 }
459 }
460
461 /**
462 * Get the label for a given cloud status.
463 *
464 * @param int $status Cloud status code.
465 *
466 * @return string The label for the status.
467 */
468 public static function get_status_label( int $status ): string {
469 $labels = [
470 self::STATUS_PRIVATE => __( 'Private', 'code-snippets' ),
471 self::STATUS_PUBLIC => __( 'Public', 'code-snippets' ),
472 self::STATUS_UNVERIFIED => __( 'Unverified', 'code-snippets' ),
473 self::STATUS_AI_VERIFIED => __( 'AI Verified', 'code-snippets' ),
474 self::STATUS_PRO_VERIFIED => __( 'Pro Verified', 'code-snippets' ),
475 ];
476
477 return $labels[ $status ] ?? __( 'Unknown', 'code-snippets' );
478 }
479
480 /**
481 * Get the badge class for a given cloud status.
482 *
483 * @param int $status Cloud status code.
484 *
485 * @return string
486 */
487 public static function get_status_badge( int $status ): string {
488 $badge_names = [
489 self::STATUS_PRIVATE => 'private',
490 self::STATUS_PUBLIC => 'public',
491 self::STATUS_UNVERIFIED => 'failure',
492 self::STATUS_AI_VERIFIED => 'success',
493 self::STATUS_PRO_VERIFIED => 'info',
494 ];
495
496 return $badge_names[ $status ] ?? 'neutral';
497 }
498
499 /**
500 * Renders the html for the preview thickbox popup.
501 *
502 * @return void
503 */
504 public static function render_cloud_snippet_thickbox() {
505 add_thickbox();
506 ?>
507 <div id="show-code-preview" style="display: none;">
508 <h3 id="snippet-name-thickbox"></h3>
509 <h4><?php esc_html_e( 'Snippet Code:', 'code-snippets' ); ?></h4>
510 <pre class="thickbox-code-viewer">
511 <code id="snippet-code-thickbox"></code>
512 </pre>
513 </div>
514 <?php
515 }
516
517 /**
518 * Refresh the cached synced data.
519 *
520 * @return void
521 */
522 public function clear_caches() {
523 $this->cached_cloud_links = null;
524
525 delete_transient( self::CLOUD_MAP_TRANSIENT_KEY );
526 }
527 }
528