PluginProbe
User Profile Builder – Beautiful User Registration Forms, User Profiles & User Role Editor / 4.0.2
User Profile Builder – Beautiful User Registration Forms, User Profiles & User Role Editor v4.0.2
4.0.2 4.0.1 4.0.0 3.16.6 3.16.5 3.16.4 3.16.3 3.16.2 3.16.1 3.16.0 3.15.9 3.9.9 3.9.5 3.9.6 3.9.7 3.9.8 1.1.7 1.1.8 1.1.9 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 All 340 releases
profile-builder / admin / admin-functions.php

admin-functions.php in User Profile Builder – Beautiful User Registration Forms, User Profiles & User Role Editor 4.0.2, at admin/admin-functions.php

1,069 lines 46.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
3
4 /**
5 * Function which returns the same field-format over and over again.
6 *
7 * @since v.2.0
8 *
9 * @param string $field
10 * @param string $field_title
11 *
12 * @return string
13 */
14 function wppb_field_format ( $field_title, $field ){
15
16 return trim( $field_title ).' ( '.trim( $field ).' )';
17 }
18
19 /**
20 * Add a notification for either the Username or the Email field letting the user know that, even though it is there, it won't do anything
21 *
22 * @since v.2.0
23 *
24 * @param string $form
25 * @param integer $id
26 * @param string $value
27 *
28 * @return string $form
29 */
30
31 function wppb_manage_fields_display_field_title_slug( $form ){
32 // add a notice to fields
33 global $wppb_results_field;
34 switch ($wppb_results_field){
35 case 'Default - Username':
36 $wppb_generalSettings = get_option( 'wppb_general_settings', 'not_found' );
37 if ( $wppb_generalSettings != 'not_found' && $wppb_generalSettings['loginWith'] == 'email' ) {
38 $form .= '<div id="wppb-login-email-nag" class="wppb-backend-notice">' . sprintf(__('Login is set to be done using the Email. This field will NOT appear in the front-end! ( you can change these settings under the "%s" tab )', 'profile-builder'), '<a href="' . admin_url('admin.php?page=profile-builder-general-settings') . '" target="_blank">' . __('General Settings', 'profile-builder') . '</a>') . '</div>';
39 }
40 break;
41 case 'Default - Display name publicly as':
42 $form .= '<div id="wppb-display-name-nag" class="wppb-backend-notice">' . __( 'Display name publicly as - only appears on the Edit Profile page!', 'profile-builder' ) . '</div>';
43 break;
44 case 'Default - Blog Details':
45 $form .= '<div id="wppb-blog-details-nag" class="wppb-backend-notice">' . __( 'Blog Details - only appears on the Registration page!', 'profile-builder' ) . '</div>';
46 break;
47 }
48
49 return $form;
50 }
51
52 add_filter( 'wck_after_content_element', 'wppb_manage_fields_display_field_title_slug' );
53
54 /**
55 * Check if field type is 'Default - Display name publicly as' so we can add a notification for it
56 *
57 * @since v.2.2
58 *
59 * @param string $wck_element_class
60 * @param string $meta
61 * @param array $results
62 * @param integer $element_id
63 *
64 */
65 function wppb_manage_fields_display_name_notice( $wck_element_class, $meta, $results, $element_id ) {
66 global $wppb_results_field;
67
68 $wppb_results_field = $results[$element_id]['field'];
69 }
70 add_filter( 'wck_element_class_wppb_manage_fields', 'wppb_manage_fields_display_name_notice', 10, 4 );
71
72 /**
73 * Function that adds a custom class to the existing container
74 *
75 * @since v.2.0
76 *
77 * @param string $update_container_class - the new class name
78 * @param string $meta - the name of the meta
79 * @param array $results
80 * @param integer $element_id - the ID of the element
81 *
82 * @return string
83 */
84 function wppb_update_container_class( $update_container_class, $meta, $results, $element_id ) {
85 $wppb_element_type = Wordpress_Creation_Kit_PB::wck_generate_slug( $results[$element_id]["field"] );
86
87 return "class='wck_update_container update_container_$meta update_container_$wppb_element_type element_type_$wppb_element_type'";
88 }
89 add_filter( 'wck_update_container_class_wppb_manage_fields', 'wppb_update_container_class', 10, 4 );
90
91 /**
92 * Function that adds a custom class to the existing element
93 *
94 * @since v.2.0
95 *
96 * @param string $element_class - the new class name
97 * @param string $meta - the name of the meta
98 * @param array $results
99 * @param integer $element_id - the ID of the element
100 *
101 * @return string
102 */
103 function wppb_element_class( $element_class, $meta, $results, $element_id ){
104 $wppb_element_type = Wordpress_Creation_Kit_PB::wck_generate_slug( $results[$element_id]["field"] );
105
106 return "class='element_type_$wppb_element_type added_fields_list'";
107 }
108 add_filter( 'wck_element_class_wppb_manage_fields', 'wppb_element_class', 10, 4 );
109
110 /**
111 * Functions to check password length and strength
112 *
113 * @since v.2.0
114 */
115 /* on add user and update profile from WP admin area */
116 add_action( 'user_profile_update_errors', 'wppb_password_check_on_profile_update', 0, 3 );
117 function wppb_password_check_on_profile_update( $errors, $update, $user ){
118 wppb_password_check_extra_conditions( $errors, $user );
119 }
120
121 /* on reset password */
122 add_action( 'validate_password_reset', 'wppb_password_check_extra_conditions', 10, 2 );
123 function wppb_password_check_extra_conditions( $errors, $user ){
124 $password = ( isset( $_POST[ 'pass1' ] ) && trim( $_POST[ 'pass1' ] ) ) ? $_POST[ 'pass1' ] : false; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
125
126 if( $password && ( !isset( $_POST['pw_weak'] ) || $_POST['pw_weak'] != 'on' ) ){
127 $wppb_generalSettings = get_option( 'wppb_general_settings' );
128
129 if( !empty( $wppb_generalSettings['minimum_password_length'] ) ){
130 if( strlen( $password ) < $wppb_generalSettings['minimum_password_length'] )
131 $errors->add( 'pass', sprintf( __( '<strong>ERROR:</strong> The password must have the minimum length of %s characters', 'profile-builder' ), $wppb_generalSettings['minimum_password_length'] ) );
132 }
133
134 if( isset( $_POST['wppb_password_strength'] ) && !empty( $wppb_generalSettings['minimum_password_strength'] ) ){
135 $password_strength_array = array( 'short' => 0, 'bad' => 1, 'good' => 2, 'strong' => 3 );
136 $password_strength_text = array( 'short' => __( 'Very weak', 'profile-builder' ), 'bad' => __( 'Weak', 'profile-builder' ), 'good' => __( 'Medium', 'profile-builder' ), 'strong' => __( 'Strong', 'profile-builder' ) );
137
138 foreach( $password_strength_text as $psr_key => $psr_text ){
139 if( $psr_text == sanitize_text_field( $_POST['wppb_password_strength'] ) ){
140 $password_strength_result_slug = $psr_key;
141 break;
142 }
143 }
144
145 if( !empty( $password_strength_result_slug ) ){
146 if( $password_strength_array[$password_strength_result_slug] < $password_strength_array[$wppb_generalSettings['minimum_password_strength']] )
147 $errors->add( 'pass', sprintf( __( '<strong>ERROR:</strong> The password must have a minimum strength of %s', 'profile-builder' ), $password_strength_text[$wppb_generalSettings['minimum_password_strength']] ) );
148 }
149 }
150 }
151
152 return $errors;
153 }
154
155 /* we need to create a hidden field that contains the results of the password strength from the js strength tester */
156 add_action( 'admin_footer', 'wppb_add_hidden_password_strength_on_backend' );
157 add_action( 'login_footer', 'wppb_add_hidden_password_strength_on_backend' );
158 function wppb_add_hidden_password_strength_on_backend(){
159 if( $GLOBALS['pagenow'] == 'profile.php' || $GLOBALS['pagenow'] == 'user-new.php' || ( $GLOBALS['pagenow'] == 'wp-login.php' && isset( $_GET['action'] ) && ( $_GET['action'] === 'rp' || $_GET['action'] === 'resetpass' ) ) ){
160 $wppb_generalSettings = get_option( 'wppb_general_settings' );
161 if( !empty( $wppb_generalSettings['minimum_password_strength'] ) ){
162 ?>
163 <script type="text/javascript">
164 jQuery( document ).ready( function() {
165 var passswordStrengthResult = jQuery( '#pass-strength-result' );
166 // Check for password strength meter
167 if ( passswordStrengthResult.length ) {
168 // Attach submit event to form
169 passswordStrengthResult.parents( 'form' ).on( 'submit', function() {
170 // Store check results in hidden field
171 jQuery( this ).append( '<input type="hidden" name="wppb_password_strength" value="' + passswordStrengthResult.text() + '">' );
172 });
173 }
174 });
175 </script>
176 <?php
177 }
178 }
179 }
180
181 /* Modify the Add Entry buttons for WCK metaboxes according to context */
182 add_filter( 'wck_add_entry_button', 'wppb_change_add_entry_button', 10, 2 );
183 function wppb_change_add_entry_button( $string, $meta ){
184 if( $meta == 'wppb_manage_fields' || $meta == 'wppb_epf_fields' || $meta == 'wppb_rf_fields' ){
185 return __( "Add Field", 'profile-builder' );
186 }elseif( $meta == 'wppb_epf_page_settings' || $meta == 'wppb_rf_page_settings' || $meta == 'wppb_ul_page_settings' ){
187 return __( "Save Settings", 'profile-builder' );
188 }
189
190 return $string;
191 }
192
193 /**
194 * add links on plugin page
195 */
196 add_filter( 'plugin_action_links', 'wppb_plugin_action_links', 10, 2 );
197 function wppb_plugin_action_links( $links, $file ) {
198 if ( $file != WPPB_PLUGIN_BASENAME ) {
199 return $links;
200 }
201
202 if ( ! current_user_can( 'manage_options' ) ) {
203 return $links;
204 }
205
206 $settings_link = sprintf( '<a href="%1$s">%2$s</a>',
207 menu_page_url( 'profile-builder-general-settings', false ),
208 esc_html( __( 'Settings', 'profile-builder' ) ) );
209
210 array_unshift( $links, $settings_link );
211
212 return $links;
213 }
214
215 /**
216 * add links on plugin page
217 */
218 add_filter( 'plugin_row_meta', 'wppb_plugin_row_meta', 10, 2 );
219 function wppb_plugin_row_meta( $links, $file ) {
220 if ( WPPB_PLUGIN_BASENAME == $file ) {
221 $row_meta = array(
222 'docs' => '<a href="' . esc_url( 'https://www.cozmoslabs.com/docs/profile-builder/' ) . '" target="_blank" aria-label="' . esc_attr__( 'View Profile Builder documentation', 'profile-builder' ) . '">' . esc_html__( 'Docs', 'profile-builder' ) . '</a>',
223 'get_support' => '<a href="' . esc_url( apply_filters( 'wppb_docs_url', 'https://wordpress.org/support/plugin/profile-builder/' ) ) . '" title="' . esc_attr( __( 'Get Support', 'profile-builder' ) ) . '" target="_blank">' . __( 'Get Support', 'profile-builder' ) . '</a>',
224 );
225
226 return array_merge( $links, $row_meta );
227 }
228
229 return (array) $links;
230 }
231
232 /**
233 * Send deactivation feedback from the plugins screen modal.
234 *
235 * @param array $reason_data Sanitized reason payload from the popup form.
236 */
237 function wppb_send_deactivation_feedback( $reason_data ) {
238
239 if ( ! is_array( $reason_data ) || empty( $reason_data['reason'] ) )
240 return;
241
242 $body = array(
243 'unique_identifier' => hash( 'sha256', home_url( '', 'https' ) ),
244 'product' => 'wppb',
245 'action' => 'end',
246 'reason' => sanitize_key( $reason_data['reason'] ),
247 );
248
249 $reason_key = $reason_data['reason'] . '_reason';
250
251 if ( ! empty( $reason_data[ $reason_key ] ) )
252 $body['extra_metadata'] = sanitize_text_field( $reason_data[ $reason_key ] );
253
254 wp_remote_post( 'https://usagetracker.cozmoslabs.com/syncPlugin', array(
255 'body' => $body,
256 'timeout' => 3,
257 'blocking' => false,
258 ) );
259 }
260
261 /**
262 * Store the deactivation reason selected in the popup
263 */
264 function wppb_store_deactivation_reason() {
265
266 if ( ! check_ajax_referer( 'wppb_deactivation_reason', 'nonce', false ) )
267 wp_send_json_error( array( 'message' => __( 'We could not verify your request. Please refresh the page and try again.', 'profile-builder' ) ), 403 );
268
269 if ( ! current_user_can( 'activate_plugins' ) )
270 wp_send_json_error( array( 'message' => __( 'You do not have permission to deactivate plugins on this site.', 'profile-builder' ) ), 403 );
271
272 $valid_reasons = array(
273 'dont_need_it',
274 'switched_to_another_plugin',
275 'missing_features',
276 'did_not_work',
277 'temporary_deactivation',
278 'other',
279 'skip',
280 );
281
282 $reason = '';
283
284 if ( isset( $_POST['reason'] ) )
285 $reason = sanitize_key( wp_unslash( $_POST['reason'] ) );
286
287 if ( ! in_array( $reason, $valid_reasons, true ) )
288 wp_send_json_error( array( 'message' => __( 'We could not save your deactivation feedback. Please try again.', 'profile-builder' ) ), 400 );
289
290 $reason_data = array(
291 'reason' => $reason,
292 );
293
294 if ( $reason === 'switched_to_another_plugin' && ! empty( $_POST['switched_to_another_plugin_reason'] ) )
295 $reason_data['switched_to_another_plugin_reason'] = sanitize_text_field( wp_unslash( $_POST['switched_to_another_plugin_reason'] ) );
296
297 if ( $reason === 'missing_features' && ! empty( $_POST['missing_features_reason'] ) )
298 $reason_data['missing_features_reason'] = sanitize_text_field( wp_unslash( $_POST['missing_features_reason'] ) );
299
300 if ( $reason === 'other' && ! empty( $_POST['other_reason'] ) )
301 $reason_data['other_reason'] = sanitize_text_field( wp_unslash( $_POST['other_reason'] ) );
302
303 wppb_send_deactivation_feedback( $reason_data );
304
305 wp_send_json_success();
306 }
307 add_action( 'wp_ajax_wppb_store_deactivation_reason', 'wppb_store_deactivation_reason' );
308
309 /* hook to create pages for out forms when a user press the create pages/setup button */
310 add_action( 'admin_init', 'wppb_create_form_pages' );
311 function wppb_create_form_pages(){
312
313 if( !isset( $_GET['page'] ) || $_GET['page'] != 'profile-builder-basic-info' || !isset( $_GET['wppb_create_pages'] ) || $_GET['wppb_create_pages'] != 'true' )
314 return;
315
316 if( !isset( $_GET['_wpnonce'] ) || !wp_verify_nonce( sanitize_text_field( $_GET['_wpnonce'] ), 'wppb_create_pages' ) )
317 return;
318
319 if( !current_user_can( 'manage_options' ) )
320 return;
321
322 $wppb_pages_created = get_option( 'wppb_pages_created' );
323
324 if( empty( $wppb_pages_created ) || ( isset( $_GET['wppb_force_create_pages'] ) && $_GET['wppb_force_create_pages'] === 'true' ) ) {
325 $register_page = array(
326 'post_title' => 'Register',
327 'post_content' => '[wppb-register]',
328 'post_status' => 'publish',
329 'post_type' => 'page'
330 );
331 $register_id = wp_insert_post($register_page);
332
333 $edit_page = array(
334 'post_title' => 'Edit Profile',
335 'post_content' => '[wppb-edit-profile]',
336 'post_status' => 'publish',
337 'post_type' => 'page'
338 );
339 $edit_id = wp_insert_post($edit_page);
340
341 $login_page = array(
342 'post_title' => 'Log In',
343 'post_content' => '[wppb-login]',
344 'post_status' => 'publish',
345 'post_type' => 'page'
346 );
347 $login_id = wp_insert_post($login_page);
348
349 update_option('wppb_pages_created', 'true' );
350
351 wp_safe_redirect( admin_url('edit.php?s=%5Bwppb-&post_status=all&post_type=page&action=-1&m=0&paged=1&action2=-1') );
352 }
353 }
354
355 /**
356 * Function that prepares labels to pass to the WCK api...we have a quirk in wck api that labels are wrapped
357 * in %label%...so if we want to have % inside the label for example 25% off...we will have a bad time
358 * @param string $label
359 * @return string|string[]
360 */
361 function wppb_prepare_wck_labels( $label ){
362 return trim( str_replace( '%', '&#37;', $label ) );
363 }
364
365 /**
366 * Function that returns the reserved meta name list
367 */
368 function wppb_get_reserved_meta_name_list( $all_fields, $posted_values ){
369
370 $unique_meta_name_list = array( 'first_name', 'last_name', 'nickname', 'description', 'role' );
371
372 // Default contact methods were removed in WP 3.6. A filter dictates contact methods.
373 if ( apply_filters( 'wppb_remove_default_contact_methods', get_site_option( 'initial_db_version' ) < 23588 ) ){
374 $unique_meta_name_list[] = 'aim';
375 $unique_meta_name_list[] = 'yim';
376 $unique_meta_name_list[] = 'jabber';
377 }
378
379 $add_reserved = true;
380
381 // source: https://codex.wordpress.org/Reserved_Terms
382 $reserved_meta_names = array( 'action', 'attachment', 'attachment_id', 'author', 'author_name', 'calendar', 'cat', 'category', 'category__and', 'category__in', 'category__not_in', 'category_name', 'comments_per_page',
383 'comments_popup', 'custom', 'customize_messenger_channel', 'customized', 'cpage', 'day', 'debug', 'embed', 'error', 'exact', 'feed', 'fields', 'hour', 'link_category', 'm', 'map', 'minute', 'monthnum', 'more',
384 'name', 'nav_menu', 'nonce', 'nopaging', 'offset', 'order', 'orderby', 'p', 'page', 'page_id', 'paged', 'pagename', 'pb', 'perm', 'post', 'post__in', 'post__not_in', 'post_format', 'post_mime_type',
385 'post_status', 'post_tag', 'post_type', 'posts', 'posts_per_archive_page', 'posts_per_page', 'preview', 'robots', 's', 'search', 'second', 'sentence', 'showposts', 'static', 'status', 'subpost',
386 'subpost_id', 'tag', 'tag__and', 'tag__in', 'tag__not_in', 'tag_id', 'tag_slug__and', 'tag_slug__in', 'taxonomy', 'tb', 'term', 'terms', 'theme', 'title', 'type', 'types', 'w', 'withcomments', 'withoutcomments', 'year' );
387
388 $args = array(
389 'public' => true,
390 '_builtin' => true
391 );
392 $post_types = get_post_types( $args, 'names', 'or' );
393 $taxonomies = get_taxonomies( $args, 'names', 'or' );
394
395
396 /*reserved meta names were added in PB 3.1.2 so to avoid the situation where someone updates an already existing field
397 with a reserved name and gets an error check if it is an update or new field */
398 if( !empty( $all_fields ) && !empty($posted_values['id'] ) && !empty($posted_values['meta-name']) ){
399 foreach( $all_fields as $field ){
400 if( $field['id'] === $posted_values['id'] && $field['meta-name'] === $posted_values['meta-name'] ){//it is an update
401 $add_reserved = false;
402 }
403 }
404 }
405
406 if( $add_reserved )
407 $unique_meta_name_list = array_merge( $unique_meta_name_list, $reserved_meta_names, $post_types, $taxonomies );
408
409 return apply_filters ( 'wppb_unique_meta_name_list', $unique_meta_name_list );
410 }
411
412 add_action('admin_head', 'wppb_maybe_remove_add_new_button_for_cpt' );
413 function wppb_maybe_remove_add_new_button_for_cpt() {
414
415 global $pagenow;
416
417 // Registration ('wppb-rf-cpt') and Edit Profile ('wppb-epf-cpt') forms are deliberately excluded: form
418 // creation is gated on an active paid VERSION at the CPT capability level (see
419 // wppb_fb_multiple_forms_available() in form-builder/form-builder-licensing.php), not on the LICENSE. Gating
420 // them here would disable a button an expired-but-paid install may still use, and -- since the capability gate
421 // makes core skip the button entirely on free -- would inject an empty, label-less stand-in next to the page
422 // title (no button left to read a label from). The free-tier stand-in comes from
423 // wppb_fb_enqueue_locked_add_new_button() instead. Same narrowing as wppb_filter_own_post_creation() below.
424 $target_slugs = [ 'wppb-ul-cpt' ];
425 $current_slug = '';
426 $pointer_content = '';
427
428 $correct_page = false;
429
430 if ( $pagenow === 'edit.php' && isset( $_GET['post_type'] ) && in_array( sanitize_text_field( $_GET['post_type'] ), $target_slugs ) ) {
431 $correct_page = true;
432 $current_slug = sanitize_text_field( $_GET['post_type'] );
433 } else if( $pagenow === 'post.php' && isset( $_GET['post'] ) ) {
434
435 $post_type = get_post_type( absint( $_GET['post'] ) );
436
437 if ( in_array( $post_type, $target_slugs ) ) {
438 $correct_page = true;
439 $current_slug = $post_type;
440 }
441
442 }
443
444 if ( $correct_page ) {
445 $license_status = wppb_get_serial_number_status();
446
447 if( $current_slug === 'wppb-ul-cpt' ) {
448 if( $license_status == 'missing' ) {
449 $pointer_content .= '<p>' . sprintf( __( 'Please %1$senter your license key%2$s first, to add new User Listing.', 'profile-builder' ), '<a href="'. admin_url( 'admin.php?page=profile-builder-general-settings' ) .'">', '</a>' ) . '</p>';
450 } else {
451 $pointer_content .= '<p>' . sprintf( __( 'You need an active license to add a new User Listing. %1$sRenew%2$s or %3$spurchase a new one%4$s.', 'profile-builder' ), '<a href="https://www.cozmoslabs.com/account/?utm_source=pb-user-listing&utm_medium=client-site&utm_campaign=pb-expired-license">', '</a>', '<a href="https://www.cozmoslabs.com/wordpress-profile-builder/?utm_source=pb-user-listing&utm_medium=client-site&utm_campaign=pb-user-listing-addon#pricing" target="_blank">', '</a>' ) . '</p>';
452 }
453 }
454
455 if( $license_status !== 'valid' ) {
456 wp_enqueue_style('wp-pointer');
457 wp_enqueue_script('wp-pointer');
458
459 echo '
460 <script>
461 jQuery(document).ready(function($) {
462 let button_text = $(".page-title-action").text();
463 $(".page-title-action").remove();
464
465 $(".wrap .wp-heading-inline").after(`<a class="page-title-action page-title-action-disabled" style="cursor:pointer;">${button_text}</a>`);
466
467 $(".page-title-action-disabled").on("click", function(e) {
468 e.preventDefault();
469
470 let pointer_content = '. json_encode( $pointer_content ) .';
471
472 jQuery( this ).pointer({
473 content: pointer_content,
474 position: { edge: "right", align: "middle" }
475 }).pointer("open");
476 });
477 });
478 </script>';
479 }
480
481 }
482
483 // This shows the Add new button. Since the above action removes it completelty, we can just attempt to show it here
484 echo '<script>
485 jQuery(document).ready(function($) {
486 if( $(".page-title-action").length > 0 ) {
487 $(".page-title-action").css("display", "inline-flex");
488 }
489 });
490 </script>';
491
492 // Manage fields
493 if( isset( $_GET['page'] ) && $_GET['page'] === 'manage-fields' ) {
494 $license_status = wppb_get_serial_number_status();
495 $pointer_content_cl = '';
496 $pointer_content_fv = '';
497 $pointer_content_epaa = '';
498
499 if( $license_status == 'missing' ) {
500 $pointer_content_cl .= '<p>' . sprintf( __( 'Please %1$senter your license key%2$s first, to use the Conditional Logic feature.', 'profile-builder' ), '<a href="'. admin_url( 'admin.php?page=profile-builder-general-settings' ) .'">', '</a>' ) . '</p>';
501 $pointer_content_fv .= '<p>' . sprintf( __( 'Please %1$senter your license key%2$s first, to configure Field Visibility options.', 'profile-builder' ), '<a href="'. admin_url( 'admin.php?page=profile-builder-general-settings' ) .'">', '</a>' ) . '</p>';
502 $pointer_content_epaa .= '<p>' . sprintf( __( 'Please %1$senter your license key%2$s first, to use the Edit Profile Updates Approved by Admin addon feature.', 'profile-builder' ), '<a href="'. admin_url( 'admin.php?page=profile-builder-general-settings' ) .'">', '</a>' ) . '</p>';
503 } else {
504 $pointer_content_cl .= '<p>' . sprintf( __( 'You need an active license to configure the Condi tional Logic feature. %1$sRenew%2$s or %3$spurchase a new one%4$s.', 'profile-builder' ), '<a href="https://www.cozmoslabs.com/account/?utm_source=pb-form-fields-conditional&utm_medium=client-site&utm_campaign=pb-expired-license">', '</a>', '<a href="https://www.cozmoslabs.com/wordpress-profile-builder/?utm_source=pb-form-fields-conditional&utm_medium=client-site&utm_campaign=pb-conditional-logic#pricing" target="_blank">', '</a>' ) . '</p>';
505 $pointer_content_fv .= '<p>' . sprintf( __( 'You need an active license to configure Field Visibility options. %1$sRenew%2$s or %3$spurchase a new one%4$s.', 'profile-builder' ), '<a href="https://www.cozmoslabs.com/account/?utm_source=pb-form-fields-visibility&utm_medium=client-site&utm_campaign=pb-expired-license">', '</a>', '<a href="https://www.cozmoslabs.com/wordpress-profile-builder/?utm_source=pb-form-fields-visibility&utm_medium=client-site&utm_campaign=pb-field-visibility#pricing" target="_blank">', '</a>' ) . '</p>';
506 $pointer_content_epaa .= '<p>' . sprintf( __( 'You need an active license to use the Edit Profile Updates Approved by Admin addon. %1$sRenew%2$s or %3$spurchase a new one%4$s.', 'profile-builder' ), '<a href="https://www.cozmoslabs.com/account/?utm_source=pb-form-fields-approval-on-edit-profile&utm_medium=client-site&utm_campaign=pb-expired-license">', '</a>', '<a href="https://www.cozmoslabs.com/wordpress-profile-builder/?utm_source=pb-form-fields-approval-on-edit-profile&utm_medium=client-site&utm_campaign=pb-approval-on-edit-profile#pricing" target="_blank">', '</a>' ) . '</p>';
507 }
508
509 if( $license_status != 'valid' ) {
510 wp_enqueue_style('wp-pointer');
511 wp_enqueue_script('wp-pointer');
512
513 echo '
514 <script>
515 jQuery(document).ready(function($) {
516
517 $(document).on( "wpbFormMetaLoaded", function(e, meta) {
518
519 wppb_manage_fields_license_invalid( e );
520
521 });
522
523 wppb_manage_fields_license_invalid();
524
525 function wppb_manage_fields_license_invalid(){
526
527 if( $(".row-conditional-logic-enabled").length > 0 ) {
528
529 $(".row-conditional-logic").hide();
530
531 $("#edit_form_conditional-logic-enabled_yes, label[for=conditional-logic-enabled_yes]").on("click", function(e) {
532 e.preventDefault();
533 e.stopPropagation();
534 e.stopImmediatePropagation();
535
536 let pointer_content = '. json_encode( $pointer_content_cl ) .';
537 let pointer_target = jQuery( this ).parent( ".cozmoslabs-toggle-container" );
538
539 if( e && e.currentTarget && jQuery( e.currentTarget ).hasClass( "cozmoslabs-form-field-label" ) ) {
540 pointer_target = jQuery( this );
541 }
542
543 pointer_target.pointer({
544 content: pointer_content,
545 position: { edge: "right", align: "middle" }
546 }).pointer("open");
547 });
548 }
549
550 if( $(".row-edit-profile-approved-by-admin").length > 0 ) {
551
552 $("#edit_form_edit-profile-approved-by-admin_yes, label[for=edit-profile-approved-by-admin_yes]").on("click", function(e) {
553 e.preventDefault();
554 e.stopPropagation();
555 e.stopImmediatePropagation();
556
557 let pointer_content = '. json_encode( $pointer_content_epaa ) .';
558 let pointer_target = jQuery( this ).parent( ".cozmoslabs-toggle-container" );
559
560 if( e && e.currentTarget && jQuery( e.currentTarget ).hasClass( "cozmoslabs-form-field-label" ) ) {
561 pointer_target = jQuery( this );
562 }
563
564 pointer_target.pointer({
565 content: pointer_content,
566 position: { edge: "right", align: "middle" }
567 }).pointer("open");
568 });
569 }
570
571 if( $(".row-visibility").length > 0 ) {
572
573 $("select#visibility").on("mousedown", function(e) {
574 e.preventDefault();
575 e.stopPropagation();
576 e.stopImmediatePropagation();
577
578 let pointer_content = '. json_encode( $pointer_content_fv ) .';
579
580 jQuery( this ).pointer({
581 content: pointer_content,
582 position: { edge: "right", align: "middle" }
583 }).pointer("open");
584 });
585
586 }
587
588 if( $(".row-user-role-visibility").length > 0 ) {
589
590 $(".row-user-role-visibility .wck-checkboxes input").attr("disabled", true).css("pointer-events", "none");
591
592 $(".row-user-role-visibility .wck-checkboxes").on("click", function(e) {
593 e.preventDefault();
594 e.stopPropagation();
595 e.stopImmediatePropagation();
596
597 let pointer_content = '. json_encode( $pointer_content_fv ) .';
598
599 jQuery( this ).pointer({
600 content: pointer_content,
601 position: { edge: "right", align: "middle" }
602 }).pointer("open");
603 });
604
605 }
606
607 if( $(".row-location-visibility").length > 0 ) {
608
609 $(".row-location-visibility .wck-checkboxes input").attr("disabled", true).css("pointer-events", "none");
610
611 $(".row-location-visibility .wck-checkboxes").on("click", function(e) {
612 e.preventDefault();
613 e.stopPropagation();
614 e.stopImmediatePropagation();
615
616 let pointer_content = '. json_encode( $pointer_content_fv ) .';
617
618 jQuery( this ).pointer({
619 content: pointer_content,
620 position: { edge: "right", align: "middle" }
621 }).pointer("open");
622 });
623
624 }
625
626 }
627
628 });
629 </script>';
630 }
631 }
632
633 }
634
635 function wppb_filter_own_post_creation( $data, $postarr ) {
636 // Only User Listing still needs a valid license here; RF/EPF free/multiple-forms gating lives in form-builder-licensing.php.
637 if ( in_array( $data['post_type'], [ 'wppb-ul-cpt' ] ) && empty( $postarr['ID'] ) ) {
638
639 $license_status = wppb_get_serial_number_status();
640
641 if( $license_status != 'valid' ) {
642 wp_die( 'An active Profile Builder license is required to create new posts of this type.' );
643 }
644
645 }
646
647 return $data;
648 }
649 add_filter( 'wp_insert_post_data', 'wppb_filter_own_post_creation', 10, 2 );
650
651 add_filter( 'wck_update_meta_filter_values_wppb_manage_fields', 'wppb_filter_extra_manage_fields_options', 10, 2 );
652 function wppb_filter_extra_manage_fields_options( $values, $element_id ) {
653
654 $license_status = wppb_get_serial_number_status();
655
656 if( $license_status == 'valid' )
657 return $values;
658
659 if( !empty( $values['id'] ) ) {
660 $manage_fields = get_option( 'wppb_manage_fields', array() );
661 $existing_field = false;
662
663 if( !empty( $manage_fields ) ) {
664 foreach( $manage_fields as $field ) {
665 if( $field['id'] == $values['id'] ) {
666 $existing_field = $field;
667 break;
668 }
669 }
670 }
671
672 if( !empty( $existing_field ) ) {
673
674 $target_keys = [
675 'visibility',
676 'location-visibility',
677 'user-role-visibility',
678 'conditional-logic-enabled',
679 'edit-profile-approved-by-admin',
680 ];
681
682 foreach( $target_keys as $key ) {
683 if( !empty( $existing_field[$key] ) ) {
684 $values[$key] = $existing_field[$key];
685 }
686 }
687
688 }
689
690 }
691
692 return $values;
693
694 }
695
696 function wppb_form_builder_transition_admin_notification() {
697
698 if( !current_user_can( 'manage_options' ) )
699 return;
700
701 /* don't advertise the Form Builder unless it actually loaded */
702 if( !function_exists( 'wppb_fb_loaded' ) )
703 return;
704
705 /* initiate the plugin notifications class */
706 $notifications = WPPB_Plugin_Notifications::get_instance();
707 /* this must be unique */
708 $notification_id = 'wppb_form_builder_transition_notification';
709
710 $forms_url = admin_url( 'edit.php?post_type=wppb-rf-cpt' );
711
712 /* Filterable so the documentation link can be pointed at the Form Builder
713 * article without a code change. */
714 $docs_url = apply_filters( 'wppb_fb_transition_notice_docs_url', 'https://www.cozmoslabs.com/docs/profile-builder/manage-user-fields/?utm_source=wpbackend&utm_medium=clientsite&utm_content=form_builder_transition_notification&utm_campaign=PBDocs' );
715
716 $notification_message = '<p style="font-size: 15px; margin-top:4px;">' . esc_html__( 'Registration and Edit Profile forms are now built with a drag-and-drop editor: pick fields from the inserter, arrange them on the canvas, and edit each one in place.', 'profile-builder' ) . '</p>';
717
718 $notification_message .= '<p style="font-size: 15px; margin-top:4px;">' . esc_html__( 'Your existing forms and fields carried over automatically, and your live forms keep rendering exactly as before.', 'profile-builder' ) . '</p>';
719
720 $notification_message .= '<p style="font-size: 15px; margin-top:4px;">' . wp_kses_post( sprintf(
721 /* translators: 1: opening Forms link (HTML), 2: closing Forms link, 3: opening documentation link (HTML), 4: closing documentation link */
722 __( 'Open %1$sProfile Builder → Forms%2$s to take a look, or %3$sread the documentation%4$s.', 'profile-builder' ),
723 '<a href="' . esc_url( $forms_url ) . '">',
724 '</a>',
725 '<a href="' . esc_url( $docs_url ) . '" target="_blank" rel="noopener noreferrer">',
726 '</a>'
727 ) ) . '</p>';
728
729 $pb_icon_url = ( file_exists( WPPB_PLUGIN_DIR . 'assets/images/pb-logo.svg' ) ) ? WPPB_PLUGIN_URL . 'assets/images/pb-logo.svg' : '';
730 $pb_icon = ( !empty( $pb_icon_url ) ) ? '<img src="' . esc_url( $pb_icon_url ) . '" width="64" height="64" style="float: left; margin: 15px 12px 15px 0; max-width: 100px;" alt="Profile Builder">' : '';
731
732 $message = $pb_icon;
733 $message .= '<div style="overflow: hidden;">';
734 $message .= '<h3 style="margin-bottom: 0;">' . esc_html__( 'Introducing the new Profile Builder Form Builder.', 'profile-builder' ) . '</h3>';
735 $message .= $notification_message;
736 $message .= '</div>';
737 $message .= '<a href="' . esc_url( wp_nonce_url( add_query_arg( array( 'wppb_dismiss_admin_notification' => $notification_id ) ), 'wppb_plugin_notice_dismiss' ) ) . '" type="button" class="notice-dismiss"><span class="screen-reader-text">' . esc_html__( 'Dismiss this notice.', 'profile-builder' ) . '</span></a>';
738
739 $notifications->add_marketing_notification( $notification_id, $message, 'wppb-notice notice notice-info', true, array( 'edit.php?post_type=wppb-rf-cpt' ) );
740
741 }
742 add_action( 'admin_init', 'wppb_form_builder_transition_admin_notification' );
743
744 /**
745 * Get the Profile Builder Page or Post slug
746 *
747 */
748 function wppb_get_pb_page_post_slug() {
749 if ( isset( $_GET['post_type'] ) )
750 $post_type = sanitize_text_field( $_GET['post_type'] );
751 elseif ( isset( $_GET['post'] ) )
752 $post_type = get_post_type( (int)$_GET['post'] );
753 elseif ( isset( $_GET['page'] ) )
754 $post_type = sanitize_text_field( $_GET['page'] );
755 else $post_type = '';
756
757 $possible_slugs = array(
758 'pb',
759 'wppb',
760 'profile-builder',
761 'user-email-customizer',
762 'admin-email-customizer',
763 'manage-fields',
764 'custom-redirects',
765 'profile-user-profile-picture'
766 );
767
768 if ( !empty( $post_type ) ) {
769 foreach ( $possible_slugs as $slug ) {
770 if ( ($post_type === $slug || strpos( $post_type, $slug ) === 0) && strpos($post_type, 'pb_backupbuddy') === false ) {
771
772 return $post_type;
773
774 }
775 }
776 }
777
778 return '';
779 }
780
781 /**
782 * Insert the PB Admin area Header Banner
783 *
784 */
785 function wppb_insert_page_banner() {
786 $pb_slug = wppb_get_pb_page_post_slug();
787
788 if ( $pb_slug === 'profile-builder-dashboard' && isset( $_GET['subpage'] ) && $_GET['subpage'] === 'wppb-setup' )
789 return;
790
791 $page_name = '';
792 if ( $pb_slug == 'profile-builder-add-ons' )
793 $page_name = ' Add-Ons';
794
795 if ( !empty( $pb_slug ) )
796 wppb_output_page_banner($page_name);
797
798 }
799 add_action( 'in_admin_header', 'wppb_insert_page_banner' );
800
801 /**
802 * Output the PB Admin area Header Banner content
803 *
804 */
805 function wppb_output_page_banner( $page_name ) {
806
807 $upgrade_button = '<a class="cozmoslabs-banner-link cozmoslabs-upgrade-link" href="https://www.cozmoslabs.com/wordpress-profile-builder/?utm_source=pb-settings&utm_medium=client-site&utm_campaign=pb-header-upsell#pricing" target="_blank">
808 <img src="'. esc_url(WPPB_PLUGIN_URL) . 'assets/images/upgrade-link-icon.svg" alt="">
809 Upgrade to PRO
810 </a>';
811
812 $upgrade_button_basic = '<a class="cozmoslabs-banner-link cozmoslabs-upgrade-link" href="https://www.cozmoslabs.com/wordpress-profile-builder/?utm_source=pb-settings&utm_medium=client-site&utm_campaign=pb-header-upsell#pricing" target="_blank">
813 <img src="'. esc_url(WPPB_PLUGIN_URL) . 'assets/images/upgrade-link-icon.svg" alt="">
814 Upgrade to PRO
815 </a>';
816
817 $support_url = 'https://wordpress.org/support/plugin/profile-builder/';
818
819 $output = '<div class="cozmoslabs-banner">
820 <div class="cozmoslabs-banner-title">
821 <img src="'. esc_url(WPPB_PLUGIN_URL) . 'assets/images/pb-logo.svg" alt="">
822 <h4>Profile Builder'. $page_name .'</h4>
823 </div>
824 <div class="cozmoslabs-banner-buttons">
825 <a class="cozmoslabs-banner-link cozmoslabs-support-link" href="'. $support_url .'" target="_blank">
826 <img src="'. esc_url(WPPB_PLUGIN_URL) . 'assets/images/support-link-icon.svg" alt="">
827 Support
828 </a>
829
830 <a class="cozmoslabs-banner-link cozmoslabs-documentation-link" href="https://www.cozmoslabs.com/docs/profile-builder/?utm_source=pb-settings&utm_medium=client-site&utm_campaign=pb-header-upsell" target="_blank">
831 <img src="'. esc_url(WPPB_PLUGIN_URL) . 'assets/images/docs-link-icon.svg" alt="">
832 Documentation
833 </a>';
834
835 if ( !defined( 'WPPB_PAID_PLUGIN_DIR' ) || ( defined( 'PROFILE_BUILDER_PAID_VERSION' ) && PROFILE_BUILDER_PAID_VERSION === 'dev' ) )
836 $output .= $upgrade_button;
837
838 // Add Basic version upgrade button (not to account, to plugin purchase page)
839 if( defined( 'PROFILE_BUILDER' ) && PROFILE_BUILDER == 'Profile Builder Basic' ){
840 $output .= $upgrade_button_basic;
841 }
842
843 $output .= ' </div>
844 </div>';
845
846 echo $output; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
847 }
848
849
850 /**
851 * Remove PMS Styles from PB Pages and Posts
852 *
853 */
854 function wppb_maybe_remove_pms_styles() {
855 if ( !empty( wppb_get_pb_page_post_slug() ) && wp_style_is('pms-style-back-end', 'enqueued') ) {
856 wp_dequeue_style('pms-style-back-end');
857 }
858 }
859 add_action('admin_enqueue_scripts', 'wppb_maybe_remove_pms_styles', 100);
860
861
862 /**
863 * Output the deactivation confirmation popup on the Plugins page
864 *
865 */
866 function wppb_output_deactivation_popup() {
867
868 if ( defined( 'WPPB_PAID_PLUGIN_DIR' ) )
869 return;
870
871 $screen = get_current_screen();
872
873 if ( empty( $screen ) )
874 return;
875
876 $is_plugins_screen = in_array( $screen->base, array( 'plugins', 'plugins-network' ), true ) || in_array( $screen->id, array( 'plugins', 'plugins-network' ), true );
877
878 if ( !$is_plugins_screen )
879 return;
880
881 ?>
882 <div id="wppb-deactivation-popup" title="<?php esc_attr_e( 'Before You Go', 'profile-builder' ); ?>" data-plugin="<?php echo esc_attr( WPPB_PLUGIN_BASENAME ); ?>">
883 <div class="wppb-deactivation-popup-header">
884 <img src="<?php echo esc_url( WPPB_PLUGIN_URL . 'assets/images/pb-logo.svg' ); ?>" alt="<?php esc_attr_e( 'Profile Builder', 'profile-builder' ); ?>" width="44" height="44">
885
886 <p class="wppb-deactivation-popup-description">
887 <?php esc_html_e( 'If you have a moment, please share the reason you are deactivating Profile Builder:', 'profile-builder' ); ?>
888 </p>
889 </div>
890
891 <form class="wppb-deactivation-popup-form">
892 <fieldset class="wppb-deactivation-popup-fieldset">
893 <label class="wppb-deactivation-popup-option">
894 <input type="radio" name="wppb_deactivation_reason" value="dont_need_it">
895 <span><?php esc_html_e( 'I no longer need the plugin', 'profile-builder' ); ?></span>
896 </label>
897
898 <label class="wppb-deactivation-popup-option">
899 <input type="radio" name="wppb_deactivation_reason" value="switched_to_another_plugin">
900 <span><?php esc_html_e( 'I found a better plugin', 'profile-builder' ); ?></span>
901 <input type="text" name="switched_to_another_plugin_reason" class="wppb-deactivation-popup-extra" data-reason="switched_to_another_plugin" placeholder="<?php esc_attr_e( 'Which plugin', 'profile-builder' ); ?>">
902 </label>
903
904 <label class="wppb-deactivation-popup-option">
905 <input type="radio" name="wppb_deactivation_reason" value="missing_features">
906 <span><?php esc_html_e( 'I didn\'t find the feature I need', 'profile-builder' ); ?></span>
907 <input type="text" name="missing_features_reason" class="wppb-deactivation-popup-extra" data-reason="missing_features" placeholder="<?php esc_attr_e( 'Which feature', 'profile-builder' ); ?>">
908 </label>
909
910 <label class="wppb-deactivation-popup-option">
911 <input type="radio" name="wppb_deactivation_reason" value="did_not_work">
912 <span><?php esc_html_e( 'I couldn\'t get the plugin to work', 'profile-builder' ); ?></span>
913 </label>
914
915 <label class="wppb-deactivation-popup-option">
916 <input type="radio" name="wppb_deactivation_reason" value="temporary_deactivation">
917 <span><?php esc_html_e( 'Temporary deactivation', 'profile-builder' ); ?></span>
918 </label>
919
920 <label class="wppb-deactivation-popup-option">
921 <input type="radio" name="wppb_deactivation_reason" value="other">
922 <span><?php esc_html_e( 'Other', 'profile-builder' ); ?></span>
923 <input type="text" name="other_reason" class="wppb-deactivation-popup-extra" data-reason="other" placeholder="<?php esc_attr_e( 'Please tell us more', 'profile-builder' ); ?>">
924 </label>
925 </fieldset>
926
927 <p class="wppb-deactivation-popup-error"></p>
928 </form>
929
930 <div class="wppb-deactivation-popup-actions">
931 <button type="button" class="button button-primary wppb-deactivation-popup-confirm">
932 <?php esc_html_e( 'Submit & deactivate', 'profile-builder' ); ?>
933 </button>
934
935 <button type="button" class="button wppb-deactivation-popup-skip">
936 <?php esc_html_e( 'Skip & deactivate', 'profile-builder' ); ?>
937 </button>
938 </div>
939 </div>
940 <style>
941 #wppb-deactivation-popup {
942 display: none;
943 }
944
945 .wppb-deactivation-popup-header {
946 display: flex;
947 align-items: center;
948 gap: 16px;
949 margin-bottom: 20px;
950 }
951
952 .wppb-deactivation-popup-description {
953 margin: 0;
954 }
955
956 .wppb-deactivation-popup-fieldset {
957 margin: 0;
958 padding: 0;
959 border: 0;
960 }
961
962 .wppb-deactivation-popup-option {
963 display: block;
964 margin-bottom: 12px;
965 }
966
967 .wppb-deactivation-popup-extra {
968 display: none;
969 width: 100%;
970 margin-top: 8px;
971 }
972
973 .wppb-deactivation-popup-error {
974 display: none;
975 color: #b32d2e;
976 margin: 0px 0px 12px 0px;
977 }
978
979 .wppb-deactivation-popup-actions {
980 display: flex;
981 align-items: center;
982 justify-content: space-between;
983 gap: 12px;
984 }
985
986 .wppb-deactivation-popup-extra.error {
987 border: 1px solid #b32d2e;
988 }
989
990 .ui-dialog[aria-describedby="wppb-deactivation-popup"] .ui-dialog-titlebar {
991 background: transparent;
992 border: none;
993 }
994 </style>
995 <?php
996 }
997 add_action( 'admin_footer', 'wppb_output_deactivation_popup' );
998
999
1000 /**
1001 * Check if current screen is a Profile Builder admin page.
1002 *
1003 * @return bool
1004 */
1005 function wppb_is_profile_builder_admin_page() {
1006
1007 if ( ! is_admin() )
1008 return false;
1009
1010 $screen = get_current_screen();
1011
1012 if ( ! $screen )
1013 return false;
1014
1015 $pb_pages = array(
1016 'profile-builder',
1017 'profile-builder-dashboard',
1018 'profile-builder-basic-info',
1019 'profile-builder-general-settings',
1020 'profile-builder-add-ons',
1021 'manage-fields',
1022 );
1023
1024 foreach ( $pb_pages as $page ) {
1025
1026 if ( strpos( $screen->id, $page ) !== false )
1027 return true;
1028
1029 }
1030
1031 if ( ! empty( $screen->post_type ) ) {
1032 $pb_cpt_pages = array( 'wppb-ul-cpt', 'wppb-rf-cpt', 'wppb-epf-cpt' );
1033
1034 if ( in_array( $screen->post_type, $pb_cpt_pages, true ) )
1035 return true;
1036 }
1037
1038 return false;
1039 }
1040
1041
1042 /**
1043 * Output the popup markup used for documentation links from the admin.
1044 *
1045 * @return void
1046 */
1047 function wppb_output_docs_link_popup() {
1048
1049 if ( ! wppb_is_profile_builder_admin_page() )
1050 return;
1051
1052 ?>
1053 <div id="wppb-docs-link-popup" class="wppb-docs-link-popup" title="<?php echo esc_attr__( 'Need Help?', 'profile-builder' ); ?>" style="display:none;">
1054 <div class="wppb-docs-link-popup-content">
1055 <img src="<?php echo esc_url( WPPB_PLUGIN_URL . 'assets/images/pb-logo.svg' ); ?>" alt="<?php esc_attr_e( 'Profile Builder', 'profile-builder' ); ?>" width="44" height="44" class="wppb-docs-link-popup-logo">
1056 <div>
1057 <p class="wppb-docs-link-popup-description"><?php esc_html_e( 'If you need a hand with this setting, you can check the documentation or open a support ticket on WordPress.org.', 'profile-builder' ); ?></p>
1058 <p class="wppb-docs-link-popup-description"><?php esc_html_e( 'We will do our best to help you figure it out.', 'profile-builder' ); ?></p>
1059 </div>
1060 </div>
1061 <div class="wppb-docs-link-popup-actions cozmoslabs-wrap">
1062 <a href="#" target="_blank" rel="noopener noreferrer" class="button button-primary wppb-docs-link-popup-open-docs"><?php esc_html_e( 'View Documentation', 'profile-builder' ); ?></a>
1063 <a href="https://wordpress.org/support/plugin/profile-builder/#new-topic-0" target="_blank" rel="noopener noreferrer" class="button button-primary wppb-docs-link-popup-open-wporg"><?php esc_html_e( 'Open Support Ticket', 'profile-builder' ); ?></a>
1064 </div>
1065 </div>
1066 <?php
1067 }
1068 add_action( 'admin_footer', 'wppb_output_docs_link_popup' );
1069