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 / theme-builder / ThemeBuilder.php

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

347 lines 11.8 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\ThemeBuilder;
4
5 use Templately\Admin\Roles;
6
7 use Elementor\Core\Base\Document;
8 use Templately\Modules\ThemeBuilder\Managers\LocationManager;
9 use Templately\Modules\ThemeBuilder\Managers\TemplateManager;
10 use Templately\Modules\ThemeBuilder\Managers\ThemeCompatibility;
11 use Templately\Modules\ThemeBuilder\Types\ThemeTemplate;
12 use Templately\Utils\Response\AjaxResponder;
13 use Templately\Utils\Response\ErrorCode;
14 use Templately\Utils\Base;
15 use Templately\Utils\Views;
16
17 class ThemeBuilder extends Base {
18 /**
19 * Names of the theme-builder widgets shown in the editor panel.
20 *
21 * The widgets declare show_in_panel() => false, so they are hidden on every
22 * document by default; elementor_document_config() re-enables them (via the
23 * per-document panel.widgets_settings override) only for the
24 * templately_library CPT. Keep in sync with register_elements().
25 */
26 const PANEL_WIDGETS = [
27 'tl-post-title',
28 'tl-post-content',
29 'tl-post-fatured-image',
30 'tl-site-logo',
31 'tl-post-comments',
32 ];
33
34 /**
35 * Post types that are `public` but must never be offered as display-condition
36 * targets.
37 *
38 * `templately_library` is registered `public => true, show_in_nav_menus => false`
39 * (see Source::register_post_type()), so it only stayed out of the condition
40 * dropdowns by ACCIDENT of the old `show_in_nav_menus` gate. Widening that gate
41 * without this list would let a template target the template library itself.
42 * `elementor_library`, `e-floating-buttons` and `e-landing-page` are Elementor's
43 * own builder-document CPTs (all `public => true`), and `attachment` has no
44 * meaningful theme-builder use. None of these are pages a visitor browses, so
45 * none belongs in a display condition.
46 */
47 const EXCLUDED_POST_TYPES = [
48 Source::CPT,
49 'elementor_library',
50 'e-floating-buttons',
51 'e-landing-page',
52 'attachment',
53 ];
54
55 /**
56 * @var Source
57 */
58 public static $source;
59
60 /**
61 * @var \Templately\Modules\DisplayConditions\Managers\ConditionManager|null
62 */
63 public static $conditions_manager;
64
65 /**
66 * @var TemplateManager
67 */
68 public static $templates_manager;
69
70 /**
71 * @var LocationManager
72 */
73 public static $location_manager;
74
75 /**
76 * @var ThemeCompatibility
77 */
78 public static $theme_compatibility;
79
80 /**
81 * @var PageTemplates
82 */
83 public static $page_template_module;
84
85 public static $views;
86
87 public function __construct() {
88 self::$views = Views::get_instance( TEMPLATELY_VIEWS_ABSPATH );
89
90 add_action( 'pre_get_posts', [ $this, 'pre_get_posts' ], 1 );
91 add_action( 'wp', function () {
92 new TemplateLoader( $this, self::$views );
93 } );
94 // Unconditional (admin + front end): the editor's admin page bootstrap
95 // (post.php?action=elementor) never fires `wp`, so this can't be gated
96 // behind the TemplateLoader instantiation above. See TemplateLoader::
97 // register_preview_product_hooks() for why two render pathways are covered.
98 TemplateLoader::register_preview_product_hooks();
99
100
101 add_action( 'init', [ $this, 'source_register' ] );
102 add_action( 'wp_ajax_templately_create_template', [ $this, 'create_template' ] );
103
104 add_filter( 'elementor/document/config', [$this, 'elementor_document_config'], 10, 2 );
105 add_filter( 'elementor/documents/get/post_id', [$this, 'elementor_documents_get_post_id'] );
106 add_action( 'elementor/widgets/register', [ $this, 'register_elements' ] );
107
108 add_filter( 'the_content', [$this, 'filter_content'], 10 ,1);
109
110 self::$theme_compatibility = new ThemeCompatibility();
111 self::$location_manager = new LocationManager( $this );
112 self::$page_template_module = new PageTemplates();
113
114 }
115
116
117
118 public function pre_get_posts( $query ) {
119 if ( ! is_admin() || ! $query->is_main_query() || empty( $query->query['post_type'] ) || $query->query['post_type'] !== 'templately_library' ) {
120 return;
121 }
122
123 $template_types = isset( $_GET['type'] ) ? sanitize_text_field( wp_unslash( $_GET['type'] ) ) : '';
124
125 if ( ! empty( $template_types ) ) {
126 $query->set( 'meta_query', [
127 [
128 'key' => Source::TYPE_META_KEY,
129 'value' => $template_types,
130 ]
131 ] );
132 }
133 }
134
135 public function source_register() {
136 self::$templates_manager = new TemplateManager( $this );
137 // The ConditionManager is owned by the display-conditions module (spec 034,
138 // extracted FROM theme-builder). It is supplied here through the
139 // `templately_condition_manager` filter that display-conditions registers at
140 // boot — inverting the old direct dependency so theme-builder no longer imports
141 // the display-conditions namespace. Fires on `init`, long after
142 // `plugins_loaded` has booted (and let the filter register) every module.
143 self::$conditions_manager = apply_filters( 'templately_condition_manager', null, $this );
144 self::$source = new Source( $this );
145 register_rest_field('templately_library','templately_type', [
146 'get_callback' => [ $this, 'get_rest_template_type' ],
147 ]);
148 }
149
150 public function get_rest_template_type() {
151 return get_post_meta( get_the_ID(), '_templately_template_type', true );
152 }
153
154 public function create_template() {
155 check_admin_referer( 'templately_create_template' );
156
157 // A theme-builder template controls what every visitor sees in the header,
158 // footer, archive and single slots. `delete_posts` is held by the Author
159 // role, which is scoped to its own post content — gate on the plugin's own
160 // administrator-only capability instead.
161 if ( ! Roles::can_manage_builder() ) {
162 AjaxResponder::error(ErrorCode::FORBIDDEN);
163 return;
164 }
165
166 // Both fields are required; reading them unguarded raises an undefined-index
167 // warning on any malformed POST instead of failing cleanly.
168 if ( empty( $_POST['template_type'] ) || ! isset( $_POST['title'] ) ) {
169 wp_die( esc_html__( 'Missing required template fields.', 'templately' ) );
170 }
171
172 $_type = sanitize_text_field( wp_unslash( $_POST['template_type'] ) );
173
174 $_meta = [];
175
176 if ( isset( $_POST['meta'] ) && is_array( $_POST['meta'] ) ) {
177 $_meta = array_map( 'sanitize_text_field', wp_unslash( $_POST['meta'] ) );
178 }
179
180 $_post_data = [
181 'post_type' => 'templately_library',
182 'post_title' => sanitize_text_field( wp_unslash( $_POST['title'] ) )
183 ];
184
185 $template = self::$templates_manager->create( $_type, $_post_data, $_meta );
186
187 if ( is_wp_error( $template ) ) {
188 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- wp_die() renders a WP_Error itself (title/status/message); stringifying loses that.
189 wp_die( $template );
190 }
191
192 // wp_safe_redirect keeps the destination on an allowed host; the URL comes
193 // from the templates manager, but there is no reason to accept an offsite one.
194 wp_safe_redirect( $template->get_edit_url() );
195 die;
196 }
197
198 public function get_public_post_types(): array {
199 // `public` (not `show_in_nav_menus`) is the right gate: whether a post type
200 // belongs in a nav menu says nothing about whether it has templatable views.
201 // A CPT registered `show_in_nav_menus => false` — a very common choice —
202 // still has singles and archives the theme builder must be able to target,
203 // and the old filter made every one of them invisible here.
204 $post_type_args = [
205 'public' => true,
206 ];
207
208 $_post_types = get_post_types( $post_type_args, 'objects' );
209
210 /**
211 * Post types never offered as theme-builder display-condition targets.
212 *
213 * Other builder plugins register `public` CPTs that hold DOCUMENTS rather
214 * than visitor-facing pages; without this they would appear as meaningless
215 * condition entries the moment the gate above widened.
216 *
217 * @param array $excluded Post type slugs to exclude.
218 */
219 $excluded = apply_filters( 'templately_builder_excluded_post_types', self::EXCLUDED_POST_TYPES );
220
221 $post_types = [];
222
223 foreach ( $_post_types as $post_type => $object ) {
224 if ( in_array( $post_type, (array) $excluded, true ) ) {
225 continue;
226 }
227
228 $post_types[ $post_type ] = $object->label;
229 }
230
231 return $post_types;
232 }
233
234 public function get_template( $template_id ) {
235 $template = self::$templates_manager->get( $template_id );
236
237 if ( ! empty( $template ) && ! $template instanceof ThemeTemplate ) {
238 $template = null;
239 }
240
241 return $template;
242 }
243
244 /**
245 * Save additional data for a specific post.
246 *
247 * This function filters the data by adding additional configuration.
248 * External developers can use this function to add custom configuration for different posts.
249 * It checks if the post type is not 'templately_library', then it adds a new configuration
250 * 'theme-post-content' to the data.
251 *
252 * @param array $data The original data that needs to be saved.
253 * @param int $post_id The ID of the post for which the data is being saved.
254 *
255 * @return array The modified data with the additional configuration.
256 */
257 public function elementor_document_config( $data, $post_id ) {
258 if ( Source::CPT === get_post_type( $post_id ) ) {
259 $data['panel'] = [
260 'widgets_settings' => array_merge(
261 [
262 'theme-post-content' => [
263 'show_in_panel' => true,
264 ],
265 ],
266 // Templately widgets are panel-hidden by default; surface
267 // them only when editing a templately_library template.
268 array_fill_keys( self::PANEL_WIDGETS, [ 'show_in_panel' => true ] )
269 ),
270 'elements_categories' => [
271 'theme-elements-archive' => [
272 'title' => esc_html__( 'Archive', 'elementor-pro' ),
273 ],
274 // The Templately widget group. This merge (array_replace_recursive
275 // in Document::get_config) can only append new category keys, so it
276 // lands last here — the editor-JS WidgetPanelModule reorders it to
277 // the top of the panel on document:loaded.
278 'templately' => [
279 'title' => esc_html__( 'Templately', 'templately' ),
280 'icon' => 'eicon-folder',
281 'hideIfEmpty' => false,
282 ],
283 ],
284 ];
285
286 $conditions = self::$conditions_manager->get_conditions_for_display( $post_id );
287
288 $data['templately_builder']['conditions'] = $conditions;
289 $data['templately_builder']['post_type'] = get_post_type( $post_id );
290 }
291
292 return $data;
293 }
294
295 public function elementor_documents_get_post_id( $post_id ) {
296 if ( Source::CPT === get_post_type( $post_id ) ) {
297 add_filter( 'get_post_metadata', [ $this, 'modify_template_type' ], 10, 4 );
298 }
299
300 return $post_id;
301 }
302
303 public function modify_template_type( $value, $object_id, $meta_key, $single ) {
304 $_value = $value;
305 if ( empty($value) && Document::TYPE_META_KEY === $meta_key && Source::CPT === get_post_type( $object_id ) ) {
306 remove_filter( 'get_post_metadata', [ $this, 'modify_template_type' ] );
307 $value = get_post_meta( $object_id, Source::TYPE_META_KEY, $single );
308 $arr = [
309 'post' => 'single-post',
310 'page' => 'single-page',
311 'error' => 'error-404',
312 'product_single' => 'product',
313 'product_archive' => 'product-archive',
314 'course_single' => 'single-post',
315 'course_archive' => 'archive',
316 ];
317 $key = $single ? $value : (isset($value[0]) ? $value[0] : '');
318 $value = isset( $arr[ $key ] ) ? $arr[ $key ] : $value;
319 if(in_array($value, ['header', 'footer'])){
320 return $_value;
321 }
322 }
323
324 return $value;
325 }
326
327 public function register_elements( $widgets_manager ) {
328 $widgets_manager->register( new \Templately\Modules\ThemeBuilder\Widgets\Post_Title() );
329 $widgets_manager->register( new \Templately\Modules\ThemeBuilder\Widgets\Post_Content() );
330 $widgets_manager->register( new \Templately\Modules\ThemeBuilder\Widgets\Featured_Image() );
331 $widgets_manager->register( new \Templately\Modules\ThemeBuilder\Widgets\Site_Logo() );
332 $widgets_manager->register( new \Templately\Modules\ThemeBuilder\Widgets\Post_Comments() );
333 }
334
335 public function filter_content( $content ) {
336 if(is_singular()){
337 global $post;
338 if(!empty($post) && $post->post_type === 'templately_library'){
339 if(in_array(get_post_meta($post->ID, '_templately_template_type', true), ['header', 'footer'])){
340 return '';
341 }
342 }
343 }
344 return $content;
345 }
346
347 }