PluginProbe
Elementor Website Builder – more than just a page builder / 3.6.3
Elementor Website Builder – more than just a page builder v3.6.3
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.6.3, at modules/landing-pages/module.php

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