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

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