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 / gutenberg-integration / Platform / Gutenberg.php

Gutenberg.php in Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! 3.8.0, at modules/gutenberg-integration/Platform/Gutenberg.php

341 lines 11.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Templately\Modules\GutenbergIntegration\Platform;
3
4 use Templately\Modules\FullSiteImport\Utils\Utils;
5 use Templately\Modules\GutenbergIntegration\Utils\GutenbergHelper;
6 use Templately\Modules\GutenbergIntegration\Utils\GutenbergSettingsMerger;
7 use Templately\Core\Platform;
8 use Templately\Core\Platform_Registry;
9 use Templately\Utils\Helper;
10 use Templately\Utils\Options;
11
12 use WP_Error;
13 use function get_permalink;
14 use function get_edit_post_link;
15 use function wp_insert_post;
16 use function wp_slash;
17 use Templately\Utils\Response\AjaxResponder;
18 use Templately\Utils\Response\ErrorCode;
19 use function wp_unslash;
20 use function wp_update_post;
21 use function json_decode;
22
23 class Gutenberg extends Platform {
24 /**
25 * Platform ID
26 * @var string
27 */
28 private $id = 'gutenberg';
29
30 /**
31 * Is gutenberg is active or not
32 * @var boolean
33 */
34 public $is_gutenberg_active = false;
35
36
37
38 /**
39 * Initializing the platform and add it to module.
40 */
41 public function __construct(){
42 Platform_Registry::get_instance()->add( (object) [
43 'id' => $this->id,
44 'object' => $this
45 ]);
46
47
48 $this->hooks();
49 }
50
51 /**
52 * Initializing Hooks
53 * @return void
54 */
55 public function hooks(){
56 add_action( 'enqueue_block_editor_assets', [ $this, 'scripts' ] );
57 add_action( 'admin_footer', [ $this, 'print_admin_js_template' ] );
58 // DEPRECATED SHIM (FR-003 / FR-020): the toolbar-visibility toggle migrated to the
59 // REST route PUT/POST /templately/v1/gutenberg/visibility (REST/Visibility.php). This
60 // ajax action is kept registered because the caller is browser-cached JS — an open
61 // editor tab (or a soft reload served stale gutenberg.js) would still POST here after
62 // the REST route ships. Same option key + normalization as the REST handler.
63 add_action( 'wp_ajax_update_gutenberg_hide_buttons', [ $this, 'update_gutenberg_hide_buttons' ] ); // Register AJAX action
64
65 // FSI platform seams (spec 025/033). full-site-import resolves its Gutenberg
66 // import helper + settings merger through these filters instead of importing
67 // this module directly — this breaks the full-site-import <-> gutenberg-integration
68 // cycle. Both fire at AJAX/runner time, long after plugins_loaded boots all modules.
69 add_filter( 'templately_fsi_json_helper', function ( $helper, $platform ) {
70 return 'gutenberg' === $platform ? new GutenbergHelper() : $helper;
71 }, 10, 2 );
72 add_filter( 'templately_fsi_merge_settings', function ( $content, $settings ) {
73 return GutenbergSettingsMerger::merge( $content, $settings );
74 }, 10, 2 );
75 }
76
77 /**
78 * Assets Enqueueing
79 * @return void
80 */
81 public function scripts(){
82 $this->is_gutenberg_active = true;
83 templately()->assets->enqueue( 'templately-gutenberg', 'css/gutenberg.css' );
84 templately()->assets->enqueue( 'templately-tailwind', 'css/tailwind.css', ['templately-gutenberg'] );
85 templately()->assets->enqueue( 'templately-gutenberg', 'js/gutenberg.js' );
86 // The AI editor chat bundle (041) enqueues itself off this same
87 // `enqueue_block_editor_assets` action from modules/ai-editor/module.php
88 // — this module does not own that asset.
89 templately()->admin->scripts( 'gutenberg' );
90 }
91
92 /**
93 * Templately Button and Wrapper for Gutenberg
94 *
95 * @since 2.0.0
96 *
97 * @return void
98 */
99 public function print_admin_js_template() {
100 if ( ! $this->is_gutenberg_active ) {
101 return;
102 }
103 $post_type = apply_filters( 'templately_cloud_push_post_type', get_post_type());
104
105 ?>
106 <div id="templately-gutenberg"></div>
107 <script id="templately-gutenberg-button-switch-mode" type="text/html">
108 <div id="templately-gutenberg-buttons">
109 <button id="templately-gutenberg-button" type="button" class="button button-primary button-large gutenberg-add-templately-button">
110 <i class="templately-icon" aria-hidden="true"></i>
111 <?php echo esc_html__( 'Templately', 'templately' ); ?>
112 </button>
113 <button id="templately-cloud-push" type="button" class="button button-primary button-large">
114 <i class="templately-cloud-icon" aria-hidden="true"></i>
115 <?php echo esc_html( sprintf( __( 'Save %s in Templately', 'templately' ), $post_type ) ); ?>
116 </button>
117 </div>
118 </script>
119 <?php
120 }
121
122 /**
123 * Determine Active UI Theme
124 * @return string
125 */
126 public function ui_theme(){
127 return 'light';
128 }
129
130 /**
131 * Creating a gutenberg page
132 *
133 * @param integer $id
134 * @param string $title
135 * @param Import $importer
136 *
137 * @since 2.0.0
138 *
139 * @return array|WP_Error array on success, WP_Error on failure.
140 */
141
142
143
144
145
146
147
148 public function create_page( $id, $title, $importer = null, $settings = [] ){
149 $post_data = $inserted_ID = $importer->get_content( $id, 'gutenberg' );
150
151 if( is_wp_error( $inserted_ID ) ) {
152 return $inserted_ID;
153 }
154
155 if ( ! empty( $settings ) && ! empty( $inserted_ID['content'] ) && is_string( $inserted_ID['content'] ) ) {
156 $inserted_ID['content'] = GutenbergSettingsMerger::merge( $inserted_ID['content'], $settings );
157 $post_data['content'] = $inserted_ID['content'];
158 }
159
160 if ( ! empty( $inserted_ID['content'] ) ) {
161 $inserted_ID = wp_insert_post( array (
162 'post_status' => 'draft',
163 'post_type' => 'page',
164 'post_title' => $title,
165 'post_content' => wp_slash($inserted_ID['content']),
166 ) );
167 }
168
169 if ( is_wp_error( $inserted_ID ) ) {
170 return Helper::error(
171 'import_failed',
172 $inserted_ID->get_error_message(),
173 'import/page',
174 $inserted_ID->get_error_code()
175 );
176 }
177
178 if($inserted_ID){
179 $post_data['content'] = Utils::import_and_replace_attachments($post_data['content'], $inserted_ID);
180
181 // Update the post content with the processed images
182 $updated_post = array(
183 'ID' => $inserted_ID,
184 'post_content' => wp_slash($post_data['content']),
185 );
186 wp_update_post($updated_post);
187
188 /**
189 * Block markup has just been written to a post that nobody has opened in an editor.
190 *
191 * A template carries the markup its AUTHORING site's block plugins produced, so on a
192 * site running a different version Gutenberg's `save()` comparison fails and the page
193 * opens with "Attempt recovery" — exactly the full-site-import case, minus the
194 * session. modules/block-recovery listens here.
195 *
196 * @param int[] $post_ids Posts that now hold imported block markup.
197 * @param string $context Which import produced them.
198 */
199 do_action( 'templately_blocks_imported', [ (int) $inserted_ID ], 'single' );
200 }
201
202 return [
203 'post_id' => $inserted_ID,
204 'edit_link' => get_edit_post_link( $inserted_ID, 'internal' ),
205 'visit' => get_permalink( $inserted_ID )
206 ];
207 }
208
209 /**
210 * Inserts a template into the Gutenberg editor.
211 *
212 * @param mixed $data
213 * @param int $postId
214 * @param array $settings
215 * @return array
216 */
217 public function insert($data, $postId = 0, $settings = []) {
218 if ( ! empty( $settings ) && is_array( $settings ) && ! empty( $data['content'] ) && is_string( $data['content'] ) ) {
219 $data['content'] = GutenbergSettingsMerger::merge( $data['content'], $settings );
220 }
221
222 $data['content'] = Utils::import_and_replace_attachments(
223 $data['content'],
224 $postId,
225 [
226 'attachment_timeout' => 8,
227 'attachment_retries' => 0,
228 'attachment_deadline' => microtime( true ) + 20,
229 ]
230 );
231
232 return $data;
233 }
234
235 /**
236 * Import template to Templately Library
237 *
238 * @param integer $id
239 * @param Import $importer
240 * @param array $settings
241 *
242 * @return array|WP_Error array on success, WP_Error on failure.
243 */
244 public function import_in_library( $id, $importer = null, $settings = [], $template_type = '', $item_type = '' ) {
245 $template_data = $importer->get_content( $id, 'gutenberg', 'remote' );
246
247 if ( is_wp_error( $template_data ) ) {
248 return $template_data;
249 }
250
251 if ( ! empty( $settings ) && ! empty( $template_data['content'] ) && is_string( $template_data['content'] ) ) {
252 $template_data['content'] = GutenbergSettingsMerger::merge( $template_data['content'], $settings );
253 }
254
255 // Handle attachment replacements before saving
256 $template_data['content'] = Utils::import_and_replace_attachments( $template_data['content'], 0 );
257
258 // Frontend-provided template_type (from item details API) takes precedence over
259 // whatever the fetched content JSON reports.
260 if ( ! empty( $template_type ) ) {
261 $template_data['template_type'] = $template_type;
262 }
263
264 // Resolve the correct Builder Type using template_type before passing to the importer.
265 try {
266 $type = static::resolve_library_type( $template_data );
267 } catch ( \InvalidArgumentException $e ) {
268 return Helper::error( 'unsupported_template_type', $e->getMessage(), 'import', 400 );
269 }
270
271 $factory = new \Templately\Modules\ThemeBuilder\Factory\TemplateFactory( 'gutenberg' );
272
273 $template = $factory->create( $type, [
274 'post_title' => $template_data['title'],
275 'post_status' => 'publish',
276 'post_type' => 'templately_library',
277 ] );
278
279 if ( is_wp_error( $template ) ) {
280 return $template;
281 }
282
283 $template->import( array_merge( $template_data, [
284 'import_settings' => [
285 'title' => $template_data['title'],
286 'type' => $type,
287 ]
288 ] ) );
289
290 $post_id = $template->get_main_id();
291
292 // Companion content for archive/fluent types — same side effects (Shop page,
293 // page_for_posts, LearnDash courses slug, fluent single-page template) the FSI
294 // Templates runner performs when importing these template types.
295 Utils::create_companion_content( $type, $template_data['title'] ?? '', 'gutenberg' );
296
297 // Mark user as having imported a template
298 Options::get_instance()->set( 'has_imported_template', true );
299
300 return [
301 'post_id' => $post_id,
302 'edit_link' => get_edit_post_link( $post_id, 'internal' ),
303 'visit' => get_permalink( $post_id ),
304 ];
305 }
306
307 /**
308 * DEPRECATED SHIM — AJAX handler to update the option `templately-gutenberg-hide-buttons`.
309 *
310 * Superseded by the REST route PUT/POST /templately/v1/gutenberg/visibility
311 * ({@see \Templately\Modules\GutenbergIntegration\REST\Visibility}). Retained only for
312 * browser-cached editor bundles still calling the old admin-ajax action; new code and the
313 * current frontend use the REST route. Behavior kept byte-identical (same site-wide option
314 * key, same nonce/capability gate, same 'yes'/'no' normalization).
315 */
316 public function update_gutenberg_hide_buttons() {
317 // Check nonce for security
318 check_ajax_referer( 'templately_nonce', 'nonce' );
319
320 // Nonce alone authenticates the request but not authorization — gate the
321 // option write on a real capability so a low-privilege logged-in user
322 // (e.g. subscriber) can't toggle this global editor option.
323 if ( ! current_user_can( 'edit_posts' ) ) {
324 AjaxResponder::error( ErrorCode::FORBIDDEN );
325 return;
326 }
327
328 // Get the new value from the AJAX request
329 $hide_buttons = isset($_GET['hide_buttons']) ? sanitize_text_field($_GET['hide_buttons']) : '';
330
331 // Update the option
332 update_option('templately-gutenberg-hide-buttons', $hide_buttons);
333
334 $hide_buttons = get_option('templately-gutenberg-hide-buttons', 'no');
335 $hide_buttons = $hide_buttons === 'yes' ? 'yes' : 'no';
336 // Send a response back to the JavaScript
337 AjaxResponder::success( [
338 'hide_buttons' => $hide_buttons,
339 ] );
340 }
341 }