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 / block-patterns / REST / Content.php

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

189 lines 6.3 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\BlockPatterns\REST;
4
5 use Templately\API\API;
6 use Templately\Modules\BlockPatterns\MediaLocalizer;
7 use Templately\Modules\BlockPatterns\PatternSync;
8 use Templately\Modules\FullSiteImport\Utils\PackInfoFetcher;
9 use WP_REST_Request;
10
11 /**
12 * On-demand pattern content for the lazy-pattern block.
13 *
14 * Only reachable for ids that are in the current plan-keyed catalog cache, so
15 * this cannot be used to pull arbitrary items: the plan gate that decides what
16 * is registered also decides what is fetchable.
17 */
18 class Content extends API {
19 private $endpoint = 'block-patterns/content';
20
21 public function permission_check( WP_REST_Request $request ) {
22 $this->request = $request;
23
24 return is_user_logged_in() && current_user_can( 'edit_posts' );
25 }
26
27 public function register_routes() {
28 $this->get( $this->endpoint, [ $this, 'get_content' ] );
29 $this->post( 'block-patterns/localize', [ $this, 'localize' ] );
30 }
31
32 /**
33 * Import a pattern's media and return a remote-URL => local-URL map.
34 *
35 * Split from get_content on purpose: the editor shows the design first with
36 * its demo URLs and swaps them once this returns, so insertion never waits
37 * on image downloads.
38 */
39 public function localize() {
40 $id = $this->get_param( 'id', 0, 'absint' );
41 $post_id = $this->get_param( 'post_id', 0, 'absint' );
42 $sync = PatternSync::get_instance();
43
44 if ( ! $id || null === $sync->plan_key() ) {
45 return $this->success( [ 'map' => (object) [] ] );
46 }
47
48 $list = $sync->get_list();
49 $known = $list ? in_array( $id, array_map( 'absint', wp_list_pluck( $list['items'], 'id' ) ), true ) : false;
50 if ( ! $known && ! $sync->was_searched( $id ) ) {
51 return $this->success( [ 'map' => (object) [] ] );
52 }
53
54 $content = $sync->get_content( $id );
55 if ( ! is_string( $content ) || '' === $content ) {
56 return $this->success( [ 'map' => (object) [] ] );
57 }
58
59 $map = MediaLocalizer::get_instance()->build_url_map( $content, $post_id );
60
61 return $this->success( [ 'map' => empty( $map ) ? (object) [] : $map ] );
62 }
63
64 public function get_content() {
65 $id = $this->get_param( 'id', 0, 'absint' );
66 $sync = PatternSync::get_instance();
67
68 if ( ! $id ) {
69 return $this->error( 'invalid_id', __( 'A pattern id is required.', 'templately' ), $this->endpoint, 400 );
70 }
71
72 if ( null === $sync->plan_key() ) {
73 return $this->error(
74 'not_connected',
75 __( 'Connect your Templately account to use this pattern.', 'templately' ),
76 $this->endpoint,
77 403
78 );
79 }
80
81 // The catalog cache is the allow-list: an id the current plan never synced
82 // is not fetchable, so plan gating holds on this path too.
83 $list = $sync->get_list();
84 $record = $this->find_item( $list, $id );
85 $known = null !== $record;
86
87 // A live-searched id is not in the cached catalog by definition. It is
88 // still gated: `PatternSync::search()` applied the same plan filter before
89 // recording it against this user, so the allow-list is an extension of the
90 // same boundary, not a hole in it.
91 if ( ! $known ) {
92 $known = $sync->was_searched( $id );
93 }
94
95 if ( ! $known ) {
96 return $this->error(
97 'unknown_pattern',
98 __( 'That pattern is not available on your plan.', 'templately' ),
99 $this->endpoint,
100 404
101 );
102 }
103
104 $content = $sync->get_content( $id );
105
106 if ( ! is_string( $content ) || '' === $content ) {
107 return $this->error(
108 'content_unavailable',
109 __( 'This pattern could not be loaded. Please try again.', 'templately' ),
110 $this->endpoint,
111 502
112 );
113 }
114
115 // Apply the pack's global colours and typography, exactly as single-import
116 // does. Without this the inserter produced a visibly different result from
117 // importing the SAME design through the library: identical markup, none of
118 // the pack's palette. A settings failure is non-fatal there and is non-fatal
119 // here — the design is worth more than its palette.
120 $settings = $this->pack_settings_for( $record );
121 if ( ! empty( $settings ) ) {
122 $content = apply_filters( 'templately_fsi_merge_settings', $content, $settings );
123 }
124
125 // What the CATALOG says this design needs. Shipped with the markup rather
126 // than inferred from it in the browser: a block namespace does not identify
127 // a plugin (a free plugin can register its paid tier's block names as upsell
128 // stubs, so the registry reports nothing missing), and this is the same
129 // field the full-site and single-import dependency steps already trust.
130 // Absent for a live-searched id or a cache written before this field existed
131 // — the client falls back to scanning the markup in that case.
132 return $this->success( [
133 'content' => $content,
134 'requires' => is_array( $record['requires'] ?? null ) ? array_values( $record['requires'] ) : null,
135 ] );
136 }
137
138 /**
139 * The owning pack's global settings for a cached catalog record.
140 *
141 * Returns an empty array whenever anything is missing rather than failing the
142 * request: a standalone design has no pack, a live-searched id has no cached
143 * record at all, and a cache written before `pack` was queried carries neither
144 * field. All three are the pre-existing behaviour — unstyled but working.
145 *
146 * `has_settings` is checked before the fetch, not after: it is the catalog's
147 * own answer to "is there anything to merge", so trusting it saves a round trip
148 * to the cloud for every design that has no globals.
149 *
150 * @param array<string,mixed>|null $record Cached catalog record, or null.
151 * @return array<string,mixed>
152 */
153 private function pack_settings_for( $record ): array {
154 if ( ! is_array( $record ) || empty( $record['has_settings'] ) ) {
155 return [];
156 }
157
158 $pack_id = absint( $record['pack_id'] ?? 0 );
159 if ( ! $pack_id ) {
160 return [];
161 }
162
163 $data = ( new PackInfoFetcher() )->fetch( $pack_id, false );
164 if ( is_wp_error( $data ) || empty( $data['data']['settings'] ) ) {
165 return [];
166 }
167
168 $settings = json_decode( $data['data']['settings'], true );
169
170 return is_array( $settings ) ? $settings : [];
171 }
172
173 /**
174 * The cached catalog record for an id, or null when it is not in this plan's list.
175 *
176 * @param mixed $list Cached list payload.
177 * @return array<string,mixed>|null
178 */
179 private function find_item( $list, int $id ) {
180 foreach ( (array) ( $list['items'] ?? [] ) as $item ) {
181 if ( absint( $item['id'] ?? 0 ) === $id ) {
182 return (array) $item;
183 }
184 }
185
186 return null;
187 }
188 }
189