PluginProbe
Elementor Website Builder – more than just a page builder / 3.2.2
Elementor Website Builder – more than just a page builder v3.2.2
4.3.0-beta2 4.3.0-beta1 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.5 4.2.0-beta2 4.2.0-dev2 4.2.0-beta1 4.1.4 4.1.3 4.1.2 4.1.1 4.1.0 4.1.0-beta3 4.1.0-dev3 4.0.9 4.1.0-beta2 4.1.0-dev2 4.0.8 4.1.0-beta1 4.1.0-dev1 4.0.7 All 451 releases
elementor / modules / landing-pages / module.php

module.php in Elementor Website Builder – more than just a page builder 3.2.2, at modules/landing-pages/module.php

482 lines 16.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Elementor\Modules\LandingPages;
3
4 use Elementor\Core\Base\Module as BaseModule;
5 use Elementor\Core\Documents_Manager;
6 use Elementor\Core\Experiments\Manager as Experiments_Manager;
7 use Elementor\Modules\LandingPages\Documents\Landing_Page;
8 use Elementor\Modules\LandingPages\Module as Landing_Pages_Module;
9 use Elementor\Plugin;
10 use Elementor\TemplateLibrary\Source_Local;
11 use Elementor\Utils;
12
13 if ( ! defined( 'ABSPATH' ) ) {
14 exit; // Exit if accessed directly.
15 }
16
17 class Module extends BaseModule {
18
19 const DOCUMENT_TYPE = 'landing-page';
20 const CPT = 'e-landing-page';
21 const ADMIN_PAGE_SLUG = 'edit.php?post_type=' . self::CPT;
22
23 private $posts;
24 private $trashed_posts;
25 private $new_lp_url;
26 private $permalink_structure;
27
28 public function get_name() {
29 return 'landing-pages';
30 }
31
32 /**
33 * Get Experimental Data
34 *
35 * Implementation of this method makes the module an experiment.
36 *
37 * @since 3.1.0
38 *
39 * @return array
40 */
41 public static function get_experimental_data() {
42 return [
43 'name' => 'landing-pages',
44 'title' => __( 'Landing Pages', 'elementor' ),
45 'description' => __( 'Adds a new Elementor content type that allows creating beautiful landing pages instantly in a streamlined workflow.', 'elementor' ),
46 'release_status' => Experiments_Manager::RELEASE_STATUS_BETA,
47 'new_site' => [
48 'default_active' => true,
49 'minimum_installation_version' => '3.1.0-beta',
50 ],
51 ];
52 }
53
54 /**
55 * Get Trashed Landing Pages Posts
56 *
57 * Returns the posts property of a WP_Query run for Landing Pages with post_status of 'trash'.
58 *
59 * @since 3.1.0
60 *
61 * @return array trashed posts
62 */
63 private function get_trashed_landing_page_posts() {
64 if ( $this->trashed_posts ) {
65 return $this->trashed_posts;
66 }
67
68 // `'posts_per_page' => 1` is because this is only used as an indicator to whether there are any trashed landing pages.
69 $trashed_posts_query = new \WP_Query( [
70 'post_type' => self::CPT,
71 'post_status' => 'trash',
72 'posts_per_page' => 1,
73 'meta_key' => '_elementor_template_type',
74 'meta_value' => self::DOCUMENT_TYPE,
75 ] );
76
77 $this->trashed_posts = $trashed_posts_query->posts;
78
79 return $this->trashed_posts;
80 }
81
82 /**
83 * Get Landing Pages Posts
84 *
85 * Returns the posts property of a WP_Query run for posts with the Landing Pages CPT.
86 *
87 * @since 3.1.0
88 *
89 * @return array posts
90 */
91 private function get_landing_page_posts() {
92 if ( $this->posts ) {
93 return $this->posts;
94 }
95
96 // `'posts_per_page' => 1` is because this is only used as an indicator to whether there are any landing pages.
97 $posts_query = new \WP_Query( [
98 'post_type' => self::CPT,
99 'post_status' => 'any',
100 'posts_per_page' => 1,
101 'meta_key' => '_elementor_template_type',
102 'meta_value' => self::DOCUMENT_TYPE,
103 ] );
104
105 $this->posts = $posts_query->posts;
106
107 return $this->posts;
108 }
109
110 /**
111 * Is Elementor Landing Page.
112 *
113 * Check whether the post is an Elementor Landing Page.
114 *
115 * @since 3.1.0
116 * @access public
117 *
118 * @param \WP_Post $post Post Object
119 *
120 * @return bool Whether the post was built with Elementor.
121 */
122 public function is_elementor_landing_page( $post ) {
123 return self::CPT === $post->post_type;
124 }
125
126 /**
127 * Add Submenu Page
128 *
129 * Adds the 'Landing Pages' submenu item to the 'Templates' menu item.
130 *
131 * @since 3.1.0
132 */
133 private function add_submenu_page() {
134 $posts = $this->get_landing_page_posts();
135
136 // If there are no Landing Pages, show the "Create Your First Landing Page" page.
137 // If there are, show the pages table.
138 if ( ! empty( $posts ) ) {
139 $landing_page_menu_slug = self::ADMIN_PAGE_SLUG;
140 $landing_page_menu_callback = null;
141 } else {
142 $landing_page_menu_slug = self::CPT;
143 $landing_page_menu_callback = [ $this, 'print_empty_landing_pages_page' ];
144 }
145
146 $landing_pages_title = __( 'Landing Pages', 'elementor' );
147
148 add_submenu_page(
149 Source_Local::ADMIN_MENU_SLUG,
150 $landing_pages_title,
151 $landing_pages_title,
152 'manage_options',
153 $landing_page_menu_slug,
154 $landing_page_menu_callback
155 );
156 }
157
158 /**
159 * Get 'Add New' Landing Page URL
160 *
161 * Retrieves the custom URL for the admin dashboard's 'Add New' button in the Landing Pages admin screen. This URL
162 * creates a new Landing Pages and directly opens the Elementor Editor with the Template Library modal open on the
163 * Landing Pages tab.
164 *
165 * @since 3.1.0
166 *
167 * @return string
168 */
169 private function get_add_new_landing_page_url() {
170 if ( ! $this->new_lp_url ) {
171 $this->new_lp_url = Utils::get_create_new_post_url( self::CPT, self::DOCUMENT_TYPE ) . '#library';
172 }
173 return $this->new_lp_url;
174 }
175
176 /**
177 * Get Empty Landing Pages Page
178 *
179 * Prints the HTML content of the page that is displayed when there are no existing landing pages in the DB.
180 * Added as the callback to add_submenu_page.
181 *
182 * @since 3.1.0
183 */
184 public function print_empty_landing_pages_page() {
185 $template_sources = Plugin::$instance->templates_manager->get_registered_sources();
186 $source_local = $template_sources['local'];
187 $trashed_posts = $this->get_trashed_landing_page_posts();
188
189 ?>
190 <div class="e-landing-pages-empty">
191 <?php
192 /** @var Source_Local $source_local */
193 $source_local->print_blank_state_template( __( 'Landing Page', 'elementor' ), $this->get_add_new_landing_page_url(), __( 'Build Effective Landing Pages for your business\' marketing campaigns.', 'elementor' ) );
194
195 if ( ! empty( $trashed_posts ) ) : ?>
196 <div class="e-trashed-items">
197 <?php echo sprintf( __( 'Or view <a href="%s">Trashed Items</a>', 'elementor' ), admin_url( 'edit.php?post_status=trash&post_type=' . self::CPT ) ); ?>
198 </div>
199 <?php endif; ?>
200 </div>
201 <?php
202 }
203
204 /**
205 * Is Current Admin Page Edit LP
206 *
207 * Checks whether the current page is a native WordPress edit page for a landing page.
208 */
209 private function is_landing_page_admin_edit() {
210 $screen = get_current_screen();
211
212 if ( 'post' === $screen->base ) {
213 return $this->is_elementor_landing_page( get_post() );
214 }
215
216 return false;
217 }
218
219 /**
220 * Admin Localize Settings
221 *
222 * Enables adding properties to the globally available elementorAdmin.config JS object in the Admin Dashboard.
223 * Runs on the 'elementor/admin/localize_settings' filter.
224 *
225 * @since 3.1.0
226 *
227 * @param $settings
228 * @return array|null
229 */
230 private function admin_localize_settings( $settings ) {
231 $additional_settings = [
232 'urls' => [
233 'addNewLandingPageUrl' => $this->get_add_new_landing_page_url(),
234 ],
235 'landingPages' => [
236 'landingPagesHasPages' => [] !== $this->get_landing_page_posts(),
237 'isLandingPageAdminEdit' => $this->is_landing_page_admin_edit(),
238 ],
239 ];
240
241 return array_replace_recursive( $settings, $additional_settings );
242 }
243
244 /**
245 * Register Landing Pages CPT
246 *
247 * @since 3.1.0
248 */
249 private function register_landing_page_cpt() {
250 $labels = [
251 'name' => __( 'Landing Pages', 'elementor' ),
252 'singular_name' => __( 'Landing Page', 'elementor' ),
253 'add_new' => __( 'Add New', 'elementor' ),
254 'add_new_item' => __( 'Add New Landing Page', 'elementor' ),
255 'edit_item' => __( 'Edit Landing Page', 'elementor' ),
256 'new_item' => __( 'New Landing Page', 'elementor' ),
257 'all_items' => __( 'All Landing Pages', 'elementor' ),
258 'view_item' => __( 'View Landing Page', 'elementor' ),
259 'search_items' => __( 'Search Landing Pages', 'elementor' ),
260 'not_found' => __( 'No landing pages found', 'elementor' ),
261 'not_found_in_trash' => __( 'No landing pages found in trash', 'elementor' ),
262 'parent_item_colon' => '',
263 'menu_name' => __( 'Landing Pages', 'elementor' ),
264 ];
265
266 $args = [
267 'labels' => $labels,
268 'public' => true,
269 'show_in_menu' => 'edit.php?post_type=elementor_library&tabs_group=library',
270 'capability_type' => 'page',
271 'taxonomies' => [ Source_Local::TAXONOMY_TYPE_SLUG ],
272 'supports' => [ 'title', 'editor', 'comments', 'revisions', 'trackbacks', 'author', 'excerpt', 'page-attributes', 'thumbnail', 'custom-fields', 'post-formats', 'elementor' ],
273 ];
274
275 register_post_type( self::CPT, $args );
276 }
277
278 /**
279 * Remove Post Type Slug
280 *
281 * Landing Pages are supposed to act exactly like pages. This includes their URLs being directly under the site's
282 * domain name. Since "Landing Pages" is a CPT, WordPress automatically adds the landing page slug as a prefix to
283 * it's posts' permalinks. This method checks if the post's post type is Landing Pages, and if it is, it removes
284 * the CPT slug from the requested post URL.
285 *
286 * Runs on the 'post_type_link' filter.
287 *
288 * @since 3.1.0
289 *
290 * @param $post_link
291 * @param $post
292 * @param $leavename
293 * @return string|string[]
294 */
295 private function remove_post_type_slug( $post_link, $post, $leavename ) {
296 // Only try to modify the permalink if the post is a Landing Page.
297 if ( self::CPT !== $post->post_type || 'publish' !== $post->post_status ) {
298 return $post_link;
299 }
300
301 // Any slug prefixes need to be removed from the post link.
302 return get_home_url() . '/' . $post->post_name . '/';
303 }
304
305 /**
306 * Adjust Landing Page Query
307 *
308 * Since Landing Pages are a CPT but should act like pages, the WP_Query that is used to fetch the page from the
309 * database needs to be adjusted. This method adds the Landing Pages CPT to the list of queried post types, to
310 * make sure the database query finds the correct Landing Page to display.
311 * Runs on the 'pre_get_posts' action.
312 *
313 * @since 3.1.0
314 *
315 * @param \WP_Query $query
316 */
317 private function adjust_landing_page_query( \WP_Query $query ) {
318 // Only handle actual pages.
319 if (
320 ! $query->is_main_query()
321 // If the query is not for a page.
322 || ! isset( $query->query['page'] )
323 // If the query is for a static home/blog page.
324 || is_home()
325 // If the post type comes already set, the main query is probably a custom one made by another plugin.
326 // In this case we do not want to intervene in order to not cause a conflict.
327 || isset( $query->query['post_type'] )
328 ) {
329 return;
330 }
331
332 // Create the post types property as an array and include the landing pages CPT in it.
333 $query_post_types = [ 'post', 'page', self::CPT ];
334
335 // Since WordPress determined this is supposed to be a page, we'll pre-set the post_type query arg to make sure
336 // it includes the Landing Page CPT, so when the query is parsed, our CPT will be a legitimate match to the
337 // Landing Page's permalink (that is directly under the domain, without a CPT slug prefix). In some cases,
338 // The 'name' property will be set, and in others it is the 'pagename', so we have to cover both cases.
339 if ( ! empty( $query->query['name'] ) ) {
340 $query->set( 'post_type', $query_post_types );
341 } elseif ( ! empty( $query->query['pagename'] ) && false === strpos( $query->query['pagename'], '/' ) ) {
342 $query->set( 'post_type', $query_post_types );
343
344 // We also need to set the name query var since redirect_guess_404_permalink() relies on it.
345 $query->set( 'name', $query->query['pagename'] );
346 }
347 }
348
349 /**
350 * Handle 404
351 *
352 * This method runs after a page is not found in the database, but before a page is returned as a 404.
353 * These cases are handled in this filter callback, that runs on the 'pre_handle_404' filter.
354 *
355 * In some cases (such as when a site uses custom permalink structures), WordPress's WP_Query does not identify a
356 * Landing Page's URL as a post belonging to the Landing Page CPT. Some cases are handled successfully by the
357 * adjust_landing_page_query() method, but some are not and still trigger a 404 process. This method handles such
358 * cases by overriding the $wp_query global to fetch the correct landing page post entry.
359 *
360 * For example, since Landing Pages slugs come directly after the site domain name, WP_Query might parse the post
361 * as a category page. Since there is no category matching the slug, it triggers a 404 process. In this case, we
362 * run a query for a Landing Page post with the passed slug ($query->query['category_name']. If a Landing Page
363 * with the passed slug is found, we override the global $wp_query with the new, correct query.
364 *
365 * @param $current_value
366 * @param $query
367 * @return false
368 */
369 private function handle_404( $current_value, $query ) {
370 global $wp_query;
371
372 // If another plugin/theme already used this filter, exit here to avoid conflicts.
373 if ( $current_value ) {
374 return $current_value;
375 }
376
377 if (
378 // Make sure we only intervene in the main query.
379 ! $query->is_main_query()
380 // If a post was found, this is not a 404 case, so do not intervene.
381 || ! empty( $query->posts )
382 // This filter is only meant to deal with wrong queries where the only query var is 'category_name'.
383 // If there is no 'category_name' query var, do not intervene.
384 || empty( $query->query['category_name'] )
385 // If the query is for a real taxonomy (determined by it including a table to search in, such as the
386 // wp_term_relationships table), do not intervene.
387 || ! empty( $query->tax_query->table_aliases )
388 ) {
389 return false;
390 }
391
392 // Search for a Landing Page with the same name passed as the 'category name'.
393 $possible_new_query = new \WP_Query( [
394 'post_type' => self::CPT,
395 'name' => $query->query['category_name'],
396 ] );
397
398 // Only if such a Landing Page is found, override the query to fetch the correct page.
399 if ( ! empty( $possible_new_query->posts ) ) {
400 $wp_query = $possible_new_query; //phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
401 }
402
403 return false;
404 }
405
406 public function __construct() {
407 $this->permalink_structure = get_option( 'permalink_structure' );
408
409 $this->register_landing_page_cpt();
410
411 // If there is a permalink structure set to the site, run the hooks that modify the Landing Pages permalinks to
412 // match WordPress' native 'Pages' post type.
413 if ( '' !== $this->permalink_structure ) {
414 // Landing Pages' post link needs to be modified to be identical to the pages permalink structure. This
415 // needs to happen in both the admin and the front end, since post links are also used in the admin pages.
416 add_filter( 'post_type_link', function( $post_link, $post, $leavename ) {
417 return $this->remove_post_type_slug( $post_link, $post, $leavename );
418 }, 10, 3 );
419
420 // The query itself only has to be manipulated when pages are viewed in the front end.
421 if ( ! is_admin() || wp_doing_ajax() ) {
422 add_action( 'pre_get_posts', function ( $query ) {
423 $this->adjust_landing_page_query( $query );
424 } );
425
426 // Handle cases where visiting a Landing Page's URL returns 404.
427 add_filter( 'pre_handle_404', function ( $value, $query ) {
428 return $this->handle_404( $value, $query );
429 }, 10, 2 );
430 }
431 }
432
433 add_action( 'elementor/documents/register', function( Documents_Manager $documents_manager ) {
434 $documents_manager->register_document_type( self::DOCUMENT_TYPE, Landing_Page::get_class_full_name() );
435 } );
436
437 add_action( 'admin_menu', function() {
438 $this->add_submenu_page();
439 }, 30 );
440
441 // Add the custom 'Add New' link for Landing Pages into Elementor's admin config.
442 add_action( 'elementor/admin/localize_settings', function( array $settings ) {
443 return $this->admin_localize_settings( $settings );
444 } );
445
446 add_filter( 'elementor/template_library/sources/local/register_taxonomy_cpts', function( array $cpts ) {
447 $cpts[] = self::CPT;
448
449 return $cpts;
450 } );
451
452 // In the Landing Pages Admin Table page - Overwrite Template type column header title.
453 add_action( 'manage_' . Landing_Pages_Module::CPT . '_posts_columns', function( $posts_columns ) {
454 /** @var Source_Local $source_local */
455 $source_local = Plugin::$instance->templates_manager->get_source( 'local' );
456
457 return $source_local->admin_columns_headers( $posts_columns );
458 } );
459
460 // In the Landing Pages Admin Table page - Overwrite Template type column row values.
461 add_action( 'manage_' . Landing_Pages_Module::CPT . '_posts_custom_column', function( $column_name, $post_id ) {
462 /** @var Landing_Page $document */
463 $document = Plugin::$instance->documents->get( $post_id );
464
465 $document->admin_columns_content( $column_name );
466 }, 10, 2 );
467
468 // Overwrite the Admin Bar's 'New +' Landing Page URL with the link that creates the new LP in Elementor
469 // with the Template Library modal open.
470 add_action( 'admin_bar_menu', function( $admin_bar ) {
471 // Get the Landing Page menu node.
472 $new_landing_page_node = $admin_bar->get_node( 'new-e-landing-page' );
473
474 if ( $new_landing_page_node ) {
475 $new_landing_page_node->href = $this->get_add_new_landing_page_url();
476
477 $admin_bar->add_node( $new_landing_page_node );
478 }
479 }, 100 );
480 }
481 }
482