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 / template-browsing / REST / Items.php

Items.php in Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! 3.8.0, at modules/template-browsing/REST/Items.php

654 lines 18.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Templately\Modules\TemplateBrowsing\REST;
4
5 use Templately\API\API;
6
7 use WP_REST_Request;
8 use Templately\Utils\Database;
9
10 use function json_decode;
11
12 class Items extends API {
13 public function permission_check( WP_REST_Request $request ) {
14 $this->request = $request;
15
16 $_route = $request->get_route();
17 if( $_route === '/templately/v1/items/favourite' || $_route === '/templately/v1/items/rating' ) {
18 return parent::permission_check( $request );
19 }
20
21 return true;
22 }
23
24 public function register_routes() {
25 $this->get( 'items', [ $this, 'get_items' ], [
26 'type' => [
27 'default' => 'items',
28 'required' => false,
29 // 'validate_callback' => function( $param, $request, $key ){
30 // return in_array( $param, [ 'items', 'sections', 'blocks', 'packs', 'pages' ], true );
31 // }
32 ],
33 'platform' => [
34 'default' => 'elementor',
35 'required' => false
36 ],
37 'per_page' => [
38 'default' => 40,
39 'required' => false
40 ],
41 'sort_by' => [
42 'default' => 'latest',
43 'required' => false,
44 'validate_callback' => function( $param, $request, $key ){
45 return empty( $param ) || in_array( $param, [ 'latest', 'rating', 'download' ], true );
46 }
47 ],
48 ] );
49
50 // only number
51 $this->get( 'items/(?P<id>[0-9]+)', [ $this, 'get_item_by_id' ], [
52 'id' => [
53 'required' => true,
54 ],
55 'type' => [
56 'default' => 'block'
57 ]
58 ] );
59
60 // with slug
61 $this->get( 'items/(?P<slug>[a-zA-Z0-9-]+)', [ $this, 'get_item' ], [
62 'slug' => [
63 'required' => true,
64 ],
65 'type' => [
66 'default' => 'block'
67 ]
68 ] );
69
70 $this->get( 'items/search/(?P<keyword>[a-zA-Z0-9-]+)', [ $this, 'get_search_results' ], [
71 'keyword' => [
72 'required' => true,
73 ],
74 'platform' => [
75 'default' => 'elementor',
76 ],
77 'query_type' => [
78 'default' => 'block',
79 ],
80 ] );
81
82 $this->post( 'items/favourite', [ $this, 'set_favourite' ] );
83 $this->post( 'items/rating', [ $this, 'set_rating' ] );
84 $this->get( 'items-count', [ $this, 'get_counts' ] );
85 $this->get( 'featured-items', [ $this, 'featured_items' ] );
86 $this->get( 'trending-items', [ $this, 'trending_items' ] );
87 $this->get( 'related-items', [ $this, 'related_items' ]);
88 }
89
90 /**
91 * @param string $type
92 *
93 * @return string
94 */
95 public function get_query_types( $type = 'blocks' ) {
96 $item_types = ( $type === 'blocks' || $type === 'sections' ) ? 'items' : trim( $type );
97 return in_array( $item_types, [ 'items', 'pages', 'packs' ], true ) ? $item_types : false;
98 }
99
100 public function get_items( WP_REST_Request $request ) {
101 $_type = $this->get_param( 'type', 'blocks' );
102 $type = $this->get_query_types( $_type );
103 $plan = $this->get_param( 'plan', 'all' );
104 $plan_type = $this->get_plan( $plan );
105 $platform = $this->get_param( 'platform', 'elementor' );
106 $search = $this->get_param( 'search' );
107 $include_search = $this->get_param( 'include_search' );
108 $page = $this->get_param( 'page', 1, 'intval' );
109 $per_page = $this->get_param( 'per_page', 40, 'intval' );
110 $template_type_id = $this->get_param( 'template_type_id', 0, 'intval' );
111 $categories_raw = $this->get_param( 'categories' );
112 $sort_by = $this->get_param( 'sort_by', 'latest' );
113
114 $dependencies = $request->get_param( 'dependencies' );
115 $tags = $request->get_param( 'tags' );
116
117 $funcArgs = [
118 'page' => $page,
119 'per_page' => $per_page,
120 'platform' => $platform,
121 'sort_by' => $sort_by,
122 ];
123
124 if ( $plan_type > 1 ) {
125 $funcArgs['plan_type'] = $plan_type;
126 }
127
128 if ( ! empty( $categories_raw ) ) {
129 $decoded = json_decode( $categories_raw, true );
130 if ( is_array( $decoded ) && ! empty( $decoded ) ) {
131 $funcArgs['categories'] = wp_slash( json_encode( array_values( array_filter( array_map( 'intval', $decoded ) ) ) ) );
132 }
133 }
134 if ( $template_type_id > 0 ) {
135 $funcArgs['template_type_id'] = $template_type_id;
136 }
137 if ( ! empty( $dependencies['include'] ) || ! empty( $dependencies['exclude'] ) ) {
138 $funcArgs['dependencies'] = wp_slash( json_encode( $dependencies ) );
139 }
140 if ( ! empty( $tags ) ) {
141 $funcArgs['tag_id'] = wp_slash( json_encode( $tags ) );
142 }
143
144 if ( ! empty( $search ) ) {
145 $funcArgs['search'] = $search;
146 }
147
148 if ( ! empty( $include_search ) ) {
149 $funcArgs['include_search'] = $include_search;
150 }
151
152 $query = 'total_page, current_page, data { id, fullsite_import, name, price, rating, downloads, type, template_type{ slug }, slug, favourite_count, thumbnail, thumbnail2, thumbnail3, badges, live_url }';
153 if( $type !== 'packs' ) {
154 $query = 'total_page, current_page, data { id, name, price, rating, downloads, type, template_type{ slug }, slug, favourite_count, dependencies{ id, name, icon, plugin_file, plugin_original_slug, is_pro, link }, thumbnail, pack { id, has_settings }, badges, live_url }';
155 }
156
157 if( $type === false ) {
158 return $this->error( 'invalid_type_call', __( 'Invalid Type Call', 'templately' ) );
159 }
160
161 $transient_key = "_templately_items_" . md5(json_encode([$type, $query, $funcArgs]));
162 $response = false;
163
164 if ( defined( 'TEMPLATELY_ITEMS_TRANSIENT' ) && constant( 'TEMPLATELY_ITEMS_TRANSIENT' ) ) {
165 $response = Database::get_transient( $transient_key );
166 }
167
168 if ( empty( $response ) ) {
169 $response = $this->http()->query( $type, $query, $funcArgs )->post();
170
171 if ( ! empty( $response ) && ! is_wp_error( $response ) && defined( 'TEMPLATELY_ITEMS_TRANSIENT' ) && constant( 'TEMPLATELY_ITEMS_TRANSIENT' ) ) {
172 Database::set_transient( $transient_key, $response );
173 }
174 }
175
176 return $response;
177 }
178
179 public function get_item() {
180 $slug = $this->get_param( 'slug' );
181 $_type = $this->get_param( 'type', 'blocks' );
182 $type = $this->get_query_types( $_type );
183
184 if ( empty( $slug ) ) {
185 return $this->error(
186 'invalid_item_slug',
187 __( 'Items slug cannot be empty.', 'templately' ),
188 'items/:slug',
189 '400'
190 );
191 }
192
193 if( $type === false ) {
194 return $this->error( 'invalid_type_call', __( 'Invalid Type Call', 'templately' ) );
195 }
196
197 $items_params = 'id, name, rating, type, description, slug, price, features, favourite_count, is_favourite, is_reviewed, thumbnail, downloads, categories{ id, name, slug }, dependencies{ id, name, icon, plugin_file, plugin_original_slug, is_pro, link }, tags{ name, id }, categories{ name, id }, screenshots{ url }, banner, published_at, updated_at, is_trending, badges, pack{ id, name, slug, items{ id, price, name, type, slug, thumbnail, badges }, has_settings }, live_url, template_type{ id, name, slug }';
198 $params = 'data { ' . $items_params . ', variations { name, slug, type, platform } }';
199
200 if ( $type == 'packs' ) {
201 $params = 'data { id, fullsite_import, ai_compatible, has_settings, has_attachments, name, rating, type, slug, live_url, price, features, favourite_count, is_favourite, is_reviewed, thumbnail, description, tags{ name, id }, banner, published_at, updated_at, is_trending, badges, template_type{ id, name, slug } downloads, categories{ id, name, slug }, items { ' . $items_params . ' }, variations { name, slug, type, platform } }';
202 }
203
204 $args = [
205 'slug' => $slug,
206 ];
207 if (!empty($this->api_key)) {
208 $args['api_key'] = $this->api_key;
209 }
210
211 $transient_key = "_templately_items_" . md5(json_encode([$type, $params, $args]));
212 $response = false;
213
214 if ( defined( 'TEMPLATELY_ITEMS_TRANSIENT' ) && constant( 'TEMPLATELY_ITEMS_TRANSIENT' ) ) {
215 $response = Database::get_transient( $transient_key );
216 }
217
218 if ( empty( $response ) ) {
219 $response = $this->http()->query( $type, $params, $args )->post();
220
221 if ( ! empty( $response ) && ! is_wp_error( $response ) && defined( 'TEMPLATELY_ITEMS_TRANSIENT' ) && constant( 'TEMPLATELY_ITEMS_TRANSIENT' ) ) {
222 Database::set_transient( $transient_key, $response );
223 }
224 }
225
226 if ( is_wp_error( $response ) ) {
227 return $response;
228 }
229
230 if( empty( $response['data'] ) ) {
231 return $this->error(
232 'invalid_response',
233 __('Item data not found', 'templately'),
234 '/items\/' . $slug,
235 '404'
236 );
237 }
238
239 return current( $response['data'] );
240 }
241
242 public function get_item_by_id() {
243 $id = (int) $this->get_param( 'id' );
244 $_type = $this->get_param( 'type', 'blocks' );
245 $type = $this->get_query_types( $_type );
246
247 if ( empty( $id ) ) {
248 return $this->error(
249 'invalid_item_slug',
250 __( 'Items id cannot be empty.', 'templately' ),
251 'items/:id',
252 '400'
253 );
254 }
255
256 if( $type === false ) {
257 return $this->error( 'invalid_type_call', __( 'Invalid Type Call', 'templately' ) );
258 }
259
260 $items_params = 'id, name, rating, type, description, slug, price, features, favourite_count, is_favourite, is_reviewed, thumbnail, downloads, categories{ id, name, slug }, dependencies{ id, name, icon, plugin_file, plugin_original_slug, is_pro, link }, tags{ name, id }, categories{ name, id }, screenshots{ url }, banner, published_at, updated_at, is_trending, badges, pack{ id, name, slug, items{ id, price, name, type, slug, thumbnail, badges }, has_settings }, live_url, template_type{ id, name, slug }';
261 $params = 'data { ' . $items_params . ', variations { name, slug, type, platform } }';
262
263 if ( $type == 'packs' ) {
264 $params = 'data { id, fullsite_import, ai_compatible, has_settings, has_attachments, name, rating, type, slug, live_url, price, features, favourite_count, is_favourite, is_reviewed, thumbnail, description, tags{ name, id }, banner, published_at, updated_at, is_trending, badges, template_type{ id, name, slug } downloads, categories{ id, name, slug }, items { ' . $items_params . ' }, variations { name, slug, type, platform } }';
265 }
266
267 $args = [
268 'id' => $id,
269 ];
270 if (!empty($this->api_key)) {
271 $args['api_key'] = $this->api_key;
272 }
273
274 $response = $this->http()->query( $type, $params, $args )->post();
275
276 if ( is_wp_error( $response ) ) {
277 return $response;
278 }
279
280 if( empty( $response['data'] ) ) {
281 return $this->error(
282 'invalid_response',
283 __('Item data not found', 'templately'),
284 '/items\/' . $id,
285 '404'
286 );
287 }
288
289 return current( $response['data'] );
290 }
291
292 public function get_search_results( WP_REST_Request $request ) {
293 $keyword = $request->get_param( 'keyword' );
294 $platform = $request->get_param( 'platform' );
295 $query_type = $request->get_param( 'query_type' );
296
297 if ( empty( $keyword ) ) {
298 return $this->error(
299 'invalid_search_keyword',
300 __( 'Search keyword cannot be empty.', 'templately' ),
301 'items/search/:keyword',
302 '400'
303 );
304 }
305
306 $funcArgs = [
307 'search' => $keyword,
308 'platform' => $platform,
309 'query_type' => $query_type
310 ];
311
312 $response = $this->http()->query(
313 'getItemsAndPacks',
314 'total_page, current_page, data { id, name, rating, type, slug, favourite_count, thumbnail }',
315 $funcArgs
316 )->post();
317
318 if( is_wp_error( $response ) ){
319 return $response;
320 }
321
322 $modified_response = [
323 'data' => []
324 ];
325
326 if ( ! empty( $response['data'] ) ) {
327 $modified_response['data'] = array_map( function( $item ){
328 $item['id'] = (int) $item['id'];
329 return $item;
330 }, $response['data'] );
331 unset( $response['data'] );
332 }
333
334 return array_merge( $response, $modified_response );
335 }
336
337 public function set_favourite(){
338 $id = $this->get_param( 'id', 0, 'intval' );
339 $type = $this->get_param( 'itemType', 'block' );
340 $action = $this->get_param( 'action', 'do' );
341
342 $query = 'status, message, data';
343 if( $action === 'undo' ) {
344 $query = '';
345 }
346
347 $funcArgs = [
348 'api_key' => $this->api_key,
349 'type_id' => $id,
350 'type' => $type,
351 ];
352
353 $response = $this->http()->mutation(
354 $action === 'undo' ? 'unFavourite' : 'favourite',
355 $query,
356 $funcArgs
357 )->post();
358
359 if( is_wp_error( $response ) ) {
360 return $response;
361 }
362
363 $_response = [];
364
365 $_data = $response;
366 if( $action == 'do' ) {
367 if( ! empty( $_data['data'] ) && $_data['status'] === 'success' ) {
368 $_data['data'] = $_temp_data = json_decode( $_data['data'] );
369 $_temp_data = [ $_temp_data ];
370
371 $_favourites = $this->utils('options')->get('favourites');
372 $_favourites = $this->utils('helper')->normalizeFavourites( $_temp_data, $_favourites );
373 $this->utils('options')->set('favourites', $_favourites);
374
375 $_response['status'] = 'success';
376 $_response['data'] = $_favourites;
377 }
378 }
379
380 if( $action == 'undo' ) {
381 $_temp_data = [
382 'id' => $id,
383 'type' => $type == 'block' || $type == 'page' ? 'item' : 'pack'
384 ];
385
386 if( $response == 1 || $response === false ) {
387 $_favourites = $this->utils('options')->get('favourites');
388 $_favourites = $this->utils('helper')->normalizeFavourites( $_temp_data, $_favourites, true );
389 $this->utils('options')->set('favourites', $_favourites);
390
391 $_response['status'] = 'success';
392 $_response['data'] = $_favourites;
393 } else {
394 $_response['status'] = 'error';
395 $_response['message'] = __( 'Unfavourite Action Failed: Something went wrong.', 'templately' );
396 }
397 }
398
399 return $_response;
400 }
401
402 public function set_rating(){
403 $type_id = $this->get_param( 'type_id', 0, 'intval' );
404 $itemType = $this->get_param( 'itemType', 'pack' );
405 $rating = $this->get_param( 'rating', 5, 'intval' );
406
407 $query = 'status, message, data';
408
409 $funcArgs = [
410 'api_key' => $this->api_key,
411 'type_id' => $type_id,
412 'type' => $itemType,
413 'rating' => $rating,
414 ];
415
416 $response = $this->http()->mutation(
417 'review',
418 $query,
419 $funcArgs
420 )->post();
421
422 if( is_wp_error( $response ) ) {
423 return $response;
424 }
425
426 if( empty( $response['data'] ) ) {
427 return $this->error(
428 'invalid_response',
429 __('Something went wrong.', 'templately'),
430 '/items\/rating',
431 '404'
432 );
433 }
434
435 if( ! empty( $response['data'] ) && $response['status'] === 'success' ) {
436 $response['data'] = json_decode( $response['data'], true );
437
438 $_ratings = $this->utils('options')->get('reviews', []);
439 // $_reviews = $this->utils( 'helper' )->normalizeReviews( $response['user']['reviews'] );
440 $_ratings[ $itemType ][ $type_id ] = $rating;
441 $this->utils('options')->set('reviews', $_ratings);
442
443 $_ratings[ $itemType ][ "avg_$type_id" ] = (float) $response['data']['avg_rating'];
444 $response['data']['reviews'] = $_ratings;
445 }
446
447 return $response;
448 }
449
450 public function get_counts(){
451 $defaults = Database::get_transient( 'counts' );
452 if( $defaults ) {
453 return $defaults;
454 }
455
456 $defaults = [
457 'elementor' => [
458 'items' => [
459 'total' => '1469',
460 'starter' => '964',
461 'pro' => '415',
462 ],
463 'blocks' => [
464 'total' => '517',
465 'starter' => '358',
466 'pro' => '159',
467 ],
468 'pages' => [
469 'total' => '803',
470 'starter' => '506',
471 'pro' => '297',
472 ],
473 'packs' => [
474 'total' => '149',
475 'starter' => '100',
476 'pro' => '49',
477 ]
478 ],
479 'gutenberg' => [
480 'blocks' => [
481 'total' => '3',
482 'starter' => '3',
483 'pro' => '0',
484 ],
485 'pages' => [
486 'total' => '3',
487 'starter' => '3',
488 'pro' => '0',
489 ],
490 'packs' => [
491 'total' => '3',
492 'starter' => '3',
493 'pro' => '0',
494 ],
495 ]
496 ];
497
498 $response = $this->http()->query(
499 'getCounts',
500 'key, value'
501 )->post();
502
503 if ( is_wp_error( $response ) ) {
504 return $defaults;
505 }
506
507 $new_array = [
508 'items' => [],
509 'blocks' => [],
510 'pages' => [],
511 'packs' => [],
512 ];
513 $_new_data = [];
514
515 array_walk( $response, function( $item ) use ( &$new_array, &$_new_data ) {
516 if( in_array( $item['key'] , ['elementor', 'gutenberg'], true ) ) {
517 $values = json_decode($item['value'], true);
518
519 array_walk( $values, function( $_item ) use ( &$new_array ) {
520 $new_key = explode('-', $_item['key']);
521 if( count( $new_key ) === 2 ) {
522 if( isset( $new_array[ $new_key[1] ] )) {
523 $new_array[ $new_key[1] ] = array_merge( $new_array[ $new_key[1] ], [ $new_key[0] => $_item['value'] ] );
524 $temp_array = $new_array[ $new_key[1] ];
525 unset( $temp_array['total'] );
526 $new_array[ $new_key[1] ]['total'] = array_sum( $temp_array );
527 }
528 }
529 });
530
531 $_new_data[ $item['key'] ] = $new_array;
532 }
533 });
534
535
536 Database::set_transient( 'counts', $_new_data );
537
538 return $_new_data;
539 }
540
541 /**
542 * Get Featured Item List
543 * @param WP_REST_Request $request
544 * @return mixed
545 */
546 public function featured_items(WP_REST_Request $request){
547 $platform = $request->get_param( 'platform' );
548 $defaults = Database::get_transient( "featuredItems_{$platform}" );
549 if( $defaults ) {
550 return $defaults;
551 }
552
553 $funcArgs = [
554 // 'page' => 1,
555 // 'per_page' => 10,
556 'platform' => $platform,
557 ];
558 $response = $this->http()->query(
559 'featuredItems',
560 'data{ id, name, slug, price, type, thumbnail, badges }',
561 $funcArgs
562 )->post();
563
564 if( ! is_wp_error( $response ) ) {
565 // Write the SAME platform-suffixed key the read above uses — otherwise the
566 // cache never hits and every load makes a fresh remote GraphQL call (and the
567 // unsuffixed key would collide elementor/gutenberg). Keyed per platform.
568 Database::set_transient( "featuredItems_{$platform}", $response );
569 }
570
571 return $response;
572 }
573
574 /**
575 * Get Trending Item List
576 * @param WP_REST_Request $request
577 * @return mixed
578 */
579 public function trending_items(WP_REST_Request $request){
580 $platform = $request->get_param( 'platform' );
581 $defaults = Database::get_transient( "trendingItems_{$platform}" );
582 if( $defaults ) {
583 return $defaults;
584 }
585
586 $funcArgs = [
587 // 'page' => 1,
588 // 'per_page' => 10,
589 'platform' => $platform,
590 ];
591 $response = $this->http()->query(
592 'trendingItems',
593 'data{ id, name, slug, price, type, thumbnail, badges }',
594 $funcArgs
595 )->post();
596
597 if( ! is_wp_error( $response ) ) {
598 // Platform-suffixed to match the read key (see featured_items) — fixes the
599 // permanently-cold cache + the cross-platform collision.
600 Database::set_transient( "trendingItems_{$platform}", $response );
601 }
602
603 return $response;
604 }
605
606 /**
607 * Get Related Item List
608 * @param WP_REST_Request $request
609 * @return mixed
610 */
611 public function related_items(WP_REST_Request $request){
612 $item_id = (int) $request->get_param( 'item_id' );
613 $type = $request->get_param( '_type' );
614
615 if ( empty( $item_id ) ) {
616 return $this->error(
617 'invalid_item_id',
618 __( 'Item ID cannot be empty.', 'templately' ),
619 'related-items',
620 '400'
621 );
622 }
623
624 if ( empty( $type ) ) {
625 return $this->error(
626 'invalid_type',
627 __( 'Type cannot be empty.', 'templately' ),
628 'related-items',
629 '400'
630 );
631 }
632
633 $funcArgs = [
634 'id' => $item_id,
635 'type' => $type,
636 'limit' => 4, // Limit to 3 related items
637 ];
638
639 $response = $this->http()->query(
640 'relatedItems',
641 'id, name, slug, price, type, thumbnail, badges',
642 $funcArgs
643 )->post();
644
645 if( is_wp_error( $response ) ) {
646 return $response;
647 }
648
649 return [
650 'status' => 'success',
651 'data' => $response,
652 ];
653 }
654 }