PluginProbe
Code Snippets / 3.6.3
Code Snippets v3.6.3
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.6.3, at php/cloud/class-cloud-api.php

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