dvadf
File manager - Edit - /home/centroca/public_html/Admin.tar
Back
Education/StringsTrait.php 0000644 00000020072 15252506741 0011635 0 ustar 00 <?php namespace WPForms\Admin\Education; /** * Strings trait. * * @since 1.8.8 */ trait StringsTrait { /** * Localize common strings. * * @since 1.6.6 * * @return array */ protected function get_js_strings(): array { $strings = []; $name = '%name%'; $strings['ok'] = esc_html__( 'Ok', 'wpforms-lite' ); $strings['cancel'] = esc_html__( 'Cancel', 'wpforms-lite' ); $strings['close'] = esc_html__( 'Close', 'wpforms-lite' ); $strings['view_demo'] = esc_html__( 'View Demo', 'wpforms-lite' ); $strings['ajax_url'] = admin_url( 'admin-ajax.php' ); $strings['nonce'] = wp_create_nonce( 'wpforms-education' ); $strings['activate_prompt'] = '<p>' . esc_html( sprintf( /* translators: %s - addon name. */ __( 'The %s is installed but not activated. Would you like to activate it?', 'wpforms-lite' ), $name ) ) . '</p>'; $strings['activate_confirm'] = esc_html__( 'Yes, Activate', 'wpforms-lite' ); $strings['addon_activated'] = esc_html__( 'Addon activated', 'wpforms-lite' ); $strings['plugin_activated'] = esc_html__( 'Plugin activated', 'wpforms-lite' ); $strings['activating'] = esc_html__( 'Activating', 'wpforms-lite' ); $strings['install_prompt'] = '<p>' . esc_html( sprintf( /* translators: %s - addon name. */ __( 'The %s is not installed. Would you like to install and activate it?', 'wpforms-lite' ), $name ) ) . '</p>'; $strings['install_confirm'] = esc_html__( 'Yes, Install and Activate', 'wpforms-lite' ); $strings['installing'] = esc_html__( 'Installing', 'wpforms-lite' ); $strings['save_prompt'] = esc_html__( 'Almost done! Would you like to save and refresh the form builder?', 'wpforms-lite' ); $strings['save_confirm'] = esc_html__( 'Yes, save and refresh', 'wpforms-lite' ); $strings['saving'] = esc_html__( 'Saving ...', 'wpforms-lite' ); // Check if the user can install addons. // Includes license check. $can_install_addons = wpforms_can_install( 'addon' ); // Check if the user can install plugins. // Only checks if the user has the capability. // Needed to display the correct message for non-admin users. $can_install_plugins = current_user_can( 'install_plugins' ); $strings['can_install_addons'] = $can_install_addons && $can_install_plugins; if ( ! $can_install_addons ) { $strings['install_prompt'] = '<p>' . esc_html( sprintf( /* translators: %s - addon name. */ __( 'The %s is not installed. Please install and activate it to use this feature.', 'wpforms-lite' ), $name ) ) . '</p>'; } if ( ! $can_install_plugins ) { /* translators: %s - addon name. */ $strings['install_prompt'] = '<p>' . esc_html( sprintf( /* translators: %s - addon name. */ __( 'The %s is not installed. Please contact the site administrator.', 'wpforms-lite' ), $name ) ) . '</p>'; } // Check if the user can activate plugins. $can_activate_plugins = current_user_can( 'activate_plugins' ); $strings['can_activate_addons'] = $can_activate_plugins; if ( ! $can_activate_plugins ) { /* translators: %s - addon name. */ $strings['activate_prompt'] = '<p>' . esc_html( sprintf( __( 'The %s is not activated. Please contact the site administrator.', 'wpforms-lite' ), $name ) ) . '</p>'; } $upgrade_utm_medium = wpforms_is_admin_page() ? 'Settings - Integration' : 'Builder - Settings'; if ( wpforms_is_block_editor() ) { $upgrade_utm_medium = 'gutenberg'; } $strings['upgrade'] = [ 'pro' => $this->get_upgrade_strings( 'Pro', $name, $upgrade_utm_medium ), 'elite' => $this->get_upgrade_strings( 'Elite', $name, $upgrade_utm_medium ), ]; // Shared bonus body — reused by `upgrade_bonus` and `upgrade_banner.bonus` // so translators see this sentence exactly once. $bonus_body = __( 'WPForms Lite users get <span>50% off</span> regular price, automatically applied at checkout.', 'wpforms-lite' ); $strings['upgrade_bonus'] = wpautop( wp_kses( '<strong>' . esc_html__( 'Bonus:', 'wpforms-lite' ) . '</strong> ' . $bonus_body, [ 'strong' => [], 'span' => [], ] ) ); $activate_license_link = sprintf( '<a href="%1$s" class="js-activate-license">%2$s</a>', esc_url( admin_url( 'admin.php?page=wpforms-settings' ) ), esc_html__( 'Activate your license', 'wpforms-lite' ) ); // Banner upgrade modal copy — activated when the trigger carries `data-banner-src`. // The activate anchor's `href="#"` is a placeholder; JS overwrites it at runtime. $strings['upgrade_banner'] = [ 'message' => esc_html__( 'Understand how your forms are performing with detailed analytics on views, conversions, and field-level activity, so you can identify friction and improve completion rates.', 'wpforms-lite' ), 'button' => esc_html__( 'Upgrade to WPForms Pro', 'wpforms-lite' ), 'bonus' => wp_kses( '<strong>' . esc_html__( 'Bonus!', 'wpforms-lite' ) . '</strong> ' . $bonus_body . '<span class="banner-activate">' . sprintf( /* translators: %s - "Activate your license" link HTML. */ __( 'Already purchased? %s to unlock this feature.', 'wpforms-lite' ), $activate_license_link ) . '</span>', [ 'strong' => [], 'span' => [ 'class' => [] ], 'a' => [ 'href' => [], 'class' => [], ], ] ), ]; $strings['thanks_for_interest'] = esc_html__( 'Thanks for your interest in WPForms Pro!', 'wpforms-lite' ); /** * Filters the education strings. * * @since 1.6.6 * * @param array $strings Education strings. * * @return array */ return (array) apply_filters( 'wpforms_admin_education_strings', $strings ); } /** * Get upgrade strings. * * @since 1.8.8 * * @param string $level Upgrade level. * @param string $name Addon name. * @param string $upgrade_utm_medium UTM medium for the upgrade link. * * @return array * @noinspection HtmlUnknownTarget */ private function get_upgrade_strings( string $level, string $name, string $upgrade_utm_medium ): array { // phpcs:ignore WPForms.Formatting.EmptyLineAfterFunctionDeclaration.AddEmptyLineAfterFunctionDeclaration return [ 'title' => esc_html( sprintf( /* translators: %s - level name, either Pro or Elite. */ __( 'is a %s Feature', 'wpforms-lite' ), $level ) ), 'title_plural' => esc_html( sprintf( /* translators: %s - level name, either Pro or Elite. */ __( 'are a %s Feature', 'wpforms-lite' ), $level ) ), 'message' => '<p>' . esc_html( sprintf( /* translators: %1$s - addon name, %2$s - level name, either Pro or Elite. */ __( 'We\'re sorry, the %1$s is not available on your plan. Please upgrade to the %2$s plan to unlock all these awesome features.', 'wpforms-lite' ), $name, $level ) ) . '</p>', 'message_plural' => '<p>' . esc_html( sprintf( /* translators: %1$s - addon name, %2$s - level name, either Pro or Elite. */ __( 'We\'re sorry, %1$s are not available on your plan. Please upgrade to the %2$s plan to unlock all these awesome features.', 'wpforms-lite' ), $name, $level ) ) . '</p>', 'doc' => sprintf( '<a href="%1$s" target="_blank" rel="noopener noreferrer" class="already-purchased">%2$s</a>', esc_url( wpforms_utm_link( 'https://wpforms.com/docs/upgrade-wpforms-lite-paid-license/#installing-wpforms', $upgrade_utm_medium, '%name%' ) ), esc_html__( 'Already purchased?', 'wpforms-lite' ) ), 'button' => esc_html( sprintf( /* translators: %s - level name, either Pro or Elite. */ __( 'Upgrade to %s', 'wpforms-lite' ), $level ) ), 'url' => wpforms_admin_upgrade_link( $upgrade_utm_medium ), 'url_template' => wpforms_is_admin_page( 'templates' ) ? wpforms_admin_upgrade_link( 'Form Templates Subpage' ) : wpforms_admin_upgrade_link( 'builder-modal-template' ), 'url_themes' => wpforms_admin_upgrade_link( 'Builder Themes' ), 'modal' => wpforms_get_upgrade_modal_text( strtolower( $level ) ), ]; } } Education/AddonsItemBase.php 0000644 00000006751 15252506741 0012032 0 ustar 00 <?php namespace WPForms\Admin\Education; use WPForms\Admin\Addons\Addons; use WPForms\Requirements\Requirements; /** * Base class for all "addon item" type Education features. * * @since 1.6.6 */ abstract class AddonsItemBase implements EducationInterface { /** * Instance of the Education\Core class. * * @since 1.6.6 * * @var Core */ protected $education; /** * Instance of the Education\Addons class. * * @since 1.6.6 * * @var Addons */ protected $addons; /** * Template name for rendering single addon item. * * @since 1.6.6 * * @var string */ protected $single_addon_template; /** * Indicate if the current Education feature is allowed to load. * Should be called from the child feature class. * * @since 1.6.6 * * @return bool * * @noinspection PhpMissingReturnTypeInspection * @noinspection ReturnTypeCanBeDeclaredInspection */ abstract public function allow_load(); /** * Init. * * @since 1.6.6 * * @noinspection ReturnTypeCanBeDeclaredInspection */ public function init() { if ( ! $this->allow_load() ) { return; } // Store the instance of the Education core class. $this->education = wpforms()->obj( 'education' ); // Store the instance of the Education\Addons class. $this->addons = wpforms()->obj( 'addons' ); // Define hooks. $this->hooks(); } /** * Hooks. * * @since 1.6.6 */ abstract public function hooks(); /** * Display single addon item. * * @since 1.6.6 * * @param array $addon Addon data. * * @noinspection ReturnTypeCanBeDeclaredInspection * @noinspection PhpMissingParamTypeInspection */ protected function display_single_addon( $addon ) { /** * Filter to disallow addons to be displayed in the Education feature. * * @since 1.8.2 * * @param bool $display Whether to hide the addon. * @param array $slug Addon data. */ $is_disallowed = (bool) apply_filters( 'wpforms_admin_education_addons_item_base_display_single_addon_hide', false, $addon ); if ( empty( $addon ) || $is_disallowed ) { return; } echo wpforms_render( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped $this->single_addon_template, $addon, true ); } /** * Prepare field data-attributes for the education actions. * E.g., install, activate, incompatible. * * @since 1.9.4 * * @param array $addon Current addon information. * * @return array */ protected function prepare_field_action_data( array $addon ): array { if ( empty( $addon['plugin_allow'] ) ) { return []; } if ( $addon['action'] === 'install' ) { return [ 'data' => [ 'action' => 'install', 'name' => $addon['modal_name'], 'url' => $addon['url'], 'nonce' => wp_create_nonce( 'wpforms-admin' ), 'license' => $addon['license_level'], ], 'class' => 'education-modal', ]; } if ( $addon['action'] === 'activate' ) { return [ 'data' => [ 'action' => 'activate', 'name' => sprintf( /* translators: %s - addon name. */ esc_html__( '%s addon', 'wpforms-lite' ), $addon['name'] ), 'path' => $addon['path'], 'nonce' => wp_create_nonce( 'wpforms-admin' ), ], 'class' => 'education-modal', ]; } if ( $addon['action'] === 'incompatible' ) { return [ 'data' => [ 'action' => 'incompatible', 'message' => Requirements::get_instance()->get_notice( $addon['path'] ), ], 'class' => 'education-modal', ]; } return []; } } Education/Builder/PDF.php 0000644 00000012557 15252506741 0011210 0 ustar 00 <?php namespace WPForms\Admin\Education\Builder; use WPForms\Admin\Education\Helpers; /** * PDF educational popup. * * @since 1.9.7.3 */ class PDF { /** * Addon slug. * * @since 1.9.7.3 * * @var string */ private $slug = 'wpforms-pdf'; /** * Addon data. * * @since 1.9.7.3 * * @var array */ private $addon_data; /** * Initialize. * * @since 1.9.7.3 */ public function init(): void { $this->addon_data = $this->get_addon_data(); if ( ! $this->should_show_popup() ) { return; } $this->hooks(); } /** * Should show popup. * * @since 1.9.7.3 * * @return bool */ private function should_show_popup(): bool { if ( ! wpforms_is_admin_page( 'builder' ) && ! wpforms_is_admin_ajax() ) { return false; } if ( ! current_user_can( wpforms_get_capability_manage_options() ) ) { return false; } $challenge = wpforms()->obj( 'challenge' ); if ( ! $challenge || $challenge->challenge_active() ) { return false; } return $this->is_popup_visible(); } /** * Is popup visible. * * @since 1.9.7.3 * * @return bool */ private function is_popup_visible(): bool { $action = $this->addon_data['action'] ?? 'install'; if ( empty( $this->addon_data ) || ( $action === 'install' && empty( $this->addon_data['url'] ) ) // The install action requires a valid URL. ) { return false; } $meta = get_user_meta( get_current_user_id(), 'wpforms_dismissed', true ); return empty( $meta['edu-builder-pdf'] ); } /** * Hooks. * * @since 1.9.7.3 */ private function hooks(): void { add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] ); add_filter( 'wpforms_builder_output_before_toolbar', [ $this, 'popup_html' ] ); } /** * Enqueue scripts. * * @since 1.9.7.3 */ public function enqueue_scripts(): void { $min = wpforms_get_min_suffix(); wp_enqueue_script( 'wpforms-pdf-popup', WPFORMS_PLUGIN_URL . "assets/js/admin/education/pdf$min.js", [], WPFORMS_VERSION, true ); } /** * Popup HTML. * * @since 1.9.7.3 * * @param string|mixed $html HTML. * * @return string * @noinspection HtmlUnknownTarget */ public function popup_html( $html ): string { $html = (string) $html; $popup = sprintf( '<div id="wpforms-pdf-popup" class="wpforms-pdf-popup wpforms-hidden wpforms-dismiss-container" role="dialog" aria-modal="true" aria-labelledby="wpforms-pdf-popup-title" aria-describedby="wpforms-pdf-popup-description"> <div class="wpforms-pdf-popup-content"> <div class="icon"> <img src="%1$s" alt="PDF Icon"> </div> <div class="close-popup wpforms-dismiss-button dashicons-no-alt" data-section="builder-pdf"></div> <div class="badge">%2$s</div> <h2 id="wpforms-pdf-popup-title">%3$s</h2> <p id="wpforms-pdf-popup-description">%4$s</p> %5$s </div> </div>', esc_url( WPFORMS_PLUGIN_URL . 'assets/images/pdf-education/pdf.svg' ), __( 'NEW FEATURE', 'wpforms-lite' ), __( 'PDF Addon', 'wpforms-lite' ), __( 'Easily turn form entry data into beautifully designed PDFs and attach them to notifications.', 'wpforms-lite' ), $this->get_button_html() ); return $popup . $html; } /** * Get button HTML. * * @since 1.9.7.3 * * @return string * @noinspection HtmlUnknownAttribute */ private function get_button_html(): string { $addon = $this->addon_data; $action = $addon['action'] ?? 'switch'; [ $button_label, $button_utm, $button_class, $button_attr ] = $this->get_button_data( $action, $addon ); return sprintf( '<button class="wpforms-btn wpforms-btn-sm wpforms-btn-orange %1$s" data-action="%2$s" %4$s data-license="%5$s" data-utm-content="%6$s">%3$s</button>', esc_attr( $button_class ), esc_attr( $action ), esc_html( $button_label ), $button_attr, esc_attr( $addon['license_level'] ?? 'pro' ), esc_attr( $button_utm ) ); } /** * Get addon data. * * @since 1.9.7.3 * * @return array */ private function get_addon_data(): array { /** * Filter the slug for the PDF educational popup. * * @since 1.9.7.3 * * @param string $slug The slug for the PDF educational popup. */ $slug = apply_filters( 'wpforms_admin_education_builder_pdf_get_addon_data_slug', $this->slug ); $addons = Helpers::get_edu_addons(); return $addons[ $slug ] ?? []; } /** * Get button data. * * @since 1.9.7.3 * * @param string $action Action type (switch, upgrade, etc.). * @param array $addon Addon data. * * @return array */ protected function get_button_data( string $action, array $addon ): array { $button_label = $action === 'upgrade' ? esc_html__( 'Upgrade to Pro', 'wpforms-lite' ) : esc_html__( 'Try it Out', 'wpforms-lite' ); $button_utm = 'PDF Addon Pop-up'; $button_class = 'education-action-button'; $button_attr = ''; if ( $action === 'switch' ) { $button_class = 'education-modal education-switch-button'; $button_attr = 'data-target="wpforms-pdf"'; } elseif ( $action !== 'upgrade' ) { $button_class = 'education-modal'; $button_attr = sprintf( 'data-nonce="%1$s" data-path="%2$s" data-url="%3$s" data-message="" data-name="%4$s"', esc_attr( wp_create_nonce( 'wpforms-admin' ) ), $addon['path'] ?? '', $addon['url'] ?? '', esc_html__( 'WPForms PDF Addon', 'wpforms-lite' ) ); } return [ $button_label, $button_utm, $button_class, $button_attr ]; } } Education/Builder/Panel.php 0000644 00000002421 15252506741 0011623 0 ustar 00 <?php namespace WPForms\Admin\Education\Builder; use \WPForms\Admin\Education\AddonsListBase; /** * Base class for Builder/Settings, Builder/Providers, Builder/Payments Education features. * * @since 1.6.6 */ abstract class Panel extends AddonsListBase { /** * Panel slug. Should be redefined in the real Builder/Panel class. * * @since 1.6.6 * * @return string **/ abstract protected function get_name(); /** * Indicate if current Education feature is allowed to load. * * @since 1.6.6 * * @return bool */ public function allow_load() { // Load only in the Form Builder. return wpforms_is_admin_page( 'builder' ) && ! empty( $this->get_name() ); } /** * Get addons for the current panel. * * @since 1.6.6 */ protected function get_addons() { return $this->addons->get_by_path( 'form_builder.category', $this->get_name() ); } /** * Template name for rendering single addon item. * * @since 1.6.6 * * @return string */ protected function get_single_addon_template() { return 'education/builder/' . $this->get_name() . '-item'; } /** * Display addons. * * @since 1.6.6 */ public function display_addons() { $this->single_addon_template = $this->get_single_addon_template(); parent::display_addons(); } } Education/Builder/Providers.php 0000644 00000002630 15252506741 0012543 0 ustar 00 <?php namespace WPForms\Admin\Education\Builder; use \WPForms\Admin\Education; /** * Builder/Providers Education feature. * * @since 1.6.6 */ class Providers extends Education\Builder\Panel { /** * Panel slug. * * @since 1.6.6 * * @return string **/ protected function get_name() { return 'providers'; } /** * Hooks. * * @since 1.6.6 */ public function hooks() { add_action( 'wpforms_providers_panel_sidebar', [ $this, 'filter_addons' ], 1 ); add_action( 'wpforms_providers_panel_sidebar', [ $this, 'display_addons' ], 500 ); } /** * Ensure that we do not display activated addon items if those addons are not allowed according to the current license. * * @since 1.6.6 */ public function filter_addons() { $this->filter_not_allowed_addons( 'wpforms_providers_panel_sidebar' ); } /** * Get addons for the Marketing panel. * * @since 1.7.7.2 */ protected function get_addons() { $addons = parent::get_addons(); /** * Google Sheets uses Providers API. All providers are automatically * added to the Marketing tab in the builder. We don't need the addon * on the Marketing tab because the addon is already added to * the builder's Settings tab. */ foreach ( $addons as $key => $addon ) { if ( isset( $addon['slug'] ) && $addon['slug'] === 'wpforms-google-sheets' ) { unset( $addons[ $key ] ); break; } } return $addons; } } Education/Builder/Geolocation.php 0000644 00000006633 15252506741 0013040 0 ustar 00 <?php namespace WPForms\Admin\Education\Builder; use WPForms\Admin\Education\AddonsItemBase; use WPForms\Admin\Education\Helpers; /** * Builder/Geolocation Education feature for Lite and Pro. * * @since 1.6.6 */ class Geolocation extends AddonsItemBase { /** * Indicate if the current Education feature is allowed to load. * * @since 1.6.6 * * @return bool * * @noinspection ReturnTypeCanBeDeclaredInspection * @noinspection PhpMissingReturnTypeInspection */ public function allow_load() { return wpforms_is_admin_page( 'builder' ) || wp_doing_ajax(); } /** * Hooks. * * @since 1.6.6 * * @noinspection ReturnTypeCanBeDeclaredInspection */ public function hooks() { add_action( 'wpforms_field_options_bottom_advanced-options', [ $this, 'geolocation_options' ], 10, 2 ); } /** * Display geolocation options. * * @since 1.6.6 * * @param array $field Field data. * @param object $instance Builder instance. * * @noinspection ReturnTypeCanBeDeclaredInspection * @noinspection PhpMissingParamTypeInspection */ public function geolocation_options( $field, $instance ) { if ( ! in_array( $field['type'], [ 'text', 'address' ], true ) ) { return; } $addon = $this->addons->get_addon( 'geolocation' ); if ( empty( $addon ) || empty( $addon['action'] ) || empty( $addon['status'] ) || ( $addon['status'] === 'active' && $addon['action'] !== 'upgrade' ) ) { return; } $row_args = $this->get_address_autocomplete_row_attributes( $addon ); $row_args['content'] = $instance->field_element( 'toggle', $field, $this->get_address_autocomplete_field_attributes( $addon ), false ); $instance->field_element( 'row', $field, $row_args ); } /** * Get attributes for address autocomplete row. * * @since 1.6.6 * * @param array $addon Current addon information. * * @return array */ private function get_address_autocomplete_row_attributes( array $addon ): array { $data = $this->prepare_field_action_data( $addon ); $default = [ 'slug' => 'enable_address_autocomplete', ]; if ( ! empty( $data ) ) { return wp_parse_args( $data, $default ); } return wp_parse_args( [ 'data' => [ 'action' => 'upgrade', 'name' => esc_html__( 'Address Autocomplete', 'wpforms-lite' ), 'utm-content' => 'Address Autocomplete', 'licence' => 'pro', 'message' => esc_html__( 'We\'re sorry, Address Autocomplete is part of the Geolocation Addon and not available on your plan. Please upgrade to the PRO plan to unlock all these awesome features.', 'wpforms-lite' ), ], 'class' => 'education-modal', ], $default ); } /** * Get attributes for address autocomplete field. * * @since 1.6.6 * * @param array $addon Current addon information. * * @return array */ private function get_address_autocomplete_field_attributes( array $addon ): array { $default = [ 'slug' => 'enable_address_autocomplete', 'value' => '0', 'desc' => esc_html__( 'Enable Address Autocomplete', 'wpforms-lite' ), ]; if ( $addon['plugin_allow'] ) { return $default; } return wp_parse_args( [ 'desc' => sprintf( '%1$s%2$s', esc_html__( 'Enable Address Autocomplete', 'wpforms-lite' ), Helpers::get_badge( 'Pro', 'sm', 'inline', 'slate' ) ), 'attrs' => [ 'disabled' => 'disabled', ], ], $default ); } } Education/Builder/Gdpr.php 0000644 00000005165 15252506741 0011470 0 ustar 00 <?php namespace WPForms\Admin\Education\Builder; use WPForms\Education\WPConsent\Helper as WPConsent; /** * WPConsent cross-promo notice inside the GDPR Agreement field options. * * @since 2.0.0 */ class Gdpr { /** * Init. * * @since 2.0.0 */ public function init() { $this->hooks(); } /** * Register hooks. * * @since 2.0.0 */ private function hooks() { add_action( 'wpforms_field_options_bottom_basic-options', [ $this, 'maybe_render_notice' ], 10, 1 ); add_action( 'wpforms_builder_enqueues', [ $this, 'enqueue_assets' ] ); } /** * Whether the WPConsent notice should render. * * @since 2.0.0 * * @return bool */ private function should_render(): bool { return ! WPConsent::is_activated() && wpforms_can_install( 'plugin' ); } /** * Render the "Set Up Privacy Compliance" notice below Description for the * GDPR Agreement field only. * * @since 2.0.0 * * @param array $field Field data. */ public function maybe_render_notice( $field ) { if ( ! isset( $field['type'] ) || $field['type'] !== 'gdpr-checkbox' || ! $this->should_render() ) { return; } $data = WPConsent::get_install_button_data(); $attrs_html = ''; foreach ( $data['attrs'] as $key => $value ) { $attrs_html .= sprintf( ' %s="%s"', esc_attr( $key ), esc_attr( $value ) ); } printf( '<div class="wpforms-alert wpforms-alert-info wpforms-gdpr-wpconsent-notice"> <h4 class="wpforms-alert-heading">%1$s</h4> <p>%2$s</p> <button type="button" class="wpforms-btn wpforms-btn-sm wpforms-btn-blue wpforms-wpconsent-install" data-setup-url="%3$s" data-setup-text="%4$s"%5$s>%6$s</button> </div>', esc_html__( 'Set Up Privacy Compliance', 'wpforms-lite' ), esc_html__( 'Improve GDPR, CCPA, and privacy compliance with cookie banner and consent records.', 'wpforms-lite' ), esc_url( WPConsent::get_setup_url() ), esc_attr__( 'Set Up WPConsent', 'wpforms-lite' ), $attrs_html, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- each attr key/value escaped above. esc_html__( 'Install WPConsent', 'wpforms-lite' ) ); } /** * Enqueue the inline install handler in the builder. * * @since 2.0.0 */ public function enqueue_assets() { if ( ! $this->should_render() ) { return; } $min = wpforms_get_min_suffix(); wp_enqueue_script( 'wpforms-wpconsent-install', WPFORMS_PLUGIN_URL . "assets/js/admin/education/wpconsent-install{$min}.js", [ 'jquery' ], WPFORMS_VERSION, true ); wp_localize_script( 'wpforms-wpconsent-install', 'wpforms_wpconsent_install', [ 'ajax_url' => admin_url( 'admin-ajax.php' ) ] ); } } Education/Builder/Settings.php 0000644 00000002322 15252506741 0012364 0 ustar 00 <?php namespace WPForms\Admin\Education\Builder; use \WPForms\Admin\Education; /** * Builder/Settings Education feature. * * @since 1.6.6 */ class Settings extends Education\Builder\Panel { /** * Panel slug. * * @since 1.6.6 * * @return string **/ protected function get_name() { return 'settings'; } /** * Hooks. * * @since 1.6.6 */ public function hooks() { add_filter( 'wpforms_builder_settings_sections', [ $this, 'filter_addons' ], 1 ); add_action( 'wpforms_builder_after_panel_sidebar', [ $this, 'display' ], 100, 2 ); } /** * Display settings addons. * * @since 1.6.6 * * @param object $form Current form. * @param string $panel Panel slug. */ public function display( $form, $panel ) { if ( empty( $form ) || $this->get_name() !== $panel ) { return; } $this->display_addons(); } /** * Ensure that we do not display activated addon items if those addons are not allowed according to the current license. * * @since 1.6.6 * * @param array $sections Settings sections. * * @return array */ public function filter_addons( $sections ) { $this->filter_not_allowed_addons( 'wpforms_builder_settings_sections' ); return $sections; } } Education/Builder/Fields.php 0000644 00000003140 15252506741 0011771 0 ustar 00 <?php namespace WPForms\Admin\Education\Builder; use WPForms\Admin\Education\AddonsItemBase; use WPForms\Admin\Education\Fields as EducationFields; /** * Base class for Builder/Fields Education feature. * * @since 1.6.6 */ abstract class Fields extends AddonsItemBase { /** * Instance of the Education\Fields class. * * @since 1.6.6 * * @var EducationFields */ protected $fields; /** * Indicate if current Education feature is allowed to load. * * @since 1.6.6 * * @return bool */ public function allow_load(): bool { return wp_doing_ajax() || wpforms_is_admin_page( 'builder' ); } /** * Init. * * @since 1.6.6 */ public function init(): void { parent::init(); // Store the instance of the Education\Fields class. $this->fields = wpforms()->obj( 'education_fields' ); } /** * Print the form preview notice. * * @since 1.9.4 * * @param array $texts Notice texts. */ protected function print_form_preview_notice( $texts ): void { printf( '<div class="wpforms-alert %1$s wpforms-alert-dismissible wpforms-pro-fields-notice wpforms-dismiss-container"> <div class="wpforms-alert-message"> <h3>%2$s</h3> <p>%3$s</p> </div> <div class="wpforms-alert-buttons"> <button type="button" class="wpforms-dismiss-button" data-section="%4$s" title="%5$s" /> </div> </div>', esc_attr( $texts['class'] ), esc_html( $texts['title'] ), $texts['content'], // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped esc_html( $texts['dismiss_section'] ), esc_attr__( 'Dismiss this notice', 'wpforms-lite' ) ); } } Education/Builder/Captcha.php 0000644 00000017652 15252506741 0012143 0 ustar 00 <?php namespace WPForms\Admin\Education\Builder; use WPForms\Admin\Education\EducationInterface; use WPForms\Education\ActiveLayer\Helper as ActiveLayer; /** * Builder/ReCaptcha Education feature. * * @since 1.6.6 */ class Captcha implements EducationInterface { /** * Indicate if current Education feature is allowed to load. * * @since 1.6.6 */ public function allow_load() { return wp_doing_ajax(); } /** * Init. * * @since 1.6.6 */ public function init() { if ( ! $this->allow_load() ) { return; } // Define hooks. $this->hooks(); } /** * Hooks. * * @since 1.6.6 */ public function hooks() { add_action( 'wp_ajax_wpforms_update_field_captcha', [ $this, 'captcha_field_callback' ] ); } /** * Targeting on hCaptcha/reCAPTCHA field button in the builder. * * @since 1.6.6 */ public function captcha_field_callback() { // Run a security check. check_ajax_referer( 'wpforms-builder', 'nonce' ); // Check for form ID. if ( empty( $_POST['id'] ) ) { wp_send_json_error( esc_html__( 'No form ID found.', 'wpforms-lite' ) ); } $form_id = absint( $_POST['id'] ); // Check for permissions. if ( ! wpforms_current_user_can( 'edit_form_single', $form_id ) ) { wp_send_json_error( esc_html__( 'You do not have permission.', 'wpforms-lite' ) ); } // Get an actual form data. $form_data = wpforms()->obj( 'form' )->get( $form_id, [ 'content_only' => true ] ); // Check that CAPTCHA is configured in the settings. $captcha_settings = wpforms_get_captcha_settings(); $captcha_name = $this->get_captcha_name( $captcha_settings ); if ( empty( $form_data ) || empty( $captcha_name ) ) { wp_send_json_error( esc_html__( 'Something wrong. Please try again later.', 'wpforms-lite' ) ); } // Prepare a result array. $data = $this->get_captcha_result_mockup( $captcha_settings ); if ( empty( $captcha_settings['site_key'] ) || empty( $captcha_settings['secret_key'] ) ) { // If CAPTCHA is not configured in the WPForms plugin settings. $data['current'] = 'not_configured'; } elseif ( ! isset( $form_data['settings']['recaptcha'] ) || $form_data['settings']['recaptcha'] !== '1' ) { // If CAPTCHA is configured in WPForms plugin settings, but wasn't set in form settings. $data['current'] = 'configured_not_enabled'; } else { // If CAPTCHA is configured in WPForms plugin and form settings. $data['current'] = 'configured_enabled'; } wp_send_json_success( $data ); } /** * Retrieve the CAPTCHA name. * * @since 1.6.6 * * @param array $settings The CAPTCHA settings. * * @return string */ private function get_captcha_name( $settings ) { if ( empty( $settings['provider'] ) ) { return ''; } if ( empty( $settings['site_key'] ) && empty( $settings['secret_key'] ) ) { return esc_html__( 'CAPTCHA', 'wpforms-lite' ); } if ( $settings['provider'] === 'hcaptcha' ) { return esc_html__( 'hCaptcha', 'wpforms-lite' ); } if ( $settings['provider'] === 'turnstile' ) { return esc_html__( 'Cloudflare Turnstile', 'wpforms-lite' ); } $recaptcha_names = [ 'v2' => esc_html__( 'Google Checkbox v2 reCAPTCHA', 'wpforms-lite' ), 'invisible' => esc_html__( 'Google Invisible v2 reCAPTCHA', 'wpforms-lite' ), 'v3' => esc_html__( 'Google v3 reCAPTCHA', 'wpforms-lite' ), ]; return isset( $recaptcha_names[ $settings['recaptcha_type'] ] ) ? $recaptcha_names[ $settings['recaptcha_type'] ] : ''; } /** * Get CAPTCHA callback result mockup. * * @since 1.6.6 * * @param array $settings The CAPTCHA settings. * * @return array */ private function get_captcha_result_mockup( $settings ) { $captcha_name = $this->get_captcha_name( $settings ); if ( empty( $captcha_name ) ) { wp_send_json_error( esc_html__( 'Something wrong. Please, try again later.', 'wpforms-lite' ) ); } $not_configured_content = sprintf( wp_kses( /* translators: %1$s - CAPTCHA settings page URL, %2$s - WPForms.com doc URL. */ __( 'Please complete the setup in your <a href="%1$s" target="_blank">WPForms Settings</a>, and check out <a href="%2$s" target="_blank" rel="noopener noreferrer">our guide</a> to learn about available CAPTCHA solutions.', 'wpforms-lite' ), [ 'a' => [ 'href' => true, 'rel' => true, 'target' => true, ], ] ), esc_url( admin_url( 'admin.php?page=wpforms-settings&view=captcha' ) ), esc_url( wpforms_utm_link( 'https://wpforms.com/docs/setup-captcha-wpforms/', 'builder-modal', 'Captcha Documentation' ) ) ); // The success modal is only reachable through the footer's install CTA, // so only ship its data when the footer actually renders. $footer = $this->get_activelayer_footer_html(); return [ 'current' => false, 'cases' => [ 'not_configured' => [ 'title' => esc_html__( 'Heads up!', 'wpforms-lite' ), 'content' => $not_configured_content, 'footer' => $footer, 'success_modal' => $footer === '' ? null : $this->get_activelayer_success_modal_data(), ], 'configured_not_enabled' => [ 'title' => false, /* translators: %s - CAPTCHA name. */ 'content' => sprintf( esc_html__( '%s has been enabled for this form. Don\'t forget to save your form!', 'wpforms-lite' ), $captcha_name ), ], 'configured_enabled' => [ 'title' => false, /* translators: %s - CAPTCHA name. */ 'content' => sprintf( esc_html__( 'Are you sure you want to disable %s for this form?', 'wpforms-lite' ), $captcha_name ), 'cancel' => true, ], ], 'provider' => $settings['provider'], ]; } /** * ActiveLayer cross-promo footer for the captcha not_configured modal. * Empty when ActiveLayer is already active or the user cannot install plugins. * * @since 2.0.0 * * @return string */ private function get_activelayer_footer_html(): string { if ( ActiveLayer::is_activated() || ! wpforms_can_install( 'plugin' ) ) { return ''; } $modal = ActiveLayer::get_modal_data(); $attrs_html = ''; foreach ( $modal['attrs'] as $key => $value ) { $attrs_html .= sprintf( ' %s="%s"', esc_attr( $key ), esc_attr( $value ) ); } // Body copy with "ActiveLayer" linking to the marketing site in a new tab. $body = sprintf( wp_kses( /* translators: %1$s - ActiveLayer marketing site URL. */ __( 'Captchas can reduce form completions by up to 40%%. <a href="%1$s" target="_blank" rel="noopener noreferrer">ActiveLayer</a> catches bots invisibly, without asking your visitors to prove they\'re human.', 'wpforms-lite' ), [ 'a' => [ 'href' => true, 'target' => true, 'rel' => true, ], ] ), esc_url( wpforms_utm_link( 'https://activelayer.com/', 'builder-modal', 'ActiveLayer Captcha Footer' ) ) ); // The install CTA drives the bespoke flow in captcha.js (direct install, // no confirm modal); it deliberately does NOT use the `education-modal` class. return sprintf( '<div class="wpforms-captcha-activelayer-footer"><p>%1$s <a href="#" class="wpforms-captcha-activelayer-install"%2$s>%3$s</a></p></div>', $body, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped via wp_kses and esc_url. $attrs_html, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- attrs escaped above. esc_html__( 'Install Now', 'wpforms-lite' ) ); } /** * Copy and target for the "Installation Successful" confirmation modal * shown by captcha.js after ActiveLayer is installed from the footer CTA. * * @since 2.0.0 * * @return array */ private function get_activelayer_success_modal_data(): array { return [ 'title' => esc_html__( 'Installation Successful', 'wpforms-lite' ), 'content' => esc_html__( 'ActiveLayer has been installed and is ready to be configured. Would you like to set it up now?', 'wpforms-lite' ), 'setup_text' => esc_html__( 'Yes, Go to Setup', 'wpforms-lite' ), 'setup_url' => ActiveLayer::get_dashboard_url(), ]; } } Education/Builder/Quiz.php 0000644 00000012717 15252506741 0011525 0 ustar 00 <?php namespace WPForms\Admin\Education\Builder; use WPForms\Admin\Education\AddonsItemBase; use WPForms\Admin\Education\Helpers; /** * Builder/Quiz Education feature for Lite and Pro. * * @since 1.9.9 */ class Quiz extends AddonsItemBase { /** * Indicate if the current Education feature is allowed to load. * * @since 1.9.9 * * @return bool * * @noinspection ReturnTypeCanBeDeclaredInspection * @noinspection PhpMissingReturnTypeInspection */ public function allow_load() { return wpforms_is_admin_page( 'builder' ) || wp_doing_ajax(); } /** * Hooks. * * @since 1.9.9 * * @noinspection ReturnTypeCanBeDeclaredInspection */ public function hooks() { add_action( 'wpforms_field_options_before_description', [ $this, 'quiz_fields' ], 10, 2 ); } /** * Display the Enable Quiz option. * * @since 1.9.9 * * @param array $field Field data. * @param object $instance Builder instance. * * @noinspection ReturnTypeCanBeDeclaredInspection * @noinspection PhpMissingParamTypeInspection * @noinspection HtmlUnknownTarget */ public function quiz_fields( $field, $instance ) { if ( ! in_array( $field['type'], [ 'radio', 'checkbox', 'select' ], true ) ) { return; } $addon = $this->addons->get_addon( 'quiz' ); if ( empty( $addon ) || empty( $addon['action'] ) || empty( $addon['status'] ) || ( $addon['status'] === 'active' && $addon['action'] !== 'upgrade' ) ) { return; } $form_id = ! empty( $instance->form_id ) ? (int) $instance->form_id : 0; $row_args = $this->get_enable_quiz_row_attributes( $addon, $form_id ); $row_args['content'] = $instance->field_element( 'toggle', $field, $this->get_enable_quiz_field_attributes( $addon ), false ); $instance->field_element( 'row', $field, $row_args ); $dismissed = get_user_meta( get_current_user_id(), 'wpforms_dismissed', true ); $dismiss_section = "builder-form-$form_id-field-options-quiz-notice"; // Check whether it is dismissed. if ( ! empty( $dismissed[ 'edu-' . $dismiss_section ] ) ) { return; } $badge = esc_html__( 'NEW FEATURE', 'wpforms-lite' ); $notice_header = esc_html__( 'Turn Your Form Into a Quiz', 'wpforms-lite' ); $notice = sprintf( wp_kses( /* translators: %1$s - link to the WPForms.com doc article. */ __( 'Easily create interactive quizzes. Add true or false, multiple choice, or checkbox questions. Set correct answers and automatically score submissions. <a href="%1$s" target="_blank" rel="noopener noreferrer">Learn more about the Quiz Addon</a>', 'wpforms-lite' ), [ 'a' => [ 'href' => [], 'rel' => [], 'target' => [], ], ] ), esc_url( wpforms_utm_link( 'https://wpforms.com/docs/quiz-addon/', 'Quiz Education', 'Quiz Documentation' ) ) ); printf( '<div class="wpforms-alert wpforms-alert-info wpforms-educational-alert wpforms-field-educational-alert wpforms-dismiss-container"> <span class="wpforms-badge wpforms-badge-sm wpforms-badge-block wpforms-badge-green wpforms-badge-rounded"> %5$s </span> <button type="button" class="wpforms-dismiss-button" title="%1$s" data-section="%2$s"></button> <h3>%4$s</h3> <p>%3$s</p> </div>', esc_html__( 'Dismiss this notice.', 'wpforms-lite' ), esc_attr( $dismiss_section ), $notice, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped $notice_header, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped $badge // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ); } /** * Get attributes for the `Enable Quiz` field option row. * * @since 1.9.9 * * @param array $addon Current addon information. * @param int $form_id Form ID. * * @return array */ private function get_enable_quiz_row_attributes( array $addon, int $form_id ): array { $data = $this->prepare_field_action_data( $addon ); $default = [ 'slug' => 'enable_quiz', ]; if ( ! empty( $data ) ) { $data = wp_parse_args( $data, $default ); $data['data']['redirect-url'] = add_query_arg( [ 'page' => 'wpforms-builder', 'view' => 'settings', 'form_id' => $form_id, 'section' => 'quiz', ], admin_url( 'admin.php' ) ); return $data; } return wp_parse_args( [ 'data' => [ 'action' => 'upgrade', 'name' => esc_html__( 'Quiz Addon', 'wpforms-lite' ), 'utm-content' => 'Quiz Addon', 'licence' => 'pro', 'message' => esc_html__( 'We\'re sorry, Enable Quiz is part of the Quiz Addon and not available on your plan. Please upgrade to the PRO plan to unlock all these awesome features.', 'wpforms-lite' ), ], 'class' => 'education-modal', ], $default ); } /** * Get attributes for the `Enable Quiz` field option. * * @since 1.9.9 * * @param array $addon Current addon information. * * @return array */ private function get_enable_quiz_field_attributes( array $addon ): array { $default = [ 'slug' => 'enable_quiz', 'value' => '0', 'desc' => esc_html__( 'Include in Quiz Scoring', 'wpforms-lite' ), ]; if ( $addon['plugin_allow'] ) { return $default; } return wp_parse_args( [ 'desc' => sprintf( '%1$s%2$s', esc_html__( 'Include in Quiz Scoring', 'wpforms-lite' ), Helpers::get_badge( 'Pro', 'sm', 'inline', 'slate' ) ), 'attrs' => [ 'disabled' => 'disabled', ], ], $default ); } } Education/Builder/Calculations.php 0000644 00000020070 15252506741 0013205 0 ustar 00 <?php namespace WPForms\Admin\Education\Builder; use WPForms\Admin\Education\AddonsItemBase; use WPForms\Admin\Education\Helpers; use WPForms\Integrations\AI\Helpers as AIHelpers; /** * Builder/Calculations Education feature for Lite and Pro. * * @since 1.8.4.1 */ class Calculations extends AddonsItemBase { /** * Support calculations in these field types. * * @since 1.8.4.1 * * @var array */ public const ALLOWED_FIELD_TYPES = [ 'text', 'textarea', 'number', 'hidden', 'payment-single' ]; /** * Field types that should display educational notice in the basic field options tab. * * @since 1.8.4.1 * * @var array */ public const BASIC_OPTIONS_NOTICE_FIELD_TYPES = [ 'number', 'payment-single' ]; /** * Indicate if the current Education feature is allowed to load. * * @since 1.8.4.1 * * @return bool * * @noinspection PhpMissingReturnTypeInspection * @noinspection ReturnTypeCanBeDeclaredInspection */ public function allow_load() { return wpforms_is_admin_page( 'builder' ) || wpforms_is_admin_ajax(); } /** * Hooks. * * @since 1.8.4.1 * * @noinspection ReturnTypeCanBeDeclaredInspection */ public function hooks() { add_action( 'wpforms_field_options_bottom_basic-options', [ $this, 'basic_options' ], 20, 2 ); add_action( 'wpforms_field_options_bottom_advanced-options', [ $this, 'advanced_options' ], 20, 2 ); } /** * Display notice on basic options. * * @since 1.8.4.1 * * @param array $field Field data. * @param object $instance Builder instance. * * @noinspection HtmlUnknownTarget * @noinspection ReturnTypeCanBeDeclaredInspection * @noinspection PhpMissingParamTypeInspection * @noinspection HtmlUnknownAnchorTarget */ public function basic_options( $field, $instance ) { // Display notice in basic options only in numbers and payment-single fields. if ( ! in_array( $field['type'], self::BASIC_OPTIONS_NOTICE_FIELD_TYPES, true ) ) { return; } $dismissed = get_user_meta( get_current_user_id(), 'wpforms_dismissed', true ); $form_id = $instance->form_id ?? 0; $dismiss_section = "builder-form-$form_id-field-options-calculations-notice"; // Check whether it is dismissed. if ( ! empty( $dismissed[ 'edu-' . $dismiss_section ] ) ) { return; } // Display notice only if Calculations addon is released (available in the `addons.json` file). $addon = $this->addons->get_addon( 'calculations' ); if ( ! $addon ) { return; } if ( AIHelpers::is_disabled() || ( wpforms_version_compare( $addon['version'] ?? '1.5.0', '1.5.0', '<=' ) ) ) { $this->print_standard_education( $dismiss_section ); return; } $badge = esc_html__( 'NEW FEATURE', 'wpforms-lite' ); $notice_header = esc_html__( 'AI Calculations Are Here!', 'wpforms-lite' ); $notice = sprintf( wp_kses( /* translators: %1$s - link to the WPForms.com doc article. */ __( 'Easily create advanced calculations with WPForms AI. Head over to the <a href="#advanced-tab">Advanced Tab</a> to get started or read <a href="%1$s" target="_blank" rel="noopener noreferrer">our documentation</a> to learn more.', 'wpforms-lite' ), [ 'a' => [ 'href' => [], 'rel' => [], 'target' => [], ], ] ), esc_url( wpforms_utm_link( 'https://wpforms.com/docs/generating-calculation-formulas-with-wpforms-ai/', 'Calculations Education', 'Calculations Documentation' ) ) ); printf( '<div class="wpforms-alert-ai wpforms-alert wpforms-educational-alert wpforms-calculations wpforms-field-educational-alert wpforms-dismiss-container"> <span class="wpforms-badge wpforms-badge-sm wpforms-badge-block wpforms-badge-purple wpforms-badge-rounded"> %5$s </span> <button type="button" class="wpforms-dismiss-button" title="%1$s" data-section="%2$s"></button> <h3>%4$s</h3> <p>%3$s</p> </div>', esc_html__( 'Dismiss this notice.', 'wpforms-lite' ), esc_attr( $dismiss_section ), $notice, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped $notice_header, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped $badge // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ); } /** * Print standard education notice. * * @since 1.9.4 * * @param string $dismiss_section Dismiss section. * * @noinspection HtmlUnknownAnchorTarget * @noinspection HtmlUnknownTarget */ private function print_standard_education( string $dismiss_section ): void { $notice = sprintf( wp_kses( /* translators: %1$s - link to the WPForms.com doc article. */ __( 'Easily perform calculations based on user input. Head over to the <a href="#advanced-tab">Advanced Tab</a> to get started or read <a href="%1$s" target="_blank" rel="noopener noreferrer">our documentation</a> to learn more.', 'wpforms-lite' ), [ 'a' => [ 'href' => [], 'rel' => [], 'target' => [], ], ] ), esc_url( wpforms_utm_link( 'https://wpforms.com/docs/calculations-addon/', 'Calculations Education', 'Calculations Documentation' ) ) ); printf( '<div class="wpforms-alert-info wpforms-alert wpforms-educational-alert wpforms-calculations wpforms-field-educational-alert wpforms-dismiss-container"> <button type="button" class="wpforms-dismiss-button" title="%1$s" data-section="%2$s"></button> <p>%3$s</p> </div>', esc_html__( 'Dismiss this notice.', 'wpforms-lite' ), esc_attr( $dismiss_section ), $notice // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ); } /** * Display advanced options. * * @since 1.8.4.1 * * @param array $field Field data. * @param object $instance Builder instance. * * @noinspection ReturnTypeCanBeDeclaredInspection * @noinspection PhpMissingParamTypeInspection */ public function advanced_options( $field, $instance ) { if ( ! in_array( $field['type'], self::ALLOWED_FIELD_TYPES, true ) ) { return; } $addon = $this->addons->get_addon( 'calculations' ); if ( ! $this->is_edu_required_by_status( $addon ) ) { return; } $row_args = $this->get_row_attributes( $addon ); $row_args['content'] = $instance->field_element( 'toggle', $field, $this->get_field_attributes( $addon ), false ); $instance->field_element( 'row', $field, $row_args ); } /** * Get row attributes. * * @since 1.8.4.1 * * @param array $addon Addon data. * * @return array */ private function get_row_attributes( array $addon ): array { $data = $this->prepare_field_action_data( $addon ); $default = [ 'slug' => 'calculation_is_enabled', ]; if ( ! empty( $data ) ) { return wp_parse_args( $data, $default ); } return wp_parse_args( [ 'data' => [ 'action' => 'upgrade', 'name' => esc_html__( 'Calculations', 'wpforms-lite' ), 'utm-content' => 'Enable Calculations', 'license' => $addon['license_level'], ], 'class' => 'education-modal', ], $default ); } /** * Get attributes for the Enable Calculation field. * * @since 1.8.4.1 * * @param array $addon Addon data. * * @return array */ private function get_field_attributes( array $addon ): array { $default = [ 'slug' => 'calculation_is_enabled', 'value' => '0', 'desc' => esc_html__( 'Enable Calculation', 'wpforms-lite' ), ]; if ( $addon['plugin_allow'] ) { return $default; } return wp_parse_args( [ 'desc' => sprintf( '%1$s%2$s', esc_html__( 'Enable Calculation', 'wpforms-lite' ), Helpers::get_badge( $addon['license_level'], 'sm', 'inline', 'slate' ) ), 'attrs' => [ 'disabled' => 'disabled', ], ], $default ); } /** * Determine if we require displaying educational items according to the addon status. * * @since 1.8.4.1 * * @param array $addon Addon data. * * @return bool */ private function is_edu_required_by_status( array $addon ): bool { return ! ( empty( $addon ) || empty( $addon['action'] ) || empty( $addon['status'] ) || ( $addon['status'] === 'active' && $addon['action'] !== 'upgrade' ) ); } } Education/Builder/Payments.php 0000644 00000002347 15252506741 0012373 0 ustar 00 <?php namespace WPForms\Admin\Education\Builder; use \WPForms\Admin\Education; /** * Builder/Payments Education feature. * * @since 1.6.6 */ class Payments extends Education\Builder\Panel { /** * Panel slug. * * @since 1.6.6 * * @return string **/ protected function get_name() { return 'payments'; } /** * Hooks. * * @since 1.6.6 */ public function hooks() { add_action( 'wpforms_payments_panel_sidebar', [ $this, 'filter_addons' ], 1 ); add_action( 'wpforms_payments_panel_sidebar', [ $this, 'display_addons' ], 500 ); } /** * Get addons for the Payments panel. * * @since 1.7.7.2 * * @return array */ protected function get_addons() { return $this->addons->get_by_path( 'form_builder.category', $this->get_name() ); } /** * Template name for rendering single addon item. * * @since 1.6.6 * * @return string */ protected function get_single_addon_template() { return 'education/builder/providers-item'; } /** * Ensure that we do not display activated addon items if those addons are not allowed according to the current license. * * @since 1.6.6 */ public function filter_addons() { $this->filter_not_allowed_addons( 'wpforms_payments_panel_sidebar' ); } } Education/EducationInterface.php 0000644 00000000636 15252506741 0012740 0 ustar 00 <?php namespace WPForms\Admin\Education; /** * Interface EducationInterface defines required methods for Education features to work properly. * * @since 1.6.6 */ interface EducationInterface { /** * Indicate if current Education feature is allowed to load. * * @since 1.6.6 * * @return bool */ public function allow_load(); /** * Init. * * @since 1.6.6 */ public function init(); } Education/Fields.php 0000644 00000023705 15252506741 0010414 0 ustar 00 <?php namespace WPForms\Admin\Education; /** * Fields data holder. * * @since 1.6.6 */ class Fields { /** * All fields data. * * @since 1.6.6 * * @var array */ protected $fields; /** * All fields data. * * @since 1.6.6 * * @return array All possible fields. */ private function get_all(): array { if ( ! empty( $this->fields ) ) { return $this->fields; } $this->fields = [ [ 'icon' => 'fa-phone', 'name' => esc_html__( 'Phone', 'wpforms-lite' ), 'name_en' => 'Phone', 'type' => 'phone', 'group' => 'fancy', 'order' => '50', ], [ 'icon' => 'fa-map-marker', 'name' => esc_html__( 'Address', 'wpforms-lite' ), 'name_en' => 'Address', 'type' => 'address', 'group' => 'fancy', 'order' => '70', ], [ 'icon' => 'fa-map-location-dot', 'name' => esc_html__( 'Map', 'wpforms-lite' ), 'name_en' => 'Map', 'type' => 'Map', 'group' => 'fancy', 'addon' => 'wpforms-geolocation', 'order' => '75', ], [ 'icon' => 'fa-calendar-o', 'name' => esc_html__( 'Date / Time', 'wpforms-lite' ), 'name_en' => 'Date / Time', 'type' => 'date-time', 'group' => 'fancy', 'order' => '60', ], [ 'icon' => 'fa-link', 'name' => esc_html__( 'Website / URL', 'wpforms-lite' ), 'name_en' => 'Website / URL', 'type' => 'url', 'group' => 'fancy', 'order' => '90', ], [ 'icon' => 'fa-upload', 'name' => esc_html__( 'File Upload', 'wpforms-lite' ), 'name_en' => 'File Upload', 'type' => 'file-upload', 'group' => 'fancy', 'order' => '100', ], [ 'icon' => 'fa-camera', 'name' => esc_html__( 'Camera', 'wpforms-lite' ), 'name_en' => 'Camera', 'type' => 'camera', 'group' => 'fancy', 'order' => '105', ], [ 'icon' => 'fa-lock', 'name' => esc_html__( 'Password', 'wpforms-lite' ), 'name_en' => 'Password', 'type' => 'password', 'group' => 'fancy', 'order' => '95', ], [ 'icon' => 'fa-columns', 'name' => esc_html__( 'Layout', 'wpforms-lite' ), 'name_en' => 'Layout', 'type' => 'layout', 'group' => 'fancy', 'order' => '140', ], [ 'icon' => 'fa-list', 'name' => esc_html__( 'Repeater', 'wpforms-lite' ), 'name_en' => 'Repeater', 'type' => 'repeater', 'group' => 'fancy', 'order' => '150', ], [ 'icon' => 'fa-files-o', 'name' => esc_html__( 'Page Break', 'wpforms-lite' ), 'name_en' => 'Page Break', 'type' => 'pagebreak', 'group' => 'fancy', 'order' => '160', ], [ 'icon' => 'fa-arrows-h', 'name' => esc_html__( 'Section Divider', 'wpforms-lite' ), 'name_en' => 'Section Divider', 'type' => 'divider', 'group' => 'fancy', 'order' => '170', ], [ 'icon' => 'fa-pencil-square-o', 'name' => esc_html__( 'Rich Text', 'wpforms-lite' ), 'name_en' => 'Rich Text', 'type' => 'richtext', 'group' => 'fancy', 'order' => '170', ], [ 'icon' => 'fa-file-image-o', 'name' => esc_html__( 'Content', 'wpforms-lite' ), 'name_en' => 'Content', 'type' => 'content', 'group' => 'fancy', 'order' => '180', ], [ 'icon' => 'fa-code', 'name' => esc_html__( 'HTML', 'wpforms-lite' ), 'name_en' => 'HTML', 'type' => 'html', 'group' => 'fancy', 'order' => '185', ], [ 'icon' => 'fa-file-text-o', 'name' => esc_html__( 'Entry Preview', 'wpforms-lite' ), 'name_en' => 'Entry Preview', 'type' => 'entry-preview', 'group' => 'fancy', 'order' => '190', ], [ 'icon' => 'fa-star', 'name' => esc_html__( 'Rating', 'wpforms-lite' ), 'name_en' => 'Rating', 'type' => 'rating', 'group' => 'fancy', 'order' => '310', ], [ 'icon' => 'fa-eye-slash', 'name' => esc_html__( 'Hidden Field', 'wpforms-lite' ), 'name_en' => 'Hidden Field', 'type' => 'hidden', 'group' => 'fancy', 'order' => '305', ], [ 'icon' => 'fa-question-circle', 'name' => esc_html__( 'Custom Captcha', 'wpforms-lite' ), 'keywords' => esc_html__( 'spam, math, maths, question', 'wpforms-lite' ), 'name_en' => 'Custom Captcha', 'type' => 'captcha', 'group' => 'fancy', 'addon' => 'wpforms-captcha', 'order' => '300', ], [ 'icon' => 'fa-pencil', 'name' => esc_html__( 'Signature', 'wpforms-lite' ), 'keywords' => esc_html__( 'user, e-signature', 'wpforms-lite' ), 'name_en' => 'Signature', 'type' => 'signature', 'group' => 'fancy', 'addon' => 'wpforms-signatures', 'order' => '200', ], [ 'icon' => 'fa-ellipsis-h', 'name' => esc_html__( 'Likert Scale', 'wpforms-lite' ), 'keywords' => esc_html__( 'survey, rating scale', 'wpforms-lite' ), 'name_en' => 'Likert Scale', 'type' => 'likert_scale', 'group' => 'fancy', 'addon' => 'wpforms-surveys-polls', 'order' => '400', ], [ 'icon' => 'fa-tachometer', 'name' => esc_html__( 'Net Promoter Score', 'wpforms-lite' ), 'keywords' => esc_html__( 'survey, nps', 'wpforms-lite' ), 'name_en' => 'Net Promoter Score', 'type' => 'net_promoter_score', 'group' => 'fancy', 'addon' => 'wpforms-surveys-polls', 'order' => '410', ], [ 'icon' => 'fa-credit-card', 'name' => esc_html__( 'Authorize.Net', 'wpforms-lite' ), 'keywords' => esc_html__( 'store, ecommerce, credit card, pay, payment, debit card', 'wpforms-lite' ), 'name_en' => 'Authorize.Net', 'type' => 'authorize_net', 'group' => 'payment', 'addon' => 'wpforms-authorize-net', 'order' => '95', ], [ 'icon' => 'fa-ticket', 'name' => esc_html__( 'Coupon', 'wpforms-lite' ), 'keywords' => esc_html__( 'discount, sale', 'wpforms-lite' ), 'name_en' => 'Coupon', 'type' => 'payment-coupon', 'group' => 'payment', 'addon' => 'wpforms-coupons', 'order' => '100', ], ]; $captcha = $this->get_captcha(); if ( ! empty( $captcha ) ) { $this->fields[] = $captcha; } return $this->fields; } /** * Get Captcha field data. * * @since 1.6.6 * * @return array Captcha field data. */ private function get_captcha(): array { $captcha_settings = wpforms_get_captcha_settings(); if ( empty( $captcha_settings['provider'] ) ) { return []; } $captcha = [ 'hcaptcha' => [ 'name' => 'hCaptcha', 'icon' => 'fa-question-circle-o', ], 'recaptcha' => [ 'name' => 'reCAPTCHA', 'icon' => 'fa-google', ], 'turnstile' => [ 'name' => 'Turnstile', 'icon' => 'fa-question-circle-o', ], ]; if ( ! empty( $captcha_settings['site_key'] ) || ! empty( $captcha_settings['secret_key'] ) ) { $captcha_name = $captcha[ $captcha_settings['provider'] ]['name']; $captcha_icon = $captcha[ $captcha_settings['provider'] ]['icon']; } else { $captcha_name = 'CAPTCHA'; $captcha_icon = 'fa-question-circle-o'; } return [ 'icon' => $captcha_icon, 'name' => $captcha_name, 'name_en' => $captcha_name, 'keywords' => esc_html__( 'captcha, spam, antispam', 'wpforms-lite' ), 'type' => 'captcha_' . $captcha_settings['provider'], 'group' => 'standard', 'order' => 180, 'class' => 'not-draggable', ]; } /** * Get filtered fields data. * * Usage: * get_filtered( [ 'group' => 'payment' ] ) - fields from the 'payment' group. * get_filtered( [ 'addon' => 'surveys-polls' ] ) - fields of the addon 'surveys-polls'. * get_filtered( [ 'type' => 'payment-total' ] ) - field 'payment-total'. * * @since 1.6.6 * * @param array $args Arguments array. * * @return array Fields data filtered according to given arguments. */ private function get_filtered( array $args = [] ): array { $default_args = [ 'group' => '', 'addon' => '', 'type' => '', ]; $args = array_filter( wp_parse_args( $args, $default_args ) ); $fields = $this->get_all(); $filtered_fields = []; foreach ( $args as $prop => $prop_val ) { foreach ( $fields as $field ) { if ( ! empty( $field[ $prop ] ) && $field[ $prop ] === $prop_val ) { $filtered_fields[] = $field; } } } return $filtered_fields; } /** * Get fields by group. * * @since 1.6.6 * * @param string $group Fields group (standard, fancy or payment). * * @return array. */ public function get_by_group( string $group ): array { return $this->get_filtered( [ 'group' => $group ] ); } /** * Get fields by addon. * * @since 1.6.6 * * @param string $addon Addon slug. * * @return array. */ public function get_by_addon( string $addon ): array { return $this->get_filtered( [ 'addon' => $addon ] ); } /** * Get field by type. * * @since 1.6.6 * * @param string $type Field type. * * @return array Single field data. Empty array if field is not available. */ public function get_field( string $type ): array { $fields = $this->get_filtered( [ 'type' => $type ] ); return ! empty( $fields[0] ) ? $fields[0] : []; } /** * Set key value of each field (conditionally). * * @since 1.6.6 * * @param array $fields Fields data. * @param string $key Key. * @param string $value Value. * @param string $condition Condition. * * @return array Updated field data. */ public function set_values( array $fields, string $key, string $value, string $condition ): array { if ( empty( $fields ) || empty( $key ) ) { return $fields; } foreach ( $fields as $f => $field ) { switch ( $condition ) { case 'empty': $fields[ $f ][ $key ] = empty( $field[ $key ] ) ? $value : $field[ $key ]; break; default: $fields[ $f ][ $key ] = $value; } } return $fields; } } Education/Admin/EditPost.php 0000644 00000013657 15252506741 0011776 0 ustar 00 <?php namespace WPForms\Admin\Education\Admin; use WP_Post; use WPForms\Admin\Education\EducationInterface; /** * Admin/EditPost Education feature. * * @since 1.8.1 */ class EditPost implements EducationInterface { /** * Determine if the website has some forms. * * @since 1.8.1 * * @var bool */ private $has_forms; /** * Indicate if edit post education is allowed to load. * * @since 1.8.1 * * @return bool */ public function allow_load() { if ( ! is_admin() ) { return false; } if ( ! wpforms_current_user_can( 'view_forms' ) ) { return false; } // Skip it if it's the Challenge flow. if ( wpforms()->obj( 'challenge' )->is_form_embed_page() ) { return false; } $form_embed_wizard = wpforms()->obj( 'form_embed_wizard' ); // Skip it if it's the Form Embed Wizard flow. if ( $form_embed_wizard->is_form_embed_page( 'edit' ) && $form_embed_wizard->get_meta() ) { return false; } $user_id = get_current_user_id(); $dismissed = get_user_meta( $user_id, 'wpforms_dismissed', true ); return empty( $dismissed['edu-edit-post-notice'] ); } /** * Initialize. * * @since 1.8.1 */ public function init() { if ( ! $this->allow_load() ) { return; } // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.SuppressFilters_suppress_filters $this->has_forms = (bool) wpforms()->obj( 'form' )->get( '', [ 'numberposts' => 1, 'nopaging' => false, 'fields' => 'ids', 'no_found_rows' => true, 'update_post_meta_cache' => false, 'update_post_term_cache' => false, 'suppress_filters' => true, // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.SuppressFilters_suppress_filters ] ); $this->hooks(); } /** * Add hooks. * * @since 1.8.1 */ private function hooks() { add_action( 'edit_form_after_title', [ $this, 'classic_editor_notice' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_styles' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] ); } /** * Is gutenberg Editor. * * @since 1.8.1 * * @return bool */ private function is_gutenberg_editor() { return (bool) get_current_screen()->is_block_editor(); } /** * Enqueue styles. * * @since 1.8.1 */ public function enqueue_styles() { $min = wpforms_get_min_suffix(); wp_enqueue_style( 'wpforms-edit-post-education', WPFORMS_PLUGIN_URL . "assets/css/admin/edit-post-education{$min}.css", [], WPFORMS_VERSION ); } /** * Enqueue scripts. * * @since 1.8.1 */ public function enqueue_scripts() { $min = wpforms_get_min_suffix(); wp_enqueue_script( 'wpforms-edit-post-education', WPFORMS_PLUGIN_URL . "assets/js/admin/education/edit-post.es5{$min}.js", [ 'jquery', 'underscore' ], WPFORMS_VERSION, true ); $strings = [ 'ajax_url' => admin_url( 'admin-ajax.php' ), 'education_nonce' => wp_create_nonce( 'wpforms-education' ), ]; if ( $this->is_gutenberg_editor() ) { $strings = array_merge( $strings, $this->get_gutenberg_strings() ); } wp_localize_script( 'wpforms-edit-post-education', 'wpforms_edit_post_education', $strings ); } /** * Get Gutenberg i18n strings. * * @since 1.8.1 * * @return array */ private function get_gutenberg_strings() { $strings = [ 'is_gutenberg' => true, 'gutenberg_notice' => [ 'template' => $this->get_gutenberg_notice_template(), 'button' => __( 'Get Started', 'wpforms-lite' ), ], ]; if ( ! $this->has_forms ) { $strings['gutenberg_notice']['url'] = add_query_arg( 'page', 'wpforms-overview', admin_url( 'admin.php' ) ); return $strings; } $strings['gutenberg_guide'] = [ [ 'image' => WPFORMS_PLUGIN_URL . '/assets/images/edit-post-education-page-1.png', 'title' => __( 'Easily add your contact form', 'wpforms-lite' ), 'content' => __( 'Oh hey, it looks like you\'re working on a contact page. Don\'t forget to embed your contact form. Click the plus icon above and search for WPForms.', 'wpforms-lite' ), ], [ 'image' => WPFORMS_PLUGIN_URL . '/assets/images/edit-post-education-page-2.png', 'title' => __( 'Embed your form', 'wpforms-lite' ), 'content' => __( 'Then click on the WPForms block to embed your desired contact form.', 'wpforms-lite' ), ], ]; return $strings; } /** * Add notice to classic editor. * * @since 1.8.1 * * @param WP_Post $post Add notice to classic editor. */ public function classic_editor_notice( $post ) { $message = $this->has_forms ? __( 'Don\'t forget to embed your contact form. Simply click the Add Form button below.', 'wpforms-lite' ) : sprintf( /* translators: %1$s - link to create a new form. */ __( 'Did you know that with <a href="%1$s" target="_blank" rel="noopener noreferrer">WPForms</a>, you can create an easy-to-use contact form in a matter of minutes?', 'wpforms-lite' ), esc_url( add_query_arg( 'page', 'wpforms-overview', admin_url( 'admin.php' ) ) ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wpforms_render( 'education/admin/edit-post/classic-notice', [ 'message' => $message, ], true ); } /** * Get Gutenberg notice template. * * @since 1.8.1 * * @return string */ private function get_gutenberg_notice_template() { $message = $this->has_forms ? __( 'You\'ve already created a form, now add it to the page so your customers can get in touch.', 'wpforms-lite' ) : sprintf( /* translators: %1$s - link to create a new form. */ __( 'Did you know that with <a href="%1$s" target="_blank" rel="noopener noreferrer">WPForms</a>, you can create an easy-to-use contact form in a matter of minutes?', 'wpforms-lite' ), esc_url( add_query_arg( 'page', 'wpforms-overview', admin_url( 'admin.php' ) ) ) ); return wpforms_render( 'education/admin/edit-post/notice', [ 'message' => $message, ], true ); } } Education/Admin/Settings/Geolocation.php 0000644 00000007761 15252506741 0014305 0 ustar 00 <?php namespace WPForms\Admin\Education\Admin\Settings; use WPForms\Admin\Education\AddonsItemBase; /** * Admin/Settings/Geolocation Education feature for Lite and Pro. * * @since 1.6.6 */ class Geolocation extends AddonsItemBase { /** * Slug. * * @since 1.6.6 */ const SLUG = 'geolocation'; /** * Hooks. * * @since 1.6.6 */ public function hooks() { add_action( 'admin_enqueue_scripts', [ $this, 'enqueues' ] ); add_filter( 'wpforms_settings_defaults', [ $this, 'add_sections' ] ); } /** * Indicate if current Education feature is allowed to load. * * @since 1.6.6 * * @return bool */ public function allow_load() { return wpforms_is_admin_page( 'settings', 'geolocation' ); } /** * Enqueues. * * @since 1.6.6 */ public function enqueues() { // Lity - lightbox for images. wp_enqueue_style( 'wpforms-lity', WPFORMS_PLUGIN_URL . 'assets/lib/lity/lity.min.css', null, '3.0.0' ); wp_enqueue_script( 'wpforms-lity', WPFORMS_PLUGIN_URL . 'assets/lib/lity/lity.min.js', [ 'jquery' ], '3.0.0', true ); } /** * Preview of education features for customers with not enough permissions. * * @since 1.6.6 * * @param array $settings Settings sections. * * @return array */ public function add_sections( $settings ) { $addon = $this->addons->get_addon( 'geolocation' ); if ( empty( $addon ) || empty( $addon['status'] ) || empty( $addon['action'] ) ) { return $settings; } $settings[ self::SLUG ][ self::SLUG . '-page' ] = [ 'id' => self::SLUG . '-page', 'content' => wpforms_render( 'education/admin/page', $this->template_data(), true ), 'type' => 'content', 'no_label' => true, 'class' => [ 'wpforms-education-container-page' ], ]; return $settings; } /** * Get the template data. * * @since 1.8.6 * * @return array */ private function template_data(): array { $addon = $this->addons->get_addon( 'geolocation' ); $images_url = WPFORMS_PLUGIN_URL . 'assets/images/geolocation-education/'; $params = [ 'features' => [ __( 'City', 'wpforms-lite' ), __( 'Latitude/Longitude', 'wpforms-lite' ), __( 'Google Places API', 'wpforms-lite' ), __( 'Country', 'wpforms-lite' ), __( 'Address Autocomplete', 'wpforms-lite' ), __( 'Mapbox API', 'wpforms-lite' ), __( 'Postal/Zip Code', 'wpforms-lite' ), __( 'Embedded Map in Forms', 'wpforms-lite' ), ], 'images' => [ [ 'url' => $images_url . 'entry-location.jpg', 'url2x' => $images_url . 'entry-location@2x.jpg', 'title' => __( 'Location Info in Entries', 'wpforms-lite' ), ], [ 'url' => $images_url . 'address-autocomplete.jpg', 'url2x' => $images_url . 'address-autocomplete@2x.jpg', 'title' => __( 'Address Autocomplete Field', 'wpforms-lite' ), ], [ 'url' => $images_url . 'smart-address-field.jpg', 'url2x' => $images_url . 'smart-address-field@2x.jpg', 'title' => __( 'Smart Address Field', 'wpforms-lite' ), ], ], 'utm_medium' => 'Settings - Geolocation', 'utm_content' => 'Geolocation Addon', 'heading_title' => __( 'Geolocation', 'wpforms-lite' ), 'heading_description' => sprintf( '<p>%1$s</p>', __( 'Do you want to learn more about visitors who fill out your online forms? Our geolocation addon allows you to collect and store your website visitors geolocation data along with their form submission. This insight can help you to be better informed and turn more leads into customers. Furthermore, add a smart address field that autocompletes using the Google Places API.', 'wpforms-lite' ) ), 'features_description' => __( 'Powerful location-based insights and features…', 'wpforms-lite' ), ]; // Only show the upgrade badge when the current license does not include the addon. if ( empty( $addon['plugin_allow'] ) ) { $params['badge'] = __( 'Pro', 'wpforms-lite' ); } return array_merge( $params, $addon ); } } Education/Admin/Settings/SMTP.php 0000644 00000002217 15252506741 0012614 0 ustar 00 <?php namespace WPForms\Admin\Education\Admin\Settings; use WPForms\Admin\Education\EducationInterface; /** * SMTP education notice. * * @since 1.8.1 */ class SMTP implements EducationInterface { /** * Indicate if Education core is allowed to load. * * @since 1.8.1 * * @return bool */ public function allow_load() { if ( ! wpforms_can_install( 'plugin' ) || ! wpforms_can_activate( 'plugin' ) ) { return false; } $user_id = get_current_user_id(); $dismissed = get_user_meta( $user_id, 'wpforms_dismissed', true ); if ( ! empty( $dismissed['edu-smtp-notice'] ) ) { return false; } $active_plugins = get_option( 'active_plugins', [] ); $allowed_plugins = [ 'wp-mail-smtp/wp_mail_smtp.php', 'wp-mail-smtp-pro/wp_mail_smtp.php', ]; return ! array_intersect( $active_plugins, $allowed_plugins ); } /** * Init. * * @since 1.8.1 */ public function init() { } /** * Get notice template. * * @since 1.8.1 * * @return string */ public function get_template() { if ( ! $this->allow_load() ) { return ''; } return wpforms_render( 'education/admin/settings/smtp-notice' ); } } Education/Admin/Settings/Integrations.php 0000644 00000002555 15252506741 0014504 0 ustar 00 <?php namespace WPForms\Admin\Education\Admin\Settings; use \WPForms\Admin\Education\AddonsListBase; /** * Base class for Admin/Integrations feature for Lite and Pro. * * @since 1.6.6 */ class Integrations extends AddonsListBase { /** * Template for rendering single addon item. * * @since 1.6.6 * * @var string */ protected $single_addon_template = 'education/admin/settings/integrations-item'; /** * Hooks. * * @since 1.6.6 */ public function hooks() { add_action( 'wpforms_settings_providers', [ $this, 'filter_addons' ], 1 ); add_action( 'wpforms_settings_providers', [ $this, 'display_addons' ], 500 ); } /** * Indicate if current Education feature is allowed to load. * * @since 1.6.6 * * @return bool */ public function allow_load() { return wpforms_is_admin_page( 'settings', 'integrations' ); } /** * Get addons for the Settings/Integrations tab. * * @since 1.6.6 * * @return array Addons data. */ protected function get_addons() { return $this->addons->get_by_path( 'settings_integrations.category', 'crm|email-marketing|integration' ); } /** * Ensure that we do not display activated addon items if those addons are not allowed according to the current license. * * @since 1.6.6 */ public function filter_addons() { $this->filter_not_allowed_addons( 'wpforms_settings_providers' ); } } Education/Admin/Tools/EntryAutomation.php 0000644 00000010437 15252506741 0014476 0 ustar 00 <?php namespace WPForms\Admin\Education\Admin\Tools; /** * Entry Automation Education class. * * @since 1.9.6.1 */ class EntryAutomation { /** * Education init. * * @since 1.9.6.1 */ public function init(): void { $this->hooks(); } /** * Load hooks. * * @since 1.9.6.1 */ private function hooks(): void { add_action( 'wpforms_admin_tools_views_entry_automation_display', [ $this, 'display' ] ); } /** * Get the template data. * * @since 1.9.6.1 * * @return array */ private function get_template_data(): array { $images_url = WPFORMS_PLUGIN_URL . 'assets/images/entry-automation/'; $utm_medium = 'Tools - Entry Automation'; $utm_content = 'Entry Automation Addon'; $addon = wpforms()->obj( 'addons' )->get_addon( 'entry-automation' ); $upgrade_link = $addon['action'] === 'upgrade' ? sprintf( /* translators: %1$s - WPForms.com Upgrade page URL. */ ' <strong><a href="%1$s" target="_blank" rel="noopener noreferrer" class="wpforms-upgrade-link">%2$s</a></strong>', esc_url( wpforms_admin_upgrade_link( $utm_medium, $utm_content ) ), esc_html__( 'Upgrade to WPForms Elite', 'wpforms-lite' ) ) : ''; $params = [ 'features' => [ __( 'Automated Task Scheduling', 'wpforms-lite' ), __( 'Scheduled Exports', 'wpforms-lite' ), __( 'Task Chaining', 'wpforms-lite' ), __( 'Automated Deletions', 'wpforms-lite' ), __( 'Enhanced Data Management', 'wpforms-lite' ), __( 'Robust Failsafes', 'wpforms-lite' ), ], 'images' => [ [ 'url' => $images_url . 'education.png', 'url2x' => $images_url . 'education.png', 'title' => '', ], ], 'utm_medium' => $utm_medium, 'utm_content' => $utm_content, 'upgrade_link_text' => esc_html__( 'Upgrade to WPForms Elite', 'wpforms-lite' ), 'heading_title' => __( 'Tired of manually exporting and deleting entries? Wish you could schedule these actions for optimal efficiency?', 'wpforms-lite' ), /* translators: %1$s - WPForms.com Upgrade page URL. */ 'heading_description' => '<p>' . esc_html__( 'Entry Automation introduces powerful, automated task chaining, allowing you to seamlessly schedule exports and deletions, ensuring your data is managed precisely how you need it. Chain multiple tasks together for complex workflows – export to CSV, then automatically delete after a specified period, for example. We\'ve built robust failsafes to guarantee data integrity, so you can automate with confidence, knowing your valuable entries are always protected. Take back your time and let our addon handle the heavy lifting, keeping your WPForms data organized and secure, automatically.', 'wpforms-lite' ) . '</p>' . '<p>' . wp_kses( $upgrade_link, [ 'a' => [ 'href' => [], 'rel' => [], 'target' => [], 'class' => [], ], 'strong' => [], ] ) . '</p>', 'features_description' => __( 'Powerful Automation Features', 'wpforms-lite' ), ]; return isset( $addon ) ? array_merge( $params, $addon ) : $params; } /** * Check if the addon is active. * * @since 1.9.6.1 * * @return bool */ private function is_addon_active(): bool { /** * Check if the addon is active. * * @since 1.9.6.1 * * @param bool $is_active Whether the addon is active. */ return (bool) apply_filters( 'wpforms_admin_education_admin_tools_entry_automation_is_addon_active', wpforms()->obj( 'addons' )->is_active( 'entry-automation' ) ); } /** * Display education content. * * @since 1.9.6.1 */ public function display(): void { // Display the education content only if the addon is not active. if ( $this->is_addon_active() ) { return; } $this->enqueue(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wpforms_render( 'education/admin/page', $this->get_template_data(), true ); } /** * Enqueue scripts and styles. * * @since 1.9.6.1 */ private function enqueue(): void { // Lity. wp_enqueue_style( 'wpforms-lity', WPFORMS_PLUGIN_URL . 'assets/lib/lity/lity.min.css', null, '3.0.0' ); wp_enqueue_script( 'wpforms-lity', WPFORMS_PLUGIN_URL . 'assets/lib/lity/lity.min.js', [ 'jquery' ], '3.0.0', true ); } } Education/Admin/Tools/ImportEntries.php 0000644 00000006700 15252506741 0014136 0 ustar 00 <?php namespace WPForms\Admin\Education\Admin\Tools; use WPForms\Admin\Tools\Import\Views\ImportViewsInterface; /** * Import Entries Education class. * * Shows an education placeholder for Lite users to upgrade to Pro * for the Entries Import feature. * * @since 1.10.1 */ class ImportEntries implements ImportViewsInterface { /** * Initialize class. * * @since 1.10.1 */ public function init(): void { } /** * Get the Tab label. * * @since 1.10.1 * * @return string */ public function get_tab_label(): string { return __( 'Import Entries', 'wpforms-lite' ); } /** * Check if the current user has the capability to view the page. * * @since 1.10.1 * * @return bool */ public function current_user_can(): bool { return wpforms_current_user_can( [ 'edit_forms', 'view_entries' ] ); } /** * Page content. * * @since 1.10.1 */ public function display(): void { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wpforms_render( 'education/admin/page', $this->get_template_data(), true ); } /** * Get the template data. * * @since 1.10.1 * * @return array * * @noinspection HtmlUnknownTarget */ private function get_template_data(): array { $images_url = WPFORMS_PLUGIN_URL . 'assets/images/entry-importer/'; $utm_medium = 'Tools - Import'; $utm_content_link = 'Entry Importer - Upgrade Link'; $utm_content_button = 'Entry Importer - Upgrade Button'; $upgrade_link = sprintf( /* translators: %1$s - WPForms.com Upgrade page URL. */ ' <strong><a href="%1$s" target="_blank" rel="noopener noreferrer">%2$s</a></strong>', esc_url( wpforms_admin_upgrade_link( $utm_medium, $utm_content_link ) ), esc_html__( 'Upgrade to WPForms Pro', 'wpforms-lite' ) ); return [ 'heading_title' => __( 'Import Entries', 'wpforms-lite' ), 'badge' => __( 'Pro', 'wpforms-lite' ), 'images' => [ [ 'url' => $images_url . 'screenshot-other-plugins.png', 'title' => __( 'Seamless Migration From Other Plugins', 'wpforms-lite' ), ], [ 'url' => $images_url . 'screenshot-field-mapping.png', 'title' => __( 'Map Fields in Seconds', 'wpforms-lite' ), ], ], 'features' => [ __( 'Migrate From Other Plugins', 'wpforms-lite' ), __( 'Import from Any CSV File', 'wpforms-lite' ), __( 'Map Columns to Form Fields', 'wpforms-lite' ), __( 'Works with CRM Exports', 'wpforms-lite' ), __( 'Preserve Your Complete History', 'wpforms-lite' ), __( 'Move Entries Between Forms', 'wpforms-lite' ), ], 'utm_medium' => $utm_medium, 'utm_content' => $utm_content_button, /* translators: %1$s - WPForms.com Upgrade page URL. */ 'heading_description' => '<p>' . esc_html__( 'Switching from another form plugin? Moving entries between forms? With WPForms Pro, you can import entries from any CSV file. Whether it\'s exported from another WordPress form plugin, pulled from your CRM, or built by hand. Map your data to the right fields, and every submission lands exactly where it belongs. No lost leads, no manual re-entry, no messy migrations. ', 'wpforms-lite' ) . wp_kses( $upgrade_link, [ 'a' => [ 'href' => [], 'rel' => [], 'target' => [], 'class' => [], ], 'strong' => [], ] ) . '</p>', 'features_description' => __( 'Powerful, Flexible Entry Imports', 'wpforms-lite' ), ]; } } Education/Admin/Tools/ExportEntries.php 0000644 00000005633 15252506741 0014151 0 ustar 00 <?php namespace WPForms\Admin\Education\Admin\Tools; use WPForms\Admin\Tools\Export\Views\ExportViewsInterface; /** * Export Entries Education class. * * Shows an education placeholder for Lite users to upgrade to Pro * for the Entries Export feature. * * @since 1.10.1 */ class ExportEntries implements ExportViewsInterface { /** * Initialize class. * * @since 1.10.1 */ public function init(): void { } /** * Get the Tab label. * * @since 1.10.1 * * @return string */ public function get_tab_label(): string { return __( 'Export Entries', 'wpforms-lite' ); } /** * Check if the current user has the capability to view the page. * * @since 1.10.1 * * @return bool */ public function current_user_can(): bool { return wpforms_current_user_can( [ 'edit_forms', 'view_entries' ] ); } /** * Page content. * * @since 1.10.1 */ public function display(): void { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wpforms_render( 'education/admin/page', $this->get_template_data(), true ); } /** * Get the template data. * * @since 1.10.1 * * @return array * * @noinspection HtmlUnknownTarget */ private function get_template_data(): array { $utm_medium = 'Tools - Export'; $utm_content_link = 'Entry Importer - Upgrade Link'; $utm_content_button = 'Entry Importer - Upgrade Button'; $upgrade_link = sprintf( /* translators: %1$s - WPForms.com Upgrade page URL. */ ' <strong><a href="%1$s" target="_blank" rel="noopener noreferrer">%2$s</a></strong>', esc_url( wpforms_admin_upgrade_link( $utm_medium, $utm_content_link ) ), esc_html__( 'Upgrade to WPForms Pro', 'wpforms-lite' ) ); return [ 'heading_title' => 'Export Entries', 'badge' => __( 'Pro', 'wpforms-lite' ), 'features' => [ __( 'Export as CSV or XLSX', 'wpforms-lite' ), __( 'Choose Specific Fields to Include', 'wpforms-lite' ), __( 'Filter by Date Range', 'wpforms-lite' ), __( 'Search and Export Matching Entries', 'wpforms-lite' ), __( 'Schedule Automated Exports', 'wpforms-lite' ), __( 'Re-import Into Any WPForms Form', 'wpforms-lite' ), ], 'utm_medium' => $utm_medium, 'utm_content' => $utm_content_button, 'heading_description' => '<p>' . esc_html__( 'Need to back up your form data, share it with your team, or move entries to another form? WPForms Pro lets you export entries from any form as a CSV or XLSX file. Choose which fields to include, filter by date range, and download a clean file ready for spreadsheets, CRMs, or re-importing into WPForms.', 'wpforms-lite' ) . wp_kses( $upgrade_link, [ 'a' => [ 'href' => [], 'rel' => [], 'target' => [], 'class' => [], ], 'strong' => [], ] ) . '</p>', 'features_description' => __( 'Full Control Over Your Data', 'wpforms-lite' ), ]; } } Education/AddonsListBase.php 0000644 00000002756 15252506741 0012050 0 ustar 00 <?php namespace WPForms\Admin\Education; /** * Base class for all "addons list" type Education features. * * @since 1.6.6 */ abstract class AddonsListBase extends AddonsItemBase { /** * Display addons. * * @since 1.6.6 */ public function display_addons() { array_map( [ $this, 'display_single_addon' ], (array) $this->get_addons() ); } /** * Get addons. * * @since 1.6.6 * * @return array Addons array. */ abstract protected function get_addons(); /** * Ensure that we do not display activated addon items if those addons are not allowed according to the current license. * * @since 1.6.6 * * @param string $hook Hook name. */ protected function filter_not_allowed_addons( $hook ) { $edu_addons = wp_list_pluck( $this->get_addons(), 'slug' ); foreach ( $edu_addons as $i => $addon ) { $edu_addons[ $i ] = strtolower( preg_replace( '/[^a-zA-Z0-9]+/', '', $addon ) ); } if ( empty( $GLOBALS['wp_filter'][ $hook ]->callbacks ) ) { return; } foreach ( $GLOBALS['wp_filter'][ $hook ]->callbacks as $priority => $hooks ) { foreach ( $hooks as $name => $arr ) { $class = ! empty( $arr['function'][0] ) && is_object( $arr['function'][0] ) ? strtolower( get_class( $arr['function'][0] ) ) : ''; $class = explode( '\\', $class )[0]; $class = preg_replace( '/[^a-zA-Z0-9]+/', '', $class ); if ( in_array( $class, $edu_addons, true ) ) { unset( $GLOBALS['wp_filter'][ $hook ]->callbacks[ $priority ][ $name ] ); } } } } } Education/Core.php 0000644 00000005743 15252506741 0010100 0 ustar 00 <?php namespace WPForms\Admin\Education; /** * Education core. * * @since 1.6.6 */ class Core { use StringsTrait; /** * Indicate if Education core is allowed to load. * * @since 1.6.6 * * @return bool */ public function allow_load(): bool { return wp_doing_ajax() || wpforms_is_admin_page() || wpforms_is_admin_page( 'builder' ); } /** * Init. * * @since 1.6.6 */ public function init() { // Only proceed if allowed. if ( ! $this->allow_load() ) { return; } $this->hooks(); } /** * Hooks. * * @since 1.6.6 */ protected function hooks() { if ( wp_doing_ajax() ) { add_action( 'wp_ajax_wpforms_education_dismiss', [ $this, 'ajax_dismiss' ] ); return; } add_action( 'admin_enqueue_scripts', [ $this, 'enqueues' ] ); } /** * Load enqueues. * * @since 1.6.6 */ public function enqueues() { $min = wpforms_get_min_suffix(); wp_enqueue_script( 'wpforms-admin-education-core', WPFORMS_PLUGIN_URL . "assets/js/admin/education/core{$min}.js", [ 'jquery', 'jquery-confirm' ], WPFORMS_VERSION, true ); wp_localize_script( 'wpforms-admin-education-core', 'wpforms_education', $this->get_js_strings() ); } /** * Ajax handler for the education dismisses buttons. * * @since 1.6.6 */ public function ajax_dismiss() { // Run a security check. check_ajax_referer( 'wpforms-education', 'nonce' ); // Section is the identifier of the education feature. // For example, in Builder/DidYouKnow feature used 'builder-did-you-know-notifications' // and 'builder-did-you-know-confirmations'. $section = ! empty( $_POST['section'] ) ? sanitize_key( $_POST['section'] ) : ''; if ( empty( $section ) ) { wp_send_json_error( [ 'error' => esc_html__( 'Please specify a section.', 'wpforms-lite' ) ] ); } // Check for permissions. if ( ! $this->current_user_can() ) { wp_send_json_error( [ 'error' => esc_html__( 'You do not have permission to perform this action.', 'wpforms-lite' ) ] ); } $user_id = get_current_user_id(); $dismissed = get_user_meta( $user_id, 'wpforms_dismissed', true ); if ( empty( $dismissed ) ) { $dismissed = []; } $dismissed[ 'edu-' . $section ] = time(); update_user_meta( $user_id, 'wpforms_dismissed', $dismissed ); wp_send_json_success(); } /** * Whether the current user can perform an action. * * @since 1.8.0 * * @return bool */ private function current_user_can(): bool { // phpcs:ignore WordPress.Security.NonceVerification.Missing $page = ! empty( $_POST['page'] ) ? sanitize_key( $_POST['page'] ) : ''; // key is the same as $current_screen->id and the JS global 'pagenow', value - capability name(s). $caps = [ 'toplevel_page_wpforms-overview' => [ 'view_forms' ], 'wpforms_page_wpforms-builder' => [ 'edit_forms' ], 'wpforms_page_wpforms-entries' => [ 'view_entries' ], ]; return isset( $caps[ $page ] ) ? wpforms_current_user_can( $caps[ $page ] ) : wpforms_current_user_can(); } } Education/Helpers.php 0000644 00000007036 15252506741 0010607 0 ustar 00 <?php namespace WPForms\Admin\Education; /** * Helpers class. * * @since 1.8.5 */ class Helpers { /** * Get badge HTML. * * @since 1.8.5 * @since 1.8.6 Added `$icon` parameter. * * @param string $text Badge text. * @param string $size Badge size. * @param string $position Badge position. * @param string $color Badge color. * @param string $shape Badge shape. * @param string $icon Badge icon name in Font Awesome "format", e.g. `fa-check`, defaults to empty string. * * @return string */ public static function get_badge( string $text, string $size = 'sm', string $position = 'inline', string $color = 'titanium', string $shape = 'rounded', string $icon = '' ): string { if ( ! empty( $icon ) ) { $icon = self::get_inline_icon( $icon ); } return sprintf( '<span class="wpforms-badge wpforms-badge-%1$s wpforms-badge-%2$s wpforms-badge-%3$s wpforms-badge-%4$s">%5$s%6$s</span>', esc_attr( $size ), esc_attr( $position ), esc_attr( $color ), esc_attr( $shape ), wp_kses( $icon, [ 'i' => [ 'class' => [], 'aria-hidden' => [], ], ] ), esc_html( $text ) ); } /** * Print badge HTML. * * @since 1.8.5 * @since 1.8.6 Added `$icon` parameter. * * @param string $text Badge text. * @param string $size Badge size. * @param string $position Badge position. * @param string $color Badge color. * @param string $shape Badge shape. * @param string $icon Badge icon name in Font Awesome "format", e.g. `fa-check`, defaults to empty string. */ public static function print_badge( string $text, string $size = 'sm', string $position = 'inline', string $color = 'titanium', string $shape = 'rounded', string $icon = '' ) { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo self::get_badge( $text, $size, $position, $color, $shape, $icon ); } /** * Get addon badge HTML. * * @since 1.8.9 * * @param array $addon Addon data. * * @return string */ public static function get_addon_badge( array $addon ): string { // List of possible badges. $badges = [ 'recommended' => [ 'text' => esc_html__( 'Recommended', 'wpforms-lite' ), 'color' => 'green', 'icon' => 'fa-star', ], 'new' => [ 'text' => esc_html__( 'New', 'wpforms-lite' ), 'color' => 'blue', ], 'featured' => [ 'text' => esc_html__( 'Featured', 'wpforms-lite' ), 'color' => 'orange', ], ]; $badge = []; // Get first badge that exists. foreach ( $badges as $key => $value ) { if ( ! empty( $addon[ $key ] ) ) { $badge = $value; break; } } if ( empty( $badge ) ) { return ''; } return self::get_badge( $badge['text'], 'sm', 'inline', $badge['color'], 'rounded', $badge['icon'] ?? '' ); } /** * Get inline icon HTML. * * @since 1.8.6 * * @param string $name Font Awesome icon name, e.g. `fa-check`. * * @return string HTML markup for the icon element. */ public static function get_inline_icon( string $name ): string { return sprintf( '<i class="fa %1$s" aria-hidden="true"></i>', esc_attr( $name ) ); } /** * Get available education addons. * * @since 1.9.4 * * @return array */ public static function get_edu_addons(): array { static $addons = null; if ( $addons !== null ) { return $addons; } $addons_obj = wpforms()->obj( 'addons' ); if ( ! $addons_obj ) { $addons = []; return $addons; } $addons = $addons_obj->get_available(); return $addons; } } Education/Pointers/Payment.php 0000644 00000007204 15252506741 0012422 0 ustar 00 <?php namespace WPForms\Admin\Education\Pointers; use WPForms\Integrations\Stripe; use WPForms\Admin\Payments\Views\Overview\Page as PaymentsPage; use WPForms\Migrations\Base as MigrationsBase; /** * Education class for handling Payments education pointer functionality. * * This class extends the abstract Pointers class and provides functionality * specific to the Payments feature in WPForms. * * @since 1.8.8 */ class Payment extends Pointer { /** * Unique ID for the pointer. * * @since 1.8.8 * * @var string */ protected $pointer_id = 'admin_menu_payments'; /** * Selector for the pointer. * * @since 1.8.8 * * @var string */ protected $selector = '[href$="-payments"]'; /** * Make sure that the pointer is visible across other dashboard pages. * * @since 1.8.8 * * @var bool */ protected $top_level_visible = true; /** * Determine if the Payments feature pointer is allowed to load. * * Checks various conditions to determine if the Payments feature pointer * should be allowed to load for the current user. * * @since 1.8.8 * * @return bool */ protected function allow_load(): bool { // Bail early if the user doesn't have a Lite, Basic, or Plus license. if ( ! in_array( $this->get_license_type(), [ 'lite', 'basic', 'plus' ], true ) ) { return false; } // Bail early if it has been less than 90 days since activation or the installation wasn't upgraded. if ( ! get_option( MigrationsBase::PREVIOUS_CORE_VERSION_OPTION_NAME ) || wpforms_get_activated_timestamp() > ( time() - 90 * DAY_IN_SECONDS ) ) { return false; } // Bail early if a Stripe account is connected. if ( Stripe\Helpers::has_stripe_keys() ) { return false; } // Bail early if the user doesn't have the capability to manage options. if ( ! wpforms_current_user_can() ) { return false; } // Bail early if there are no published forms. $forms_obj = wpforms()->obj( 'form' ); return $forms_obj && $forms_obj->forms_exist(); } /** * Enqueue assets for the pointer. * * @since 1.8.8 */ public function enqueue_assets() { // Enqueue the pointer static assets. parent::enqueue_assets(); $min = wpforms_get_min_suffix(); wp_enqueue_script( 'wpforms-education-pointers-payment', WPFORMS_PLUGIN_URL . "assets/js/admin/education/pointers/payment{$min}.js", [ 'wp-pointer' ], WPFORMS_VERSION, true ); $admin_l10n = [ 'pointer' => sanitize_key( $this->pointer_id ), 'nonce' => sanitize_text_field( $this->get_nonce_token() ), ]; wp_localize_script( 'wpforms-education-pointers-payment', 'wpforms_education_pointers_payment', $admin_l10n ); } /** * Set arguments for the Payments feature pointer. * * @since 1.8.8 * * @noinspection HtmlUnknownTarget */ protected function set_args() { $this->args['title'] = __( 'Payment and Donation Forms are here!', 'wpforms-lite' ); $this->args['message'] = sprintf( /* translators: %1$s - Payments page URL. */ __( 'Now available for you: create forms that accept credit cards, Apple Pay, and Google Pay payments. Visit our new <a href="%1$s" id="wpforms-education-pointers-payments">Payments area</a> to get started.', 'wpforms-lite' ), esc_url( PaymentsPage::get_url() ) ); } /** * Retrieve the current installation license type in the lowercase. * If no license type is found, defaults to 'lite'. * * @since 1.8.8 * * @return string */ private function get_license_type(): string { $type = wpforms_get_license_type(); // Set the default to 'lite' if no license type is detected. if ( empty( $type ) ) { $type = 'lite'; } return $type; } } Education/Pointers/Pointer.php 0000644 00000024237 15252506741 0012432 0 ustar 00 <?php namespace WPForms\Admin\Education\Pointers; /** * Abstract class representing Pointers functionality. * * This abstract class provides a foundation for implementing pointers in WPForms. * Child classes should extend this class and implement the necessary methods to set properties and allow loading. * * The class separates concerns by implementing methods for different functionalities such as initializing pointers, * handling interactions, printing scripts, etc., which enhances code maintainability and security. * Additionally, the class is designed to be abstract, allowing for customization and extension while enforcing certain security measures in child classes. * * @since 1.8.8 */ abstract class Pointer { /** * Unique ID for the pointer. * * @since 1.8.8 * * @var string */ protected $pointer_id; /** * Selector for the pointer. * * @since 1.8.8 * * @var string */ protected $selector; /** * Arguments for the pointer. * * @since 1.8.8 * * @var array */ protected $args; /** * Top-level menu selector. * * @since 1.8.8 * * @var string */ private $top_level_menu = '#toplevel_page_wpforms-overview'; /** * Determines whether the pointer should be visible outside the "WPForms" primary menu. * Note that setting this property to true will display the pointer on other dashboard pages as well. * * @since 1.8.8 * * @var string */ protected $top_level_visible = false; /** * Option name for storing interactions with pointers. * * @since 1.8.8 */ private const OPTION_NAME = 'wpforms_pointers'; /** * Initialize the pointer. * * @since 1.8.8 */ public function init(): void { // If loading is not allowed, or if the pointer is already dismissed, return. if ( ! $this->allow_display() || ! $this->allow_load() ) { return; } // Set initial arguments. $this->set_initial_args(); // Register hooks. $this->hooks(); } /** * Check if the pointer is already dismissed or interacted with. * * @since 1.8.8 * * @return bool */ private function allow_display(): bool { // If the pointer ID is empty, return. // Check if announcements are allowed to be displayed. if ( empty( $this->pointer_id ) || wpforms_setting( 'hide-announcements' ) ) { return false; } // Get pointers. $pointers = (array) get_option( self::OPTION_NAME, [] ); // Check if the pointer ID exists in the engagement list. if ( isset( $pointers['engagement'] ) && in_array( $this->pointer_id, (array) $pointers['engagement'], true ) ) { return false; } // Check if the pointer ID exists in the dismissed list. if ( isset( $pointers['dismiss'] ) && in_array( $this->pointer_id, (array) $pointers['dismiss'], true ) ) { return false; } return true; } /** * Register hooks for the pointer. * * @since 1.8.8 */ private function hooks(): void { // Enqueue assets. add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] ); // Print the pointer script. add_action( 'admin_print_footer_scripts', [ $this, 'print_script' ] ); // Add Ajax callback for the engagement. add_action( 'wp_ajax_wpforms_education_pointers_engagement', [ $this, 'engagement_callback' ] ); // Add Ajax callback for dismissing the pointer. add_action( 'wp_ajax_wpforms_education_pointers_dismiss', [ $this, 'dismiss_callback' ] ); } /** * Enqueue assets for the pointer. * * @since 1.8.8 */ public function enqueue_assets() { // Enqueue the pointer CSS. wp_enqueue_style( 'wp-pointer' ); // Enqueue the pointer script. wp_enqueue_script( 'wp-pointer' ); } /** * Print the pointer script. * * @since 1.8.8 */ public function print_script(): void { // Encode the $args array into JSON format. $encoded_args = $this->get_prepared_args(); if ( empty( $encoded_args ) ) { return; } // Sanitize pointer ID and selector. $pointer_id = sanitize_text_field( $this->pointer_id ); $selector = sanitize_text_field( $this->get_selector() ); // Get the admin-ajax URL. $ajaxurl = esc_url_raw( admin_url( 'admin-ajax.php' ) ); // Create nonce for the pointer. $nonce = sanitize_text_field( $this->get_nonce_token() ); // Menu flyout selector. $menu_flyout = "{$this->top_level_menu}:not(.wp-menu-open)"; // Inline CSS style id. $inline_css_id = "wpforms-{$pointer_id}-inline-css"; // The type of echo being used in this PHP code is a HEREDOC syntax. // HEREDOC allows you to create strings that span multiple lines without // needing to concatenate them with dots (.) as you would with double quotes. // phpcs:disable echo <<<HTML <script type="text/javascript"> ( function( $ ) { let options = $encoded_args, setup; if ( ! options ) { return; } options = $.extend( options, { show: function() { if ( ! $( '#$inline_css_id' ).length && $( '$menu_flyout' ).length ) { $( '<style id="$inline_css_id">' ).text( '$menu_flyout:after, $menu_flyout .wp-submenu-wrap{ display: none }' ).appendTo( 'head' ); } }, close: function() { $( '#$inline_css_id' ).remove(); $.post( '$ajaxurl', { pointer_id: '$pointer_id', _ajax_nonce: '$nonce', action: 'wpforms_education_pointers_dismiss', } ); } } ); setup = function() { $( '$selector' ).first().pointer( options ).pointer( 'open' ); }; if ( options.position && options.position.defer_loading ) { $( window ).on( 'load.wp-pointers', setup ); } else { $( function() { setup(); } ); } } )( jQuery ); </script> HTML; // phpcs:enable } /** * Callback function for engaging with a pointer. * * This function is triggered via AJAX when a user interacts with a pointer, indicating engagement. * * @since 1.8.8 */ public function engagement_callback(): void { check_ajax_referer( $this->pointer_id, '_ajax_nonce' ); if ( ! wpforms_current_user_can() ) { wp_send_json_error(); } [ $pointer_id, $pointers ] = $this->handle_pointer_interaction(); // Add the current pointer to the engagement list. $pointers['engagement'][] = $pointer_id; // Update the pointer state. update_option( self::OPTION_NAME, $pointers ); // Indicate that the pointer was engaged. wp_send_json_success(); } /** * Ajax callback for dismissing the pointer. * * @since 1.8.8 */ public function dismiss_callback(): void { check_ajax_referer( $this->pointer_id, '_ajax_nonce' ); if ( ! wpforms_current_user_can() ) { wp_send_json_error(); } [ $pointer_id, $pointers ] = $this->handle_pointer_interaction(); // Add the current pointer to the dismissed list. $pointers['dismiss'][] = $pointer_id; // Update the pointer state. update_option( self::OPTION_NAME, $pointers ); // Indicate that the pointer was dismissed. wp_send_json_success(); } /** * Get nonce for the pointer. * * @since 1.8.8 * * @return string */ protected function get_nonce_token(): string { return wp_create_nonce( $this->pointer_id ); } /** * Handle pointer interaction via AJAX. * * @since 1.8.8 * * @return array Pointer ID and pointers state. */ private function handle_pointer_interaction(): array { // Check if the request is valid. check_ajax_referer( $this->pointer_id ); // Get the pointer ID from the request. $pointer_id = isset( $_POST['pointer_id'] ) ? sanitize_key( $_POST['pointer_id'] ) : ''; // If the pointer ID is empty, return an error response. if ( empty( $pointer_id ) ) { wp_send_json_error(); } // Get the current pointers state. $pointers = (array) get_option( self::OPTION_NAME, [ 'engagement' => [], 'dismiss' => [], ] ); return [ $pointer_id, $pointers ]; } /** * Set initial arguments to use in a pointer. * * @since 1.8.8 */ private function set_initial_args(): void { // Set default arguments. $this->args = [ 'content' => '', 'pointerWidth' => 395, 'position' => [ 'edge' => 'left', 'align' => 'center', ], ]; // Set additional arguments for the pointer. $this->set_args(); } /** * Retrieves the selector based on conditions. * * @since 1.8.8 * * @return string */ private function get_selector(): string { // If the sublevel menu is defined, and it's an admin page, return the combined selector. if ( ! empty( $this->selector ) && wpforms_is_admin_page() ) { return "{$this->top_level_menu} {$this->selector}"; } // Default returns the top-level menu. return $this->top_level_menu; } /** * Prepare and encode args for the pointer. * * @since 1.8.8 * * @return string */ private function get_prepared_args(): string { // Retrieve title and message from an argument array, fallback to empty strings if not set. $title = $this->args['title'] ?? ''; $message = $this->args['message'] ?? ''; // Return early if both title and message are empty. if ( empty( $message ) ) { return ''; } // Pointer markup uses <h3> tag for the title and <p> tag for the message. $content = ! empty( $title ) ? sprintf( '<h3>%s</h3>', esc_html( $title ) ) : ''; $content .= sprintf( '<p style="font-size:14px">%s</p>', wp_kses( $message, $this->get_allowed_html() ) ); $this->args['content'] = $content; // Unset title and message to clean up an argument array. unset( $this->args['title'], $this->args['message'] ); // If RTL and position edge are 'left', switch it to 'right'. if ( ! empty( $this->args['position']['edge'] ) && $this->args['position']['edge'] === 'left' && is_rtl() ) { $this->args['position']['edge'] = 'right'; } // Encode arguments array to JSON. return wp_json_encode( $this->args ); } /** * Get allowed HTML tags for wp_kses. * * @since 1.8.8 * * @return array */ private function get_allowed_html(): array { return [ 'a' => [ 'id' => [], 'class' => [], 'href' => [], 'target' => [], 'rel' => [], ], 'strong' => [], 'em' => [], 'br' => [], ]; } /** * Check if loading of the pointer is allowed. * * @since 1.8.8 * * @return bool */ abstract protected function allow_load(): bool; /** * Set arguments for the pointer. * * @since 1.8.8 */ abstract protected function set_args(); } Education/FeatureTooltip.php 0000644 00000007161 15252506741 0012152 0 ustar 00 <?php namespace WPForms\Admin\Education; /** * Reusable feature-discovery tooltip component. * * Renders a configurable tooltip card with title, optional badge, body text, * optional CTA button, optional learn-more link, and a dismiss button. * Consumers pass a config array; the component handles rendering, escaping, * and positioning script enqueue. * * @since 2.0.0 */ class FeatureTooltip { /** * Whether the positioning script has been enqueued this request. * * @since 2.0.0 * * @var bool */ private $script_enqueued = false; /** * Initialize. * * @since 2.0.0 */ public function init(): void { $this->hooks(); } /** * Register hooks. * * @since 2.0.0 */ private function hooks(): void { add_action( 'admin_enqueue_scripts', [ $this, 'register_scripts' ] ); } /** * Register (not enqueue) the positioning script so it is available * when get() enqueues it on demand. * * @since 2.0.0 */ public function register_scripts(): void { $min = wpforms_get_min_suffix(); wp_register_script( 'wpforms-education-feature-tooltip', WPFORMS_PLUGIN_URL . "assets/js/admin/education/feature-tooltip{$min}.js", [ 'wpforms-admin-education-core' ], WPFORMS_VERSION, true ); } /** * Return the tooltip HTML. * * Required config keys: title, text, section. * Optional: badge, learn_more_url, learn_more_text, button (array with text, url, classes, data). * * @since 2.0.0 * * @param array $args Tooltip configuration. * * @return string Tooltip HTML or empty string on invalid config. */ public function get( array $args ): string { $required = [ 'title', 'text', 'section' ]; foreach ( $required as $key ) { if ( empty( $args[ $key ] ) ) { return ''; } } $button_markup = ''; if ( ! empty( $args['button'] ) && is_array( $args['button'] ) ) { $button_markup = $this->get_button_markup( $args['button'] ); } $learn_more_text = $args['learn_more_text'] ?? esc_html__( 'Learn More', 'wpforms-lite' ); if ( ! $this->script_enqueued ) { wp_enqueue_script( 'wpforms-education-feature-tooltip' ); $this->script_enqueued = true; } return wpforms_render( 'education/feature-tooltip', [ 'title' => $args['title'], 'badge' => $args['badge'] ?? '', 'text' => $args['text'], 'section' => $args['section'], 'button_markup' => $button_markup, 'learn_more_url' => $args['learn_more_url'] ?? '', 'learn_more_text' => $learn_more_text, ], true ); } /** * Echo the tooltip HTML. * * @since 2.0.0 * * @param array $args Tooltip configuration. */ public function render( array $args ): void { echo $this->get( $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } /** * Build the CTA button anchor tag from structured config. * * @since 2.0.0 * * @param array $button Button config with text, url, classes, and optional data. * * @return string Anchor tag HTML or empty string if text/url missing. */ private function get_button_markup( array $button ): string { if ( empty( $button['text'] ) || empty( $button['url'] ) ) { return ''; } $data_attrs = ''; if ( ! empty( $button['data'] ) && is_array( $button['data'] ) ) { foreach ( $button['data'] as $key => $value ) { $data_attrs .= sprintf( ' data-%s="%s"', esc_attr( $key ), esc_attr( $value ) ); } } return sprintf( '<a href="%1$s" class="%2$s"%3$s>%4$s</a>', esc_url( $button['url'] ), esc_attr( $button['classes'] ?? 'wpforms-btn wpforms-btn-sm wpforms-btn-orange' ), $data_attrs, esc_html( $button['text'] ) ); } } Challenge.php 0000644 00000043157 15252506741 0007160 0 ustar 00 <?php namespace WPForms\Admin; /** * Challenge and guide a user to set up a first form once WPForms is installed. * * @since 1.5.0 * @since 1.6.2 Challenge v2 */ class Challenge { /** * Number of minutes to complete the Challenge. * * @since 1.5.0 * * @var int */ protected $minutes = 5; /** * Initialize. * * @since 1.6.2 */ public function init() { if ( current_user_can( wpforms_get_capability_manage_options() ) ) { $this->hooks(); } } /** * Hooks. * * @since 1.5.0 */ public function hooks() { add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] ); add_action( 'wpforms_builder_init', [ $this, 'init_challenge' ] ); add_action( 'admin_footer', [ $this, 'challenge_html' ] ); add_action( 'wpforms_welcome_intro_after', [ $this, 'welcome_html' ] ); add_action( 'wp_ajax_wpforms_challenge_save_option', [ $this, 'save_challenge_option_ajax' ] ); add_action( 'wp_ajax_wpforms_challenge_send_contact_form', [ $this, 'send_contact_form_ajax' ] ); } /** * Check if the current page is related to Challenge. * * @since 1.5.0 */ public function is_challenge_page() { return wpforms_is_admin_page() || $this->is_builder_page() || $this->is_form_embed_page(); } /** * Check if the current page is a forms builder page related to Challenge. * * @since 1.5.0 * * @return bool */ public function is_builder_page() { if ( ! wpforms_is_admin_page( 'builder' ) ) { return false; } if ( ! $this->challenge_active() && ! $this->challenge_inited() ) { return false; } $step = (int) $this->get_challenge_option( 'step' ); $form_id = (int) $this->get_challenge_option( 'form_id' ); if ( $form_id && $step < 2 ) { return false; } $current_form_id = isset( $_GET['form_id'] ) ? (int) $_GET['form_id'] : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $is_new_form = isset( $_GET['newform'] ) ? (int) $_GET['newform'] : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( $is_new_form && $step !== 2 ) { return false; } if ( ! $is_new_form && $form_id !== $current_form_id && $step >= 2 ) { // In case if user skipped the Challenge by closing the browser window or exiting the builder, // we need to set the previous Challenge as `canceled`. // Otherwise, the Form Embed Wizard will think that the Challenge is active. $this->set_challenge_option( [ 'status' => 'skipped', 'finished_date_gmt' => current_time( 'mysql', true ), ] ); return false; } return true; } /** * Check if the current page is a form embed page edit related to Challenge. * * @since 1.5.0 * * @return bool */ public function is_form_embed_page() { if ( ! function_exists( 'get_current_screen' ) || ! is_admin() || ! is_user_logged_in() ) { return false; } $screen = get_current_screen(); if ( ! isset( $screen->id ) || $screen->id !== 'page' || ! $this->challenge_active() ) { return false; } $step = $this->get_challenge_option( 'step' ); if ( ! in_array( $step, [ 3, 4, 5 ], true ) ) { return false; } $embed_page = $this->get_challenge_option( 'embed_page' ); $is_embed_page = false; if ( isset( $screen->action ) && $screen->action === 'add' && $embed_page === 0 ) { $is_embed_page = true; } if ( isset( $_GET['post'] ) && $embed_page === (int) $_GET['post'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $is_embed_page = true; } if ( $is_embed_page && $step < 4 ) { $this->set_challenge_option( [ 'step' => 4 ] ); } return $is_embed_page; } /** * Load scripts and styles. * * @since 1.5.0 */ public function enqueue_scripts() { if ( ! $this->challenge_can_start() && ! $this->challenge_active() ) { return; } $min = wpforms_get_min_suffix(); if ( $this->is_challenge_page() ) { wp_enqueue_style( 'wpforms-challenge', WPFORMS_PLUGIN_URL . "assets/css/challenge{$min}.css", [], WPFORMS_VERSION ); wp_enqueue_script( 'wpforms-challenge-admin', WPFORMS_PLUGIN_URL . "assets/js/admin/challenge/challenge-admin{$min}.js", [ 'jquery' ], WPFORMS_VERSION, true ); wp_localize_script( 'wpforms-challenge-admin', 'wpforms_challenge_admin', [ 'nonce' => wp_create_nonce( 'wpforms_challenge_ajax_nonce' ), 'minutes_left' => absint( $this->minutes ), 'option' => $this->get_challenge_option(), 'frozen_tooltip' => esc_html__( 'Challenge is frozen.', 'wpforms-lite' ), ] ); } if ( $this->is_builder_page() || $this->is_form_embed_page() ) { wp_enqueue_style( 'tooltipster', WPFORMS_PLUGIN_URL . 'assets/lib/jquery.tooltipster/jquery.tooltipster.min.css', null, '4.2.6' ); wp_enqueue_script( 'tooltipster', WPFORMS_PLUGIN_URL . 'assets/lib/jquery.tooltipster/jquery.tooltipster.min.js', [ 'jquery' ], '4.2.6', true ); wp_enqueue_script( 'wpforms-challenge-core', WPFORMS_PLUGIN_URL . "assets/js/admin/challenge/challenge-core{$min}.js", [ 'jquery', 'tooltipster', 'wpforms-challenge-admin', 'wpforms-generic-utils' ], WPFORMS_VERSION, true ); } if ( $this->is_builder_page() ) { wp_enqueue_script( 'wpforms-challenge-builder', WPFORMS_PLUGIN_URL . "assets/js/admin/challenge/challenge-builder{$min}.js", [ 'jquery', 'tooltipster', 'wpforms-challenge-core', 'wpforms-builder' ], WPFORMS_VERSION, true ); } if ( $this->is_form_embed_page() ) { wp_enqueue_style( 'wpforms-font-awesome', WPFORMS_PLUGIN_URL . 'assets/lib/font-awesome/css/all.min.css', null, '7.0.1' ); // FontAwesome v4 compatibility shims. wp_enqueue_style( 'wpforms-font-awesome-v4-shim', WPFORMS_PLUGIN_URL . 'assets/lib/font-awesome/css/v4-shims.min.css', null, '4.7.0' ); wp_enqueue_script( 'wpforms-challenge-embed', WPFORMS_PLUGIN_URL . "assets/js/admin/challenge/challenge-embed{$min}.js", [ 'jquery', 'tooltipster', 'wpforms-challenge-core' ], WPFORMS_VERSION, true ); } } /** * Get 'wpforms_challenge' option schema. * * @since 1.5.0 * * @return array */ public function get_challenge_option_schema() { return [ 'status' => '', 'step' => 0, 'user_id' => get_current_user_id(), 'form_id' => 0, 'embed_page' => 0, 'embed_page_title' => '', 'started_date_gmt' => '', 'finished_date_gmt' => '', 'seconds_spent' => 0, 'seconds_left' => 0, 'feedback_sent' => false, 'feedback_contact_me' => false, 'window_closed' => '', ]; } /** * Get Challenge parameter(s) from Challenge option. * * @since 1.5.0 * * @param array|string|null $query Query using 'wpforms_challenge' schema keys. * * @return array|mixed */ public function get_challenge_option( $query = null ) { if ( ! $query ) { return get_option( 'wpforms_challenge' ); } $return_single = false; if ( ! is_array( $query ) ) { $return_single = true; $query = [ $query ]; } $query = array_flip( $query ); $option = get_option( 'wpforms_challenge' ); if ( ! $option || ! is_array( $option ) ) { return array_intersect_key( $this->get_challenge_option_schema(), $query ); } $result = array_intersect_key( $option, $query ); if ( $return_single ) { $result = reset( $result ); } return $result; } /** * Set Challenge parameter(s) to Challenge option. * * @since 1.5.0 * * @param array $query Query using 'wpforms_challenge' schema keys. */ public function set_challenge_option( $query ) { if ( empty( $query ) || ! is_array( $query ) ) { return; } $schema = $this->get_challenge_option_schema(); $replace = array_intersect_key( $query, $schema ); if ( ! $replace ) { return; } // Validate and sanitize the data. foreach ( $replace as $key => $value ) { if ( in_array( $key, [ 'step', 'user_id', 'form_id', 'embed_page', 'seconds_spent', 'seconds_left' ], true ) ) { $replace[ $key ] = absint( $value ); continue; } if ( in_array( $key, [ 'feedback_sent', 'feedback_contact_me' ], true ) ) { $replace[ $key ] = wp_validate_boolean( $value ); continue; } $replace[ $key ] = sanitize_text_field( $value ); } $option = get_option( 'wpforms_challenge' ); $option = ! $option || ! is_array( $option ) ? $schema : $option; update_option( 'wpforms_challenge', array_merge( $option, $replace ) ); } /** * Check if any forms are present on a site. * * @since 1.5.0 * * @retun bool */ public function website_has_forms() { // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.SuppressFilters_suppress_filters return (bool) wpforms()->obj( 'form' )->get( '', [ 'numberposts' => 1, 'nopaging' => false, 'fields' => 'ids', 'no_found_rows' => true, 'update_post_meta_cache' => false, 'update_post_term_cache' => false, 'suppress_filters' => true, // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.SuppressFilters_suppress_filters ] ); } /** * Check if Challenge was started. * * @since 1.5.0 * * @return bool */ public function challenge_started() { return $this->get_challenge_option( 'status' ) === 'started'; } /** * Check if Challenge was initialized. * * @since 1.6.2 * * @return bool */ public function challenge_inited() { return $this->get_challenge_option( 'status' ) === 'inited'; } /** * Check if Challenge was paused. * * @since 1.6.2 * * @return bool */ public function challenge_paused() { return $this->get_challenge_option( 'status' ) === 'paused'; } /** * Check if Challenge was finished. * * @since 1.5.0 * * @return bool */ public function challenge_finished() { $status = $this->get_challenge_option( 'status' ); return in_array( $status, [ 'completed', 'canceled', 'skipped' ], true ); } /** * Check if Challenge is in progress. * * @since 1.5.0 * * @return bool */ public function challenge_active() { return ( $this->challenge_inited() || $this->challenge_started() || $this->challenge_paused() ) && ! $this->challenge_finished(); } /** * Force Challenge to start. * * @since 1.6.2 * * @return bool */ public function challenge_force_start() { /** * Allow force start Challenge for testing purposes. * * @since 1.6.2.2 * * @param bool $is_forced True if Challenge should be started. False by default. */ return (bool) apply_filters( 'wpforms_admin_challenge_force_start', false ); } /** * Check if Challenge can be started. * * @since 1.5.0 * * @return bool */ public function challenge_can_start() { static $can_start = null; if ( $can_start !== null ) { return $can_start; } if ( $this->challenge_force_skip() ) { $can_start = false; } // Challenge is only available on WPForms admin pages or Builder page. if ( ! wpforms_is_admin_page() && ! wpforms_is_admin_page( 'builder' ) ) { $can_start = false; // No need to check something else in this case. return false; } // The challenge should not start if this is the Forms' Overview page. if ( wpforms_is_admin_page( 'overview' ) ) { $can_start = false; // No need to check something else in this case. return false; } // Force start the Challenge. if ( $this->challenge_force_start() && ! $this->is_builder_page() && ! $this->is_form_embed_page() ) { $can_start = true; // No need to check something else in this case. return true; } if ( $this->challenge_finished() ) { $can_start = false; } if ( $this->website_has_forms() ) { $can_start = false; } if ( $can_start === null ) { $can_start = true; } return $can_start; } /** * Start the Challenge in Form Builder. * * @since 1.5.0 */ public function init_challenge() { if ( ! $this->challenge_can_start() ) { return; } $this->set_challenge_option( wp_parse_args( [ 'status' => 'inited' ], $this->get_challenge_option_schema() ) ); } /** * Include Challenge HTML. * * @since 1.5.0 */ public function challenge_html() { if ( $this->challenge_force_skip() || ( $this->challenge_finished() && ! $this->challenge_force_start() ) ) { return; } if ( wpforms_is_admin_page() && ! wpforms_is_admin_page( 'getting-started' ) && $this->challenge_can_start() ) { // Before showing the Challenge in the `start` state we should reset the option. // In this way we ensure the Challenge will not appear somewhere in the builder where it is not should be. $this->set_challenge_option( [ 'status' => '' ] ); $this->challenge_modal_html( 'start' ); } if ( $this->is_builder_page() ) { $this->challenge_modal_html( 'progress' ); $this->challenge_builder_templates_html(); } if ( $this->is_form_embed_page() ) { $this->challenge_modal_html( 'progress' ); $this->challenge_embed_templates_html(); } } /** * Include Challenge main modal window HTML. * * @since 1.5.0 * * @param string $state State of Challenge ('start' or 'progress'). */ public function challenge_modal_html( $state ) { echo wpforms_render( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 'admin/challenge/modal', [ 'state' => $state, 'step' => $this->get_challenge_option( 'step' ), 'minutes' => $this->minutes, ], true ); } /** * Include Challenge HTML templates specific to Form Builder. * * @since 1.5.0 */ public function challenge_builder_templates_html() { echo wpforms_render( 'admin/challenge/builder' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } /** * Include Challenge HTML templates specific to form embed page. * * @since 1.5.0 */ public function challenge_embed_templates_html() { /** * Filter the content of the Challenge Congrats popup footer. * * @since 1.7.4 * * @param string $footer Footer markup. */ $congrats_popup_footer = apply_filters( 'wpforms_admin_challenge_embed_template_congrats_popup_footer', '' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wpforms_render( 'admin/challenge/embed', [ 'minutes' => $this->minutes, 'congrats_popup_footer' => $congrats_popup_footer, ], true ); } /** * Include Challenge CTA on WPForms welcome activation screen. * * @since 1.5.0 */ public function welcome_html() { if ( $this->challenge_can_start() ) { echo wpforms_render( 'admin/challenge/welcome' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } } /** * Save Challenge data via AJAX. * * @since 1.5.0 */ public function save_challenge_option_ajax() { check_admin_referer( 'wpforms_challenge_ajax_nonce' ); if ( empty( $_POST['option_data'] ) ) { wp_send_json_error(); } $schema = $this->get_challenge_option_schema(); $query = []; foreach ( $schema as $key => $value ) { if ( isset( $_POST['option_data'][ $key ] ) ) { $query[ $key ] = sanitize_text_field( wp_unslash( $_POST['option_data'][ $key ] ) ); } } if ( empty( $query ) ) { wp_send_json_error(); } if ( ! empty( $query['status'] ) && $query['status'] === 'started' ) { $query['started_date_gmt'] = current_time( 'mysql', true ); } if ( ! empty( $query['status'] ) && in_array( $query['status'], [ 'completed', 'canceled', 'skipped' ], true ) ) { $query['finished_date_gmt'] = current_time( 'mysql', true ); } if ( ! empty( $query['status'] ) && $query['status'] === 'skipped' ) { $query['started_date_gmt'] = current_time( 'mysql', true ); $query['finished_date_gmt'] = $query['started_date_gmt']; } $this->set_challenge_option( $query ); wp_send_json_success(); } /** * Send contact form to wpforms.com via AJAX. * * @since 1.5.0 */ public function send_contact_form_ajax() { check_admin_referer( 'wpforms_challenge_ajax_nonce' ); $url = 'https://wpforms.com/wpforms-challenge-feedback/'; $message = ! empty( $_POST['contact_data']['message'] ) ? sanitize_textarea_field( wp_unslash( $_POST['contact_data']['message'] ) ) : ''; $email = ''; if ( ( ! empty( $_POST['contact_data']['contact_me'] ) && $_POST['contact_data']['contact_me'] === 'true' ) || wpforms()->is_pro() ) { $current_user = wp_get_current_user(); $email = $current_user->user_email; $this->set_challenge_option( [ 'feedback_contact_me' => true ] ); } if ( empty( $message ) && empty( $email ) ) { wp_send_json_error(); } $data = [ 'body' => [ 'wpforms' => [ 'id' => 296355, 'submit' => 'wpforms-submit', 'fields' => [ 2 => $message, 3 => $email, 4 => $this->get_challenge_license_type(), 5 => wpforms()->version, 6 => wpforms_get_license_key(), ], ], ], ]; $response = wp_remote_post( $url, $data ); if ( is_wp_error( $response ) ) { wp_send_json_error(); } $this->set_challenge_option( [ 'feedback_sent' => true ] ); wp_send_json_success(); } /** * Get the current WPForms license type as it pertains to the challenge feedback form. * * @since 1.8.1 * * @return string The currently active license type. */ private function get_challenge_license_type() { $license_type = wpforms_get_license_type(); if ( $license_type === false ) { $license_type = wpforms()->is_pro() ? 'Unknown' : 'Lite'; } return ucfirst( $license_type ); } /** * Force WPForms Challenge to skip. * * @since 1.7.6 * * @return bool */ private function challenge_force_skip() { return defined( 'WPFORMS_SKIP_CHALLENGE' ) && WPFORMS_SKIP_CHALLENGE; } } Dashboard/Widget.php 0000644 00000020265 15252506741 0010403 0 ustar 00 <?php namespace WPForms\Admin\Dashboard; /** * Class Widget. * * @since 1.7.3 */ abstract class Widget { /** * Instance slug. * * @since 1.7.4 * * @var string */ const SLUG = 'dash_widget'; /** * Save a widget meta for a current user using AJAX. * * @since 1.7.4 */ public function save_widget_meta_ajax() { check_ajax_referer( 'wpforms_' . static::SLUG . '_nonce' ); $meta = ! empty( $_POST['meta'] ) ? sanitize_key( $_POST['meta'] ) : ''; $value = ! empty( $_POST['value'] ) ? absint( $_POST['value'] ) : 0; $this->widget_meta( 'set', $meta, $value ); exit(); } /** * Get/set a widget meta. * * @since 1.7.4 * * @param string $action Possible value: 'get' or 'set'. * @param string $meta Meta name. * @param int $value Value to set. * * @return mixed */ protected function widget_meta( $action, $meta, $value = 0 ) { $allowed_actions = [ 'get', 'set' ]; if ( ! in_array( $action, $allowed_actions, true ) ) { return false; } $defaults = [ 'timespan' => $this->get_timespan_default(), 'active_form_id' => 0, 'hide_recommended_block' => 0, 'hide_graph' => 0, 'color_scheme' => 1, // 1 - wpforms, 2 - wp 'graph_style' => 2, // 1 - bar, 2 - line ]; if ( ! array_key_exists( $meta, $defaults ) ) { return false; } $meta_key = 'wpforms_' . static::SLUG . '_' . $meta; $user_id = get_current_user_id(); if ( $action === 'get' ) { $meta_value = absint( get_user_meta( $user_id, $meta_key, true ) ); // Return a default value from $defaults if $meta_value is empty. return empty( $meta_value ) ? $defaults[ $meta ] : $meta_value; } $value = absint( $value ); if ( $action === 'set' && ! empty( $value ) ) { return update_user_meta( $user_id, $meta_key, $value ); } if ( $action === 'set' && empty( $value ) ) { return delete_user_meta( $user_id, $meta_key ); } return false; } /** * Get the default timespan option. * * @since 1.7.4 * * @return int|null */ protected function get_timespan_default() { $options = $this->get_timespan_options(); $default = reset( $options ); return is_numeric( $default ) ? $default : null; } /** * Get timespan options (in days). * * @since 1.7.4 * * @return array */ protected function get_timespan_options(): array { $default = [ 7, 30 ]; $options = $default; // Apply deprecated filters. if ( function_exists( 'apply_filters_deprecated' ) ) { // phpcs:disable WPForms.Comments.PHPDocHooks.RequiredHookDocumentation, WPForms.PHP.ValidateHooks.InvalidHookName $options = apply_filters_deprecated( 'wpforms_dash_widget_chart_timespan_options', [ $options ], '5.0', 'wpforms_dash_widget_timespan_options' ); $options = apply_filters_deprecated( 'wpforms_dash_widget_forms_list_timespan_options', [ $options ], '5.0', 'wpforms_dash_widget_timespan_options' ); // phpcs:enable WPForms.Comments.PHPDocHooks.RequiredHookDocumentation, WPForms.PHP.ValidateHooks.InvalidHookName } else { // phpcs:disable WPForms.Comments.PHPDocHooks.RequiredHookDocumentation, WPForms.PHP.ValidateHooks.InvalidHookName $options = apply_filters( 'wpforms_dash_widget_chart_timespan_options', $options ); $options = apply_filters( 'wpforms_dash_widget_forms_list_timespan_options', $options ); // phpcs:enable WPForms.Comments.PHPDocHooks.RequiredHookDocumentation, WPForms.PHP.ValidateHooks.InvalidHookName } if ( ! is_array( $options ) ) { $options = $default; } $widget_slug = static::SLUG; // phpcs:disable WPForms.Comments.PHPDocHooks.RequiredHookDocumentation, WPForms.PHP.ValidateHooks.InvalidHookName $options = apply_filters( "wpforms_{$widget_slug}_timespan_options", $options ); // phpcs:enable WPForms.Comments.PHPDocHooks.RequiredHookDocumentation, WPForms.PHP.ValidateHooks.InvalidHookName if ( ! is_array( $options ) ) { return []; } $options = array_filter( $options, 'is_numeric' ); return empty( $options ) ? $default : $options; } /** * Widget settings HTML. * * @since 1.7.4 * * @param bool $enabled Is form fields should be enabled. */ protected function widget_settings_html( $enabled = true ) { $graph_style = $this->widget_meta( 'get', 'graph_style' ); $color_scheme = $this->widget_meta( 'get', 'color_scheme' ); echo wpforms_render( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 'admin/dashboard/widget/settings', [ 'graph_style' => $graph_style, 'color_scheme' => $color_scheme, 'enabled' => $enabled, ], true ); } /** * Return randomly chosen one of the recommended plugins. * * @since 1.7.3 * * @return array */ final protected function get_recommended_plugin(): array { $plugins = [ 'google-analytics-for-wordpress/googleanalytics.php' => [ 'name' => __( 'MonsterInsights', 'wpforms-lite' ), 'slug' => 'google-analytics-for-wordpress', 'more' => 'https://www.monsterinsights.com/', 'pro' => [ 'file' => 'google-analytics-premium/googleanalytics-premium.php', ], ], 'all-in-one-seo-pack/all_in_one_seo_pack.php' => [ 'name' => __( 'AIOSEO', 'wpforms-lite' ), 'slug' => 'all-in-one-seo-pack', 'more' => 'https://aioseo.com/', 'pro' => [ 'file' => 'all-in-one-seo-pack-pro/all_in_one_seo_pack.php', ], ], 'coming-soon/coming-soon.php' => [ 'name' => __( 'SeedProd', 'wpforms-lite' ), 'slug' => 'coming-soon', 'more' => 'https://www.seedprod.com/', 'pro' => [ 'file' => 'seedprod-coming-soon-pro-5/seedprod-coming-soon-pro-5.php', ], ], 'wp-mail-smtp/wp_mail_smtp.php' => [ 'name' => __( 'WP Mail SMTP', 'wpforms-lite' ), 'slug' => 'wp-mail-smtp', 'more' => 'https://wpmailsmtp.com/', 'pro' => [ 'file' => 'wp-mail-smtp-pro/wp_mail_smtp.php', ], ], ]; $installed = get_plugins(); foreach ( $plugins as $id => $plugin ) { if ( isset( $installed[ $id ] ) ) { unset( $plugins[ $id ] ); } if ( isset( $plugin['pro']['file'], $installed[ $plugin['pro']['file'] ] ) ) { unset( $plugins[ $id ] ); } } return $plugins ? $plugins[ array_rand( $plugins ) ] : []; } /** * Timespan select HTML. * * @since 1.7.4 * * @param int $active_form_id Currently preselected form ID. * @param bool $enabled If the select menu items should be enabled. */ protected function timespan_select_html( $active_form_id, $enabled = true ) { ?> <select id="wpforms-dash-widget-timespan" class="wpforms-dash-widget-select-timespan" title="<?php esc_attr_e( 'Select timespan', 'wpforms-lite' ); ?>" <?php echo ! empty( $active_form_id ) ? 'data-active-form-id="' . absint( $active_form_id ) . '"' : ''; ?>> <?php $this->timespan_options_html( $this->get_timespan_options(), $enabled ); ?> </select> <?php } /** * Timespan select options HTML. * * @since 1.7.4 * * @param array $options Timespan options (in days). * @param bool $enabled If the select menu items should be enabled. */ protected function timespan_options_html( $options, $enabled = true ) { $timespan = $this->widget_meta( 'get', 'timespan' ); foreach ( $options as $option ) : ?> <option value="<?php echo absint( $option ); ?>" <?php selected( $timespan, absint( $option ) ); ?> <?php disabled( ! $enabled ); ?>> <?php /* translators: %d - number of days. */ ?> <?php echo esc_html( sprintf( _n( 'Last %d day', 'Last %d days', absint( $option ), 'wpforms-lite' ), absint( $option ) ) ); ?> </option> <?php endforeach; } /** * Check if the current page is a dashboard page. * * @since 1.8.3 * * @return bool */ protected function is_dashboard_page(): bool { global $pagenow; // phpcs:ignore WordPress.Security.NonceVerification.Recommended return $pagenow === 'index.php' && empty( $_GET['page'] ); } /** * Check if is a dashboard widget ajax request. * * @since 1.8.3 * * @return bool */ protected function is_dashboard_widget_ajax_request(): bool { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return wpforms_is_admin_ajax() && isset( $_REQUEST['action'] ) && strpos( sanitize_key( $_REQUEST['action'] ), 'wpforms_dash_widget' ) !== false; } } Builder/Help.php 0000644 00000153142 15252506741 0007550 0 ustar 00 <?php namespace WPForms\Admin\Builder; /** * Form Builder Help Screen. * * @since 1.6.3 */ class Help { /** * Docs data. * * @since 1.6.4 * * @var array */ private $docs; /** * Initialize class. * * @since 1.6.3 */ public function init() { // Terminate initialization if not in builder. if ( ! wpforms_current_user_can( [ 'create_forms', 'edit_forms' ] ) || ! wpforms_is_admin_page( 'builder' ) ) { return; } $builder_help_cache = wpforms()->obj( 'builder_help_cache' ); $this->docs = $builder_help_cache ? $builder_help_cache->get() : []; $this->hooks(); } /** * Hooks. * * @since 1.6.3 */ private function hooks() { add_action( 'wpforms_builder_enqueues', [ $this, 'enqueues' ] ); add_action( 'wpforms_admin_page', [ $this, 'output' ], 20 ); } /** * Enqueue assets. * * @since 1.6.3 */ public function enqueues() { $min = wpforms_get_min_suffix(); wp_enqueue_script( 'wpforms-builder-help', WPFORMS_PLUGIN_URL . "assets/js/admin/builder/help{$min}.js", [ 'wpforms-builder' ], WPFORMS_VERSION, true ); wp_localize_script( 'wpforms-builder-help', 'wpforms_builder_help', $this->get_localized_data() ); } /** * Get localized data. * * @since 1.6.3 * * @return array Localized data. */ public function get_localized_data() { return [ 'docs' => $this->docs, 'categories' => $this->get_categories(), 'context' => [ 'terms' => $this->get_context_terms(), 'docs' => $this->get_context_docs(), ], ]; } /** * Get categories. * * @return array Categories data. * @since 1.6.3 * */ public function get_categories() { return [ 'getting-started' => esc_html__( 'Getting Started', 'wpforms-lite' ), 'form-creation' => esc_html__( 'Form Creation', 'wpforms-lite' ), 'entry-management' => esc_html__( 'Entry Management', 'wpforms-lite' ), 'form-management' => esc_html__( 'Form Management', 'wpforms-lite' ), 'marketing-integrations' => esc_html__( 'Marketing Integrations', 'wpforms-lite' ), 'payment-forms' => esc_html__( 'Payment Forms', 'wpforms-lite' ), 'payment-processing' => esc_html__( 'Payment Processing', 'wpforms-lite' ), 'spam-prevention-and-security' => esc_html__( 'Spam Prevention and Security', 'wpforms-lite' ), 'extending-functionality' => esc_html__( 'Extending Functionality', 'wpforms-lite' ), 'troubleshooting-and-support' => esc_html__( 'Troubleshooting and Support', 'wpforms-lite' ), ]; } /** * Get context search terms. * * @since 1.6.3 * * @return array Search terms by context. */ public function get_context_terms() { return [ 'new_form' => 'add form', 'setup' => 'form template', 'fields/add_fields' => 'add fields', 'fields/field_options' => 'field options', 'fields/field_options/text' => 'single line text', 'fields/field_options/textarea' => 'paragraph text', 'fields/field_options/number-slider' => 'number slider', 'fields/field_options/select' => 'dropdown', 'fields/field_options/radio' => 'multiple choice', 'fields/field_options/checkbox' => 'checkboxes', 'fields/field_options/gdpr-checkbox' => 'gdpr agreement', 'fields/field_options/email' => 'email', 'fields/field_options/address' => 'address', 'fields/field_options/url' => 'website/url', 'fields/field_options/name' => 'name', 'fields/field_options/hidden' => 'hidden', 'fields/field_options/html' => 'html', 'fields/field_options/content' => 'content', 'fields/field_options/pagebreak' => 'page break', 'fields/field_options/entry-preview' => 'entry preview', 'fields/field_options/password' => 'password', 'fields/field_options/date-time' => 'date time', 'fields/field_options/divider' => 'section divider', 'fields/field_options/phone' => 'phone', 'fields/field_options/number' => 'numbers', 'fields/field_options/file-upload' => 'file upload', 'fields/field_options/captcha' => 'custom captcha', 'fields/field_options/rating' => 'rating', 'fields/field_options/richtext' => 'rich text', 'fields/field_options/layout' => 'layout', 'fields/field_options/likert_scale' => 'likert scale', 'fields/field_options/payment-single' => 'single item', 'fields/field_options/payment-multiple' => 'multiple items', 'fields/field_options/payment-checkbox' => 'checkbox items', 'fields/field_options/payment-select' => 'dropdown items', 'fields/field_options/payment-total' => 'total', 'fields/field_options/paypal-commerce' => 'paypal checkout', 'fields/field_options/stripe-credit-card' => 'stripe credit card', 'fields/field_options/authorize_net' => 'authorize.net credit card', 'fields/field_options/square' => 'square credit card', 'fields/field_options/signature' => 'signature', 'fields/field_options/net_promoter_score' => 'net promoter score', 'fields/field_options/payment-coupon' => 'coupon', 'fields/field_options/repeater' => 'repeater', 'settings/general' => 'settings', 'settings/anti_spam' => 'spam', 'settings/themes' => 'themes', 'settings/notifications' => 'notification emails', 'settings/confirmation' => 'confirmation message', 'settings/lead_forms' => 'lead forms', 'settings/form_abandonment' => 'form abandonment', 'settings/post_submissions' => 'post submissions', 'settings/user_registration' => 'user registration', 'settings/surveys_polls' => 'surveys and polls', 'settings/conversational_forms' => 'conversational forms', 'settings/form_locker' => 'form locker', 'settings/form_pages' => 'form pages', 'settings/save_resume' => 'save and resume', 'settings/google_sheets' => 'google sheets', 'settings/dropbox' => 'dropbox', 'settings/google_calendar' => 'google calendar', 'settings/airtable' => 'airtable', 'settings/google_drive' => 'google drive', 'settings/notion' => 'notion', 'settings/webhooks' => 'webhooks', 'settings/entry_automation' => 'entry automation', 'settings/pdf' => 'pdf', 'settings/quiz' => 'quiz', 'providers' => '', 'providers/aweber' => 'aweber', 'providers/activecampaign' => 'activecampaign', 'providers/campaign_monitor' => 'campaign monitor', 'providers/constant_contact' => 'constant contact', 'providers/convertkit' => 'kit', 'providers/drip' => 'drip', 'providers/getresponse' => 'getresponse', 'providers/getresponse_v3' => 'getresponse', 'providers/mailchimp' => 'mailchimp', 'providers/mailchimpv3' => 'mailchimp', 'providers/mailerlite' => 'mailerlite', 'providers/mailpoet' => 'mailpoet', 'providers/make' => 'make', 'providers/n8n' => 'n8n', 'providers/zapier' => 'zapier', 'providers/salesforce' => 'salesforce', 'providers/sendgrid' => 'sendgrid', 'providers/sendinblue' => 'brevo', 'providers/slack' => 'slack', 'providers/hubspot' => 'hubspot', 'providers/klaviyo' => 'klaviyo', 'providers/twilio' => 'twilio', 'providers/pipedrive' => 'pipedrive', 'providers/zoho_crm' => 'zoho crm', 'providers/zoho-crm' => 'zoho crm', 'payments' => '', 'payments/paypal_commerce' => 'paypal commerce', 'payments/paypal_standard' => 'paypal standard', 'payments/stripe' => 'stripe', 'payments/authorize_net' => 'authorize.net', 'payments/square' => 'square', 'revisions' => 'revisions', ]; } /** * Get context (recommended) docs links. * * @since 1.6.3 * * @return array Docs links by search terms. */ public function get_context_docs_links() { return [ 'add form' => [ '/docs/creating-first-form/', '/docs/how-to-choose-the-right-form-field-for-your-forms/', '/docs/how-to-customize-the-submit-button/', '/docs/generating-forms-with-wpforms-ai/', ], 'new form' => [ '/docs/creating-first-form/', '/docs/how-to-choose-the-right-form-field-for-your-forms/', '/docs/how-to-customize-the-submit-button/', '/docs/generating-forms-with-wpforms-ai/', ], 'create form' => [ '/docs/creating-first-form/', '/docs/how-to-choose-the-right-form-field-for-your-forms/', '/docs/how-to-customize-the-submit-button/', '/docs/generating-forms-with-wpforms-ai/', ], 'form template' => [ '/docs/how-to-create-a-custom-form-template/', '/docs/generating-forms-with-wpforms-ai/', ], 'add fields' => [ '/docs/how-to-choose-the-right-form-field-for-your-forms/', ], 'recaptcha' => [ '/docs/setup-captcha-wpforms/', ], 'spam' => [ '/docs/how-to-prevent-spam-in-wpforms/', '/docs/setup-captcha-wpforms/', '/docs/how-to-install-and-use-custom-captcha-addon-in-wpforms/', '/docs/setting-up-akismet-anti-spam-protection/', '/docs/viewing-and-managing-spam-entries/', ], 'themes' => [ '/docs/styling-your-forms/', ], 'fields' => [ '/docs/how-to-choose-the-right-form-field-for-your-forms/', ], 'field options' => [ '/docs/how-to-customize-form-field-options/', ], 'field settings' => [ '/docs/how-to-customize-form-field-options/', ], 'conditional logic' => [ '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/setup-form-notification-wpforms/', '/docs/setup-form-confirmation-wpforms/', ], 'single line text' => [ '/docs/how-to-limit-words-or-characters-in-a-form-field/', '/docs/how-to-use-custom-input-masks/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', '/docs/calculations-addon/', ], 'paragraph' => [ '/docs/how-to-limit-words-or-characters-in-a-form-field/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', '/docs/calculations-addon/', ], 'paragraph text' => [ '/docs/how-to-limit-words-or-characters-in-a-form-field/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', '/docs/calculations-addon/', ], 'textarea' => [ '/docs/how-to-limit-words-or-characters-in-a-form-field/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', '/docs/calculations-addon/', ], 'input mask' => [ '/docs/how-to-use-custom-input-masks/', ], 'limit words' => [ '/docs/how-to-limit-words-or-characters-in-a-form-field/', ], 'limit characters' => [ '/docs/how-to-limit-words-or-characters-in-a-form-field/', ], 'style' => [ '/docs/how-to-style-wpforms-with-custom-css-beginners-guide/', '/docs/how-to-customize-the-style-of-individual-form-fields/', '/docs/how-to-add-custom-css-to-your-wpforms/', ], 'custom css' => [ '/docs/how-to-style-wpforms-with-custom-css-beginners-guide/', '/docs/how-to-customize-the-style-of-individual-form-fields/', '/docs/how-to-add-custom-css-to-your-wpforms/', ], 'css' => [ '/docs/how-to-style-wpforms-with-custom-css-beginners-guide/', '/docs/how-to-customize-the-style-of-individual-form-fields/', '/docs/how-to-add-custom-css-to-your-wpforms/', ], 'dropdown' => [ '/docs/how-to-allow-multiple-selections-to-a-dropdown-field-in-wpforms/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', '/docs/generating-form-choices-with-wpforms-ai/', ], 'select' => [ '/docs/how-to-allow-multiple-selections-to-a-dropdown-field-in-wpforms/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', '/docs/generating-form-choices-with-wpforms-ai/', ], 'multiple options' => [ '/docs/how-to-allow-multiple-selections-to-a-dropdown-field-in-wpforms/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', '/docs/generating-form-choices-with-wpforms-ai/', ], 'bulk add' => [ '/docs/how-to-bulk-add-choices-for-multiple-choice-checkbox-and-dropdown-fields/', ], 'multiple columns' => [ '/docs/how-to-use-the-layout-field-in-wpforms/', '/docs/how-to-create-a-multi-column-layout-for-radio-buttons-and-checkboxes/', ], 'columns' => [ '/docs/how-to-use-the-layout-field-in-wpforms/', '/docs/how-to-create-a-multi-column-layout-for-radio-buttons-and-checkboxes/', ], 'randomize' => [ '/docs/how-to-randomize-checkbox-and-multiple-choice-options/', ], 'image choices' => [ '/docs/how-to-add-image-choices-to-fields/', ], 'icon choices' => [ '/docs/using-icon-choices/', ], 'multiple choice' => [ '/docs/how-to-bulk-add-choices-for-multiple-choice-checkbox-and-dropdown-fields/', '/docs/how-to-create-a-multi-column-layout-for-radio-buttons-and-checkboxes/', '/docs/how-to-randomize-checkbox-and-multiple-choice-options/', '/docs/how-to-add-image-choices-to-fields/', '/docs/using-icon-choices/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', '/docs/generating-form-choices-with-wpforms-ai/', ], 'radio' => [ '/docs/how-to-bulk-add-choices-for-multiple-choice-checkbox-and-dropdown-fields/', '/docs/how-to-create-a-multi-column-layout-for-radio-buttons-and-checkboxes/', '/docs/how-to-randomize-checkbox-and-multiple-choice-options/', '/docs/how-to-add-image-choices-to-fields/', '/docs/using-icon-choices/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', '/docs/generating-form-choices-with-wpforms-ai/', ], 'checkboxes' => [ '/docs/how-to-bulk-add-choices-for-multiple-choice-checkbox-and-dropdown-fields/', '/docs/how-to-add-a-terms-of-service-checkbox-to-a-form/', '/docs/how-to-create-a-multi-column-layout-for-radio-buttons-and-checkboxes/', '/docs/how-to-randomize-checkbox-and-multiple-choice-options/', '/docs/how-to-add-image-choices-to-fields/', '/docs/using-icon-choices/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', '/docs/generating-form-choices-with-wpforms-ai/', ], 'checkbox' => [ '/docs/how-to-bulk-add-choices-for-multiple-choice-checkbox-and-dropdown-fields/', '/docs/how-to-add-a-terms-of-service-checkbox-to-a-form/', '/docs/how-to-create-a-multi-column-layout-for-radio-buttons-and-checkboxes/', '/docs/how-to-randomize-checkbox-and-multiple-choice-options/', '/docs/how-to-add-image-choices-to-fields/', '/docs/using-icon-choices/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', '/docs/generating-form-choices-with-wpforms-ai/', ], 'gdpr' => [ '/docs/how-to-create-gdpr-compliant-forms/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'gdpr agreement' => [ '/docs/how-to-create-gdpr-compliant-forms/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'number slider' => [ '/docs/how-to-add-a-number-slider-field-to-wpforms/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'range' => [ '/docs/how-to-add-a-number-slider-field-to-wpforms/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'email' => [ '/docs/setup-form-notification-wpforms/', '/docs/customizing-form-notification-emails/', '/docs/how-to-create-conditional-form-notifications-in-wpforms/', '/docs/troubleshooting-email-notifications/', '/docs/how-to-fix-wordpress-contact-form-not-sending-email-with-smtp/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'address' => [ '/docs/how-to-customize-the-address-field/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'field' => [ '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'state' => [ '/docs/how-to-customize-the-address-field/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'province' => [ '/docs/how-to-customize-the-address-field/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'region' => [ '/docs/how-to-customize-the-address-field/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'city' => [ '/docs/how-to-customize-the-address-field/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'country' => [ '/docs/how-to-customize-the-address-field/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'zip code' => [ '/docs/how-to-customize-the-address-field/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'postal code' => [ '/docs/how-to-customize-the-address-field/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'hidden' => [ '/docs/how-to-choose-the-right-form-field-for-your-forms/', '/docs/how-to-use-smart-tags-in-wpforms/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/calculations-addon/', ], 'rating' => [ '/docs/how-to-add-a-rating-field-to-wpforms/', '/docs/how-to-install-and-use-the-surveys-and-polls-addon/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'star' => [ '/docs/how-to-add-a-rating-field-to-wpforms/', '/docs/how-to-install-and-use-the-surveys-and-polls-addon/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'rich text' => [ '/docs/how-to-use-the-rich-text-field-in-wpforms/', ], 'wysiwyg' => [ '/docs/how-to-use-the-rich-text-field-in-wpforms/', ], 'editor' => [ '/docs/how-to-use-the-rich-text-field-in-wpforms/', ], 'rich editor' => [ '/docs/how-to-use-the-rich-text-field-in-wpforms/', ], 'layout' => [ '/docs/how-to-use-the-layout-field-in-wpforms/', ], 'two columns' => [ '/docs/how-to-use-the-layout-field-in-wpforms/', '/docs/using-the-repeater-field/', ], 'three columns' => [ '/docs/how-to-use-the-layout-field-in-wpforms/', '/docs/using-the-repeater-field/', ], 'four columns' => [ '/docs/how-to-use-the-layout-field-in-wpforms/', '/docs/using-the-repeater-field/', ], 'fields horizontally' => [ '/docs/how-to-use-the-layout-field-in-wpforms/', '/docs/using-the-repeater-field/', ], 'fields in a row' => [ '/docs/how-to-use-the-layout-field-in-wpforms/', '/docs/using-the-repeater-field/', ], 'repeater' => [ '/docs/using-the-repeater-field/', ], 'repeatable' => [ '/docs/using-the-repeater-field/', ], 'replicate fields' => [ '/docs/using-the-repeater-field/', ], 'page break' => [ '/docs/how-to-create-multi-page-forms-in-wpforms/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'page' => [ '/docs/how-to-create-multi-page-forms-in-wpforms/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'entry preview' => [ '/docs/how-to-show-entry-previews-in-wpforms/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'break' => [ '/docs/how-to-create-multi-page-forms-in-wpforms/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'multi-page' => [ '/docs/how-to-create-multi-page-forms-in-wpforms/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'password' => [ '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'name' => [ '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'first' => [ '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'last' => [ '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'surname' => [ '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'custom captcha' => [ '/docs/how-to-install-and-use-custom-captcha-addon-in-wpforms/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'numbers' => [ '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', '/docs/calculations-addon/', ], 'website/url' => [ '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'website' => [ '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'url' => [ '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'html' => [ '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'content' => [ 'docs/using-the-content-field/', ], 'code' => [ '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'date/time' => [ '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', '/docs/how-to-customize-the-date-time-field-in-wpforms/', ], 'date' => [ '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', '/docs/how-to-customize-the-date-time-field-in-wpforms/', ], 'time' => [ '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', '/docs/how-to-customize-the-date-time-field-in-wpforms/', ], 'calendar' => [ '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', '/docs/how-to-customize-the-date-time-field-in-wpforms/', ], 'section divider' => [ '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'section' => [ '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'divider' => [ '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'header' => [ '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'phone' => [ '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'telephone' => [ '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'mobile' => [ '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'file upload' => [ '/docs/a-complete-guide-to-the-file-upload-field/', '/docs/how-to-allow-additional-file-upload-types/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'file' => [ '/docs/a-complete-guide-to-the-file-upload-field/', '/docs/how-to-allow-additional-file-upload-types/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'upload' => [ '/docs/a-complete-guide-to-the-file-upload-field/', '/docs/how-to-allow-additional-file-upload-types/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'signature' => [ '/docs/how-to-install-and-use-the-signature-addon-in-wpforms/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'likert scale' => [ '/docs/how-to-add-a-likert-scale-field-to-wpforms/', '/docs/how-to-install-and-use-the-surveys-and-polls-addon/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'likert' => [ '/docs/how-to-add-a-likert-scale-field-to-wpforms/', '/docs/how-to-install-and-use-the-surveys-and-polls-addon/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'scale' => [ '/docs/how-to-add-a-likert-scale-field-to-wpforms/', '/docs/how-to-install-and-use-the-surveys-and-polls-addon/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'net promoter score' => [ '/docs/how-to-add-a-net-promoter-score-field-to-wpforms/', '/docs/how-to-install-and-use-the-surveys-and-polls-addon/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'net' => [ '/docs/how-to-add-a-net-promoter-score-field-to-wpforms/', '/docs/how-to-install-and-use-the-surveys-and-polls-addon/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'promoter' => [ '/docs/how-to-add-a-net-promoter-score-field-to-wpforms/', '/docs/how-to-install-and-use-the-surveys-and-polls-addon/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'score' => [ '/docs/how-to-add-a-net-promoter-score-field-to-wpforms/', '/docs/how-to-install-and-use-the-surveys-and-polls-addon/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'nps' => [ '/docs/how-to-add-a-net-promoter-score-field-to-wpforms/', '/docs/how-to-install-and-use-the-surveys-and-polls-addon/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'coupon' => [ '/docs/coupons-addon/', ], 'discount' => [ '/docs/coupons-addon/', ], 'payment' => [ '/docs/viewing-and-managing-payments/', '/docs/how-to-install-and-use-the-stripe-addon-with-wpforms/', '/docs/paypal-commerce-addon/', '/docs/install-use-paypal-addon-wpforms/', '/docs/how-to-install-and-use-the-authorize-net-addon-with-wpforms/', '/docs/how-to-create-a-donation-form-with-multiple-amounts/', '/docs/how-to-allow-users-to-choose-a-payment-method-on-your-form/', ], 'price' => [ '/docs/viewing-and-managing-payments/', '/docs/how-to-install-and-use-the-stripe-addon-with-wpforms/', '/docs/paypal-commerce-addon/', '/docs/install-use-paypal-addon-wpforms/', '/docs/how-to-install-and-use-the-authorize-net-addon-with-wpforms/', '/docs/how-to-create-a-donation-form-with-multiple-amounts/', '/docs/how-to-allow-users-to-choose-a-payment-method-on-your-form/', ], 'cost' => [ '/docs/viewing-and-managing-payments/', '/docs/how-to-install-and-use-the-stripe-addon-with-wpforms/', '/docs/paypal-commerce-addon/', '/docs/install-use-paypal-addon-wpforms/', '/docs/how-to-install-and-use-the-authorize-net-addon-with-wpforms/', '/docs/how-to-create-a-donation-form-with-multiple-amounts/', '/docs/how-to-allow-users-to-choose-a-payment-method-on-your-form/', ], 'single item' => [ '/docs/viewing-and-managing-payments/', '/docs/how-to-install-and-use-the-stripe-addon-with-wpforms/', '/docs/paypal-commerce-addon/', '/docs/install-use-paypal-addon-wpforms/', '/docs/how-to-install-and-use-the-authorize-net-addon-with-wpforms/', '/docs/how-to-create-a-donation-form-with-multiple-amounts/', '/docs/how-to-allow-users-to-choose-a-payment-method-on-your-form/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', '/docs/calculations-addon/', ], 'multiple items' => [ '/docs/viewing-and-managing-payments/', '/docs/how-to-install-and-use-the-stripe-addon-with-wpforms/', '/docs/paypal-commerce-addon/', '/docs/install-use-paypal-addon-wpforms/', '/docs/how-to-install-and-use-the-authorize-net-addon-with-wpforms/', '/docs/how-to-create-a-donation-form-with-multiple-amounts/', '/docs/how-to-allow-users-to-choose-a-payment-method-on-your-form/', '/docs/how-to-add-image-choices-to-fields/', '/docs/using-icon-choices/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'checkbox items' => [ '/docs/viewing-and-managing-payments/', '/docs/how-to-install-and-use-the-stripe-addon-with-wpforms/', '/docs/paypal-commerce-addon/', '/docs/install-use-paypal-addon-wpforms/', '/docs/how-to-install-and-use-the-authorize-net-addon-with-wpforms/', '/docs/how-to-create-a-donation-form-with-multiple-amounts/', '/docs/how-to-allow-users-to-choose-a-payment-method-on-your-form/', '/docs/how-to-add-image-choices-to-fields/', '/docs/using-icon-choices/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'dropdown items' => [ '/docs/viewing-and-managing-payments/', '/docs/how-to-install-and-use-the-stripe-addon-with-wpforms/', '/docs/paypal-commerce-addon/', '/docs/install-use-paypal-addon-wpforms/', '/docs/how-to-install-and-use-the-authorize-net-addon-with-wpforms/', '/docs/how-to-create-a-donation-form-with-multiple-amounts/', '/docs/how-to-allow-users-to-choose-a-payment-method-on-your-form/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'total' => [ '/docs/viewing-and-managing-payments/', '/docs/how-to-require-payment-total-with-a-wordpress-form/', '/docs/how-to-install-and-use-the-stripe-addon-with-wpforms/', '/docs/paypal-commerce-addon/', '/docs/install-use-paypal-addon-wpforms/', '/docs/how-to-install-and-use-the-authorize-net-addon-with-wpforms/', '/docs/how-to-create-a-donation-form-with-multiple-amounts/', '/docs/how-to-allow-users-to-choose-a-payment-method-on-your-form/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/how-to-customize-the-style-of-individual-form-fields/', ], 'paypal checkout' => [ '/docs/paypal-commerce-addon/', '/docs/testing-payments-with-the-paypal-commerce-addon/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/viewing-and-managing-payments/', ], 'stripe credit card' => [ '/docs/how-to-install-and-use-the-stripe-addon-with-wpforms/', '/docs/how-to-test-stripe-payments-on-your-site/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/viewing-and-managing-payments/', ], 'authorize.net credit card' => [ '/docs/how-to-install-and-use-the-authorize-net-addon-with-wpforms/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/viewing-and-managing-payments/', ], 'square credit card' => [ '/docs/how-to-install-and-use-the-square-addon-with-wpforms/', '/docs/how-to-test-square-payments-on-your-site/', '/docs/how-to-customize-form-field-options/', '/docs/how-to-use-conditional-logic-with-wpforms/', '/docs/viewing-and-managing-payments/', ], 'settings' => [ '/docs/creating-first-form/', '/docs/setup-form-notification-wpforms/', '/docs/setup-form-confirmation-wpforms/', ], 'submit' => [ '/docs/how-to-customize-the-submit-button/', ], 'button' => [ '/docs/how-to-customize-the-submit-button/', ], 'dynamic population' => [ '/developers/how-to-enable-dynamic-field-population/', ], 'offline' => [ '/docs/how-to-enable-ajax-form-submissions/', ], 'offline forms' => [ '/docs/how-to-enable-ajax-form-submissions/', ], 'notification' => [ '/docs/setup-form-notification-wpforms/', '/docs/customizing-form-notification-emails/', '/docs/how-to-create-conditional-form-notifications-in-wpforms/', '/docs/troubleshooting-email-notifications/', '/docs/how-to-fix-wordpress-contact-form-not-sending-email-with-smtp/', '/docs/pdf-addon/', ], 'notifications' => [ '/docs/setup-form-notification-wpforms/', '/docs/customizing-form-notification-emails/', '/docs/how-to-create-conditional-form-notifications-in-wpforms/', '/docs/troubleshooting-email-notifications/', '/docs/how-to-fix-wordpress-contact-form-not-sending-email-with-smtp/', '/docs/pdf-addon/', ], 'notification email' => [ '/docs/setup-form-notification-wpforms/', '/docs/customizing-form-notification-emails/', '/docs/how-to-create-conditional-form-notifications-in-wpforms/', '/docs/troubleshooting-email-notifications/', '/docs/how-to-fix-wordpress-contact-form-not-sending-email-with-smtp/', '/docs/pdf-addon/', ], 'notification emails' => [ '/docs/setup-form-notification-wpforms/', '/docs/customizing-form-notification-emails/', '/docs/how-to-create-conditional-form-notifications-in-wpforms/', '/docs/troubleshooting-email-notifications/', '/docs/how-to-fix-wordpress-contact-form-not-sending-email-with-smtp/', '/docs/pdf-addon/', ], 'confirmation' => [ '/docs/setup-form-confirmation-wpforms/', '/docs/how-to-create-conditional-form-confirmations/', ], 'confirmation message' => [ '/docs/setup-form-confirmation-wpforms/', '/docs/how-to-create-conditional-form-confirmations/', ], 'redirect' => [ '/docs/setup-form-confirmation-wpforms/', '/docs/how-to-create-conditional-form-confirmations/', ], 'go to url (redirect)' => [ '/docs/setup-form-confirmation-wpforms/', '/docs/how-to-create-conditional-form-confirmations/', ], 'confirmation page' => [ '/docs/setup-form-confirmation-wpforms/', '/docs/how-to-create-conditional-form-confirmations/', ], 'conditional confirmation' => [ '/docs/setup-form-confirmation-wpforms/', '/docs/how-to-create-conditional-form-confirmations/', ], 'calculation' => [ '/docs/calculations-addon/', '/docs/building-formulas-with-the-calculations-addon/', '/calculations-formula-cheatsheet/', ], 'calculations' => [ '/docs/calculations-addon/', '/docs/building-formulas-with-the-calculations-addon/', '/calculations-formula-cheatsheet/', ], 'formula' => [ '/docs/calculations-addon/', '/docs/building-formulas-with-the-calculations-addon/', '/calculations-formula-cheatsheet/', ], 'conditional calculation' => [ '/docs/calculations-addon/', '/docs/building-formulas-with-the-calculations-addon/', '/calculations-formula-cheatsheet/', ], 'lead forms' => [ '/docs/lead-forms-addon/', ], 'form abandonment' => [ '/docs/how-to-install-and-use-form-abandonment-with-wpforms/', ], 'abandonment' => [ '/docs/how-to-install-and-use-form-abandonment-with-wpforms/', ], 'abandon' => [ '/docs/how-to-install-and-use-form-abandonment-with-wpforms/', ], 'lead capture' => [ '/docs/how-to-install-and-use-form-abandonment-with-wpforms/', ], 'post submissions' => [ '/docs/how-to-install-and-use-the-post-submissions-addon-in-wpforms/', ], 'guest post' => [ '/docs/how-to-install-and-use-the-post-submissions-addon-in-wpforms/', ], 'user submission' => [ '/docs/how-to-install-and-use-the-post-submissions-addon-in-wpforms/', ], 'blog' => [ '/docs/how-to-install-and-use-the-post-submissions-addon-in-wpforms/', ], 'post' => [ '/docs/how-to-install-and-use-the-post-submissions-addon-in-wpforms/', ], 'user registration' => [ '/docs/how-to-install-and-use-user-registration-addon-with-wpforms/', '/docs/how-to-set-up-custom-user-meta-fields/', ], 'register' => [ '/docs/how-to-install-and-use-user-registration-addon-with-wpforms/', '/docs/how-to-set-up-custom-user-meta-fields/', ], 'registration' => [ '/docs/how-to-install-and-use-user-registration-addon-with-wpforms/', '/docs/how-to-set-up-custom-user-meta-fields/', ], 'user meta' => [ '/docs/how-to-install-and-use-user-registration-addon-with-wpforms/', '/docs/how-to-set-up-custom-user-meta-fields/', ], 'user' => [ '/docs/how-to-install-and-use-user-registration-addon-with-wpforms/', '/docs/how-to-set-up-custom-user-meta-fields/', ], 'surveys' => [ '/docs/how-to-install-and-use-the-surveys-and-polls-addon/', ], 'polls' => [ '/docs/how-to-install-and-use-the-surveys-and-polls-addon/', ], 'surveys and polls' => [ '/docs/how-to-install-and-use-the-surveys-and-polls-addon/', ], 'conversational forms' => [ '/docs/how-to-install-and-use-the-conversational-forms-addon/', ], 'conversational' => [ '/docs/how-to-install-and-use-the-conversational-forms-addon/', ], 'form locker' => [ '/docs/how-to-install-and-use-the-form-locker-addon-in-wpforms/', '/developers/how-to-display-remaining-entry-limit-number/', ], 'password protection' => [ '/docs/how-to-install-and-use-the-form-locker-addon-in-wpforms/', '/developers/how-to-display-remaining-entry-limit-number/', ], 'entry limit' => [ '/docs/how-to-install-and-use-the-form-locker-addon-in-wpforms/', '/developers/how-to-display-remaining-entry-limit-number/', ], 'scheduling' => [ '/docs/how-to-install-and-use-the-form-locker-addon-in-wpforms/', '/developers/how-to-display-remaining-entry-limit-number/', ], 'restrict access' => [ '/docs/how-to-install-and-use-the-form-locker-addon-in-wpforms/', '/developers/how-to-display-remaining-entry-limit-number/', ], 'limit' => [ '/docs/how-to-install-and-use-the-form-locker-addon-in-wpforms/', '/developers/how-to-display-remaining-entry-limit-number/', ], 'schedule' => [ '/docs/how-to-install-and-use-the-form-locker-addon-in-wpforms/', '/developers/how-to-display-remaining-entry-limit-number/', ], 'restrict' => [ '/docs/how-to-install-and-use-the-form-locker-addon-in-wpforms/', '/developers/how-to-display-remaining-entry-limit-number/', ], 'form pages' => [ '/docs/how-to-install-and-use-the-form-pages-addon/', ], 'save' => [ '/docs/how-to-install-and-use-the-save-and-resume-addon-with-wpforms/', ], 'resume' => [ '/docs/how-to-install-and-use-the-save-and-resume-addon-with-wpforms/', ], 'continue' => [ '/docs/how-to-install-and-use-the-save-and-resume-addon-with-wpforms/', ], 'save and resume' => [ '/docs/how-to-install-and-use-the-save-and-resume-addon-with-wpforms/', ], 'save and continue' => [ '/docs/how-to-install-and-use-the-save-and-resume-addon-with-wpforms/', ], 'webhooks' => [ '/docs/how-to-install-and-use-the-webhooks-addon-with-wpforms/', ], 'aweber' => [ '/docs/install-use-aweber-addon-wpforms/', ], 'campaign monitor' => [ '/docs/how-to-install-and-use-campaign-monitor-addon-with-wpforms/', ], 'constant contact' => [ '/docs/how-to-connect-constant-contact-with-wpforms/', ], 'convertkit' => [ '/docs/convertkit-addon/', ], 'drip' => [ '/docs/how-to-install-and-use-the-drip-addon-in-wpforms/', ], 'dropbox' => [ '/docs/dropbox-addon/', ], 'google-calendar' => [ '/docs/google-calendar-addon/', ], 'google-drive' => [ '/docs/google-drive-addon/', ], 'getresponse' => [ '/docs/how-to-install-and-use-getresponse-addon-with-wpforms/', ], 'google sheets' => [ '/docs/google-sheets-addon/', '/docs/google-permissions/', ], 'mailchimp' => [ '/docs/install-use-mailchimp-addon-wpforms/', ], 'mailerlite' => [ '/docs/install-use-mailerlite-addon-wpforms/', ], 'mailpoet' => [ '/docs/mailpoet-addon/', ], 'make' => [ '/docs/make-addon/', ], 'zapier' => [ '/docs/how-to-install-and-use-zapier-addon-with-wpforms/', ], 'pipedrive' => [ '/docs/pipedrive-addon/', ], 'salesforce' => [ '/docs/how-to-install-and-use-the-salesforce-addon-with-wpforms/', ], 'sendgrid' => [ '/docs/sendgrid-addon/', ], 'sendinblue' => [ '/docs/how-to-install-and-use-the-sendinblue-addon-with-wpforms/', ], 'slack' => [ '/docs/slack-addon/', ], 'hubspot' => [ '/docs/how-to-install-and-use-the-hubspot-addon-in-wpforms/', ], 'klaviyo' => [ '/docs/klaviyo-addon/', ], 'twilio' => [ '/docs/twilio-addon/', ], 'zoho crm' => [ '/docs/zoho-crm-addon/', ], 'integrate' => [ '/docs/how-to-install-and-use-zapier-addon-with-wpforms/', '/docs/how-to-install-and-use-the-webhooks-addon-with-wpforms/', '/docs/google-sheets-addon/', '/docs/n8n-addon/', ], 'integration' => [ '/docs/how-to-install-and-use-zapier-addon-with-wpforms/', '/docs/how-to-install-and-use-the-webhooks-addon-with-wpforms/', '/docs/google-sheets-addon/', '/docs/n8n-addon/', ], 'crm' => [ '/docs/how-to-install-and-use-zapier-addon-with-wpforms/', '/docs/how-to-install-and-use-the-webhooks-addon-with-wpforms/', ], 'api' => [ '/docs/how-to-install-and-use-zapier-addon-with-wpforms/', '/docs/how-to-install-and-use-the-webhooks-addon-with-wpforms/', '/docs/google-sheets-addon/', '/docs/n8n-addon/', ], 'paypal commerce' => [ '/docs/paypal-commerce-addon/', '/docs/testing-payments-with-the-paypal-commerce-addon/', ], 'paypal standard' => [ '/docs/install-use-paypal-addon-wpforms/', '/docs/how-to-test-paypal-payments-before-accepting-real-payments/', '/docs/how-to-allow-users-to-choose-a-payment-method-on-your-form/', ], 'stripe' => [ '/docs/using-stripe-with-wpforms-lite/', '/docs/how-to-install-and-use-the-stripe-addon-with-wpforms/', '/docs/how-to-test-stripe-payments-on-your-site/', ], 'authorize' => [ '/docs/how-to-install-and-use-the-authorize-net-addon-with-wpforms/', ], 'authorize.net' => [ '/docs/how-to-install-and-use-the-authorize-net-addon-with-wpforms/', ], 'square' => [ '/docs/how-to-install-and-use-the-square-addon-with-wpforms/', '/docs/how-to-test-square-payments-on-your-site/', ], 'revisions' => [ '/docs/how-to-use-form-revisions-in-wpforms/', ], 'ai' => [ '/docs/generating-form-choices-with-wpforms-ai/', '/docs/generating-forms-with-wpforms-ai/', ], 'entry automation' => [ '/docs/entry-automation-addon/', ], 'pdf' => [ '/docs/pdf-addon/', ], 'n8n' => [ '/docs/n8n-addon/', ], 'notion' => [ '/docs/notion-addon/', ], 'airtable' => [ '/docs/airtable-addon/', ], 'quiz' => [ '/docs/quiz-addon/', ], ]; } /** * Get context (recommended) docs. * * @since 1.6.3 * * @return array Docs recommended by search terms. */ public function get_context_docs() { if ( empty( $this->docs ) ) { return []; } $docs_links = $this->get_context_docs_links(); $docs = []; foreach ( $docs_links as $word => $links ) { $docs[ $word ] = $this->get_doc_ids( $links ); } return $docs; } /** * Get doc id. * * @since 1.8.3 * * @param string $link Absolute link to the doc without the domain part. * * @return int Doc id. */ private function get_doc_id_int( $link ) { if ( empty( $this->docs ) ) { return 0; } foreach ( $this->docs as $id => $doc ) { if ( ! empty( $doc['url'] ) && $doc['url'] === 'https://wpforms.com' . $link ) { return $id; } } return 0; } /** * Get doc ids. * * @since 1.6.3 * * @param array $links Array of the doc links. * * @return array Doc ids. */ public function get_doc_ids( $links ) { $ids = []; foreach ( $links as $link ) { $ids[] = $this->get_doc_id_int( $link ); } return $ids; } /** * Output help modal markup. * * @since 1.6.3 */ public function output() { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wpforms_render( 'builder/help', [ 'settings' => [ 'docs_url' => 'https://wpforms.com/docs/', 'support_ticket_url' => 'https://wpforms.com/account/support/', 'upgrade_url' => 'https://wpforms.com/pricing/', ], ], true ); } } Builder/Ajax/PanelLoader.php 0000644 00000006771 15252506741 0011736 0 ustar 00 <?php namespace WPForms\Admin\Builder\Ajax; /** * Form Builder Panel Loader AJAX actions. * * @since 1.8.6 */ class PanelLoader { /** * Determine if the class is allowed to load. * * @since 1.8.6 * * @return bool */ private function allow_load(): bool { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $action = isset( $_REQUEST['action'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['action'] ) ) : ''; // Load only in the case of AJAX calls form the Form Builder. return wpforms_is_admin_ajax() && strpos( $action, 'wpforms_builder_' ) === 0; } /** * Initialize class. * * @since 1.8.6 */ public function init(): void { if ( ! $this->allow_load() ) { return; } $this->hooks(); } /** * Hooks. * * @since 1.8.6 */ private function hooks(): void { add_action( 'wp_ajax_wpforms_builder_load_panel', [ $this, 'load_panel_content' ] ); } /** * Save tags. * * @since 1.8.6 */ public function load_panel_content(): void { check_ajax_referer( 'wpforms-builder', 'nonce' ); $form_id = absint( filter_input( INPUT_POST, 'form_id', FILTER_SANITIZE_NUMBER_INT ) ); if ( ! wpforms_current_user_can( 'edit_forms', $form_id ) ) { wp_send_json_error( esc_html__( 'You do not have permission to perform this action.', 'wpforms-lite' ) ); } $data = $this->get_prepared_data( 'load_panel' ); $panel = $data['panel'] ?? ''; $panel_class = '\WPForms_Builder_Panel_' . ucfirst( $panel ); $panel_obj = $this->get_panel_obj( $panel_class, $panel ); ob_start(); $panel_obj->panel_output( [], $panel ); $panel_content = ob_get_clean(); wp_send_json_success( $panel_content ); } /** * Get panel object. * * @since 1.9.4 * * @param string $panel_class Panel class name. * @param string $panel Panel name. * * @return object */ private function get_panel_obj( string $panel_class, string $panel ) { if ( ! class_exists( $panel_class ) ) { // Load panel base class. require_once WPFORMS_PLUGIN_DIR . 'includes/admin/builder/panels/class-base.php'; $file = WPFORMS_PLUGIN_DIR . "includes/admin/builder/panels/class-{$panel}.php"; $file_pro = WPFORMS_PLUGIN_DIR . "pro/includes/admin/builder/panels/class-{$panel}.php"; if ( file_exists( $file_pro ) && wpforms()->is_pro() ) { require_once $file_pro; } elseif ( file_exists( $file ) ) { require_once $file; } } $panel_obj = $panel_class::instance(); if ( ! method_exists( $panel_obj, 'panel_content' ) ) { wp_send_json_error( esc_html__( 'Invalid panel.', 'wpforms-lite' ) ); } return $panel_obj; } /** * Get prepared data before perform ajax action. * * @since 1.8.6 * * @param string $action Action: `save` OR `delete`. * * @return array * @noinspection PhpSameParameterValueInspection */ private function get_prepared_data( string $action ): array { // Run a security check. if ( ! check_ajax_referer( 'wpforms-builder', 'nonce', false ) ) { wp_send_json_error( esc_html__( 'Most likely, your session expired. Please reload the page.', 'wpforms-lite' ) ); } // Check for permissions. if ( ! wpforms_current_user_can( 'edit_forms' ) ) { wp_send_json_error( esc_html__( 'You are not allowed to perform this action.', 'wpforms-lite' ) ); } $data = []; if ( $action === 'load_panel' ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $data['panel'] = ! empty( $_POST['panel'] ) ? sanitize_key( $_POST['panel'] ) : ''; } return $data; } } Builder/Ajax/TemplatesLoader.php 0000644 00000016737 15252506741 0012640 0 ustar 00 <?php namespace WPForms\Admin\Builder\Ajax; use WPForms\Admin\Traits\FormTemplates; /** * AJAX handler for infinite scroll template loading. * * @since 2.0.0 */ class TemplatesLoader { use FormTemplates; /** * Default batch size. * * @since 2.0.0 */ private const DEFAULT_LIMIT = 12; /** * Maximum allowed batch size. * * @since 2.0.0 */ private const MAX_LIMIT = 24; /** * Determine if the class is allowed to load. * * @since 2.0.0 * * @return bool */ private function allow_load(): bool { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $action = isset( $_REQUEST['action'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['action'] ) ) : ''; // Load only in the case of AJAX calls from the Form Builder. return wpforms_is_admin_ajax() && strpos( $action, 'wpforms_get_templates_' ) === 0; } /** * Initialize. * * @since 2.0.0 */ public function init(): void { if ( ! $this->allow_load() ) { return; } // Initialize addons object for FormTemplates trait. $this->addons_obj = wpforms()->obj( 'addons' ); $this->hooks(); } /** * Register hooks. * * @since 2.0.0 */ private function hooks(): void { add_action( 'wp_ajax_wpforms_get_templates_batch', [ $this, 'ajax_get_templates_batch' ] ); } /** * AJAX handler for getting a template batch. * * @since 2.0.0 */ public function ajax_get_templates_batch(): void { // Verify nonce. if ( ! check_ajax_referer( 'wpforms-form-templates', 'nonce', false ) ) { wp_send_json_error( [ 'message' => esc_html__( 'Security check failed.', 'wpforms-lite' ), ] ); } // Check permissions. if ( ! wpforms_current_user_can( 'create_forms' ) ) { wp_send_json_error( [ 'message' => esc_html__( 'You do not have permission to access templates.', 'wpforms-lite' ), ] ); } // Get and sanitize parameters. $params = $this->get_sanitized_params(); // Validate parameters. $validation_error = $this->validate_params( $params ); if ( $validation_error ) { wp_send_json_error( [ 'message' => $validation_error, ] ); } // Start performance tracking. $start_time = microtime( true ); $start_memory = memory_get_usage(); // Get filtered templates. $result = $this->get_templates_batch( $params ); if ( ! $result ) { wp_send_json_error( [ 'message' => esc_html__( 'Failed to get templates batch.', 'wpforms-lite' ), ] ); } // Add performance metadata. $result['metadata'] = [ 'execution_time' => round( ( microtime( true ) - $start_time ) * 1000, 2 ), // ms. 'memory_usage' => round( ( memory_get_usage() - $start_memory ) / 1024, 2 ), // KB. ]; wp_send_json_success( $result ); } /** * Get and sanitize request parameters. * * @since 2.0.0 * * @return array Sanitized parameters. */ private function get_sanitized_params(): array { // phpcs:disable WordPress.Security.NonceVerification.Missing return [ 'offset' => isset( $_POST['offset'] ) ? absint( $_POST['offset'] ) : 0, 'limit' => isset( $_POST['limit'] ) ? absint( $_POST['limit'] ) : self::DEFAULT_LIMIT, 'category' => isset( $_POST['category'] ) ? sanitize_key( $_POST['category'] ) : 'all', 'subcategory' => isset( $_POST['subcategory'] ) ? sanitize_key( $_POST['subcategory'] ) : '', 'search' => isset( $_POST['search'] ) ? sanitize_text_field( wp_unslash( $_POST['search'] ) ) : '', 'favorite' => isset( $_POST['favorite'] ) && filter_var( wp_unslash( $_POST['favorite'] ), FILTER_VALIDATE_BOOLEAN ), ]; // phpcs:enable WordPress.Security.NonceVerification.Missing } /** * Validate request parameters. * * @since 2.0.0 * * @param array $params Parameters to validate. * * @return string|null Error message or null if valid. */ private function validate_params( array $params ): ?string { if ( $params['offset'] < 0 ) { return esc_html__( 'Invalid offset parameter.', 'wpforms-lite' ); } if ( $params['limit'] < 1 || $params['limit'] > self::MAX_LIMIT ) { return sprintf( /* translators: %d - maximum limit value. */ esc_html__( 'Limit must be between 1 and %d.', 'wpforms-lite' ), self::MAX_LIMIT ); } if ( strlen( $params['search'] ) > 100 ) { return esc_html__( 'Search query too long.', 'wpforms-lite' ); } return null; } /** * Get a batch of templates. * * @since 2.0.0 * * @param array $params Query parameters. * * @return array|null Batch response data. */ public function get_templates_batch( array $params ): ?array { $cache_obj = $this->get_cache_obj(); $cache_key = ''; // Layer 1: Check the HTML batch cache for identical requests. if ( $cache_obj ) { $cache_key = $this->build_batch_cache_key( $params ); $cached_batch = $cache_obj->get_html_batch_cache( $cache_key ); if ( is_array( $cached_batch ) && ! empty( $cached_batch['templates'] ) ) { return $cached_batch; } } // Layer 2: Try to load prepared templates from the cache. $this->load_prepared_templates_from_cache( $cache_obj ); // Get filtered templates. $filtered = $this->get_filtered_templates( $params['offset'], $params['limit'], $params['category'], $params['subcategory'], $params['search'], $params['favorite'] ); // Render HTML for each template. $templates_html = []; foreach ( $filtered['templates'] as $template_data ) { $templates_html[] = wpforms_render( 'builder/templates-item', $template_data, true ); } $result = [ 'templates' => $templates_html, 'total' => $filtered['total'], 'offset' => $params['offset'] + count( $templates_html ), 'has_more' => $filtered['has_more'], ]; // Save to HTML batch cache. if ( $cache_obj && ! empty( $cache_key ) ) { $cache_obj->save_html_batch_cache( $cache_key, $result ); } return $result; } /** * Load prepared templates from the cache or generate fresh. * * @since 2.0.0 * * @param object|null $cache_obj Templates cache object. */ private function load_prepared_templates_from_cache( $cache_obj ): void { // Try to load from the prepared templates cache. $cached_templates = $cache_obj ? $cache_obj->get_prepared_templates_cache() : null; if ( ! empty( $cached_templates ) ) { $this->prepared_templates = $cached_templates; return; } // Cache miss: full preparation. $builder_templates = wpforms()->obj( 'builder_templates' ); if ( ! $builder_templates ) { return; } $builder_templates->get_templates(); $this->prepare_templates_data(); // Save to prepared templates cache. if ( $cache_obj && ! empty( $this->prepared_templates ) ) { $cache_obj->save_prepared_templates_cache( $this->prepared_templates ); } } /** * Build a unique cache key for the batch request. * * @since 2.0.0 * * @param array $params Request parameters. * * @return string Cache key. */ private function build_batch_cache_key( array $params ): string { return wp_json_encode( [ 'offset' => $params['offset'], 'limit' => $params['limit'], 'category' => $params['category'], 'subcategory' => $params['subcategory'], 'search' => $params['search'], 'favorite' => $params['favorite'], 'version' => WPFORMS_VERSION, 'user' => get_current_user_id(), ] ); } /** * Get the templates cache object. * * @since 2.0.0 * * @return object|null Cache object or null. */ private function get_cache_obj() { return wpforms()->obj( 'builder_templates_cache' ); } } Builder/Ajax/SaveForm.php 0000644 00000005204 15252506741 0011260 0 ustar 00 <?php namespace WPForms\Admin\Builder\Ajax; /** * Save the form data. * * @since 1.9.4 */ class SaveForm { /** * Field types that persist a `choices` array and require empty-choice pruning on save. * * @since 2.0.0 * * @var array */ private const CHOICE_FIELD_TYPES = [ 'radio', 'checkbox', 'select', 'payment-checkbox', 'payment-multiple', 'payment-select', ]; /** * The form fields processing while saving the form. * * @since 1.9.4 * * @param array $fields Form fields data. * @param array $form_data Form data. * * @return array */ public function process_fields( array $fields, array $form_data ): array { $form_obj = wpforms()->obj( 'form' ); if ( ! $form_obj || empty( $fields ) || empty( $form_data['id'] ) ) { return $fields; } $saved_form_data = $form_obj->get( $form_data['id'], [ 'content_only' => true ] ); foreach ( $fields as $field_id => $field_data ) { if ( empty( $field_data['type'] ) ) { continue; } // Prune fully-empty leftover choices so a deleted choice is removed before persistence. $field_data = $this->prune_empty_choices( $field_data ); /** * Filter field settings before saving the form. * * @since 1.9.4 * * @param array $field_data Field data. * @param array $form_data Forms data. * @param array $saved_form_data Saved form data. */ $fields[ $field_id ] = apply_filters( "wpforms_admin_builder_ajax_save_form_field_{$field_data['type']}", $field_data, $form_data, $saved_form_data ); } return $fields; } /** * Prune fully-empty leftover choices from a choice-type field before it is saved. * * Dynamic-choice fields (post type / taxonomy) are skipped because their * `choices` are not the persisted source of truth. * * @since 2.0.0 * * @param array $field_data Field data being saved. * * @return array */ private function prune_empty_choices( array $field_data ): array { // Only choice-type fields persist a `choices` array. if ( ! in_array( $field_data['type'], self::CHOICE_FIELD_TYPES, true ) ) { return $field_data; } // Reach the field type object registered in WPForms_Field::common_hooks(). /** This filter is documented in src/Pro/Forms/Fields/Base/EntriesEdit.php. */ $field_obj = apply_filters( "wpforms_fields_get_field_object_{$field_data['type']}", null ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName // The field object guards dynamic-choice fields and empty choices arrays internally. if ( ! $field_obj instanceof \WPForms_Field ) { return $field_data; } return $field_obj->remove_empty_choices_on_save( $field_data ); } } Builder/HelpCache.php 0000644 00000002246 15252506741 0010472 0 ustar 00 <?php namespace WPForms\Admin\Builder; use WPForms\Helpers\CacheBase; /** * Form Builder Help Cache. * * @since 1.8.2 */ class HelpCache extends CacheBase { /** * Remote source URL. * * @since 1.9.3 * * @var string */ const REMOTE_SOURCE = 'https://wpformsapi.com/feeds/v1/docs/'; /** * Determine if the class is allowed to load. * * @since 1.8.2 * * @return bool */ protected function allow_load() { if ( wp_doing_cron() || wpforms_doing_wp_cli() ) { return true; } if ( ! wpforms_current_user_can( [ 'create_forms', 'edit_forms' ] ) ) { return false; } return wpforms_is_admin_page( 'builder' ); } /** * Setup settings and other things. * * @since 1.8.2 */ protected function setup() { return [ 'remote_source' => self::REMOTE_SOURCE, 'cache_file' => 'docs.json', /** * Allow modifying Help Docs cache TTL (time to live). * * @since 1.6.3 * * @param int $cache_ttl Cache TTL in seconds. Defaults to 1 week. */ 'cache_ttl' => (int) apply_filters( 'wpforms_admin_builder_help_cache_ttl', WEEK_IN_SECONDS ), 'update_action' => 'wpforms_builder_help_cache_update', ]; } } Builder/PreviewDropdown.php 0000644 00000012567 15252506741 0012023 0 ustar 00 <?php namespace WPForms\Admin\Builder; use WPForms_Builder; use WPForms\Admin\Addons\Addons; use WP_Post; /** * Form Builder preview dropdown class. * * Registers assets powering the split-button Preview dropdown in the * Form Builder toolbar, which surfaces the Standard Form Preview link * along with Pro product education items. * * @since 1.10.1 */ class PreviewDropdown { /** * Init class. * * @since 1.10.1 */ public function init() { $this->hooks(); } /** * Register hooks. * * @since 1.10.1 */ private function hooks() { add_action( 'wpforms_builder_enqueues', [ $this, 'enqueues' ] ); } /** * Enqueue assets. * * @since 1.10.1 */ public function enqueues() { $min = wpforms_get_min_suffix(); wp_enqueue_script( 'wpforms-builder-preview-dropdown', WPFORMS_PLUGIN_URL . "assets/js/admin/builder/preview-dropdown{$min}.js", [ 'jquery', 'wpforms-builder' ], WPFORMS_VERSION, true ); // The Addons Required modal is only shown to Pro users when a // feature's addon is not installed or not activated. if ( ! wpforms()->is_pro() ) { return; } wp_enqueue_script( 'wpforms-builder-preview-dropdown-education', WPFORMS_PLUGIN_URL . "assets/js/admin/builder/preview-dropdown-education{$min}.js", [ 'jquery', 'underscore', 'jquery-confirm', 'wpforms-builder', 'wpforms-admin-education-core' ], WPFORMS_VERSION, true ); wp_localize_script( 'wpforms-builder-preview-dropdown-education', 'wpforms_builder_preview_dropdown_education', $this->get_localized_data() ); } /** * Get data passed to the preview dropdown education JS module. * * @since 1.10.1 * * @return array */ private function get_localized_data(): array { return [ 'ajax_url' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'wpforms-admin' ), 'addons' => $this->get_addons_data(), 'strings' => [ 'title' => __( 'Addons Required', 'wpforms-lite' ), 'description' => __( 'In order to preview your form in other formats, you need to install addons. Would you like to install them now?', 'wpforms-lite' ), 'install_activate' => __( 'Install & Activate', 'wpforms-lite' ), 'cancel' => __( 'Cancel', 'wpforms-lite' ), 'view_demo' => __( 'View Demo', 'wpforms-lite' ), 'installing' => __( 'Installing', 'wpforms-lite' ), 'activating' => __( 'Activating', 'wpforms-lite' ), 'no_selection' => __( 'Please select at least one addon to install.', 'wpforms-lite' ), 'generic_error' => __( 'Something went wrong while installing the addons. Please try again.', 'wpforms-lite' ), 'addon_activated' => __( 'Addon Activated', 'wpforms-lite' ), 'addons_activated' => __( 'Addons Activated', 'wpforms-lite' ), ], ]; } /** * Get addons data for the Addons Required modal. * * Combines the preview dropdown education items with live addon status * (installed/active) so the JS can render checkboxes and drive installation. * * @since 1.10.1 * * @return array */ private function get_addons_data(): array { $form = $this->get_current_form(); $items = ( new PreviewDropdownEducationItems( $form ) )->get_items(); $addons_obj = wpforms()->obj( 'addons' ); $data = []; foreach ( $items as $item ) { $prepared = $this->prepare_addon_item_data( $item, $addons_obj ); if ( $prepared === [] ) { continue; } $data[] = $prepared; } return $data; } /** * Resolve the current builder form from the Builder singleton. * * @since 1.10.1 * * @return WP_Post|null */ private function get_current_form(): ?WP_Post { if ( ! class_exists( WPForms_Builder::class ) ) { return null; } $form = WPForms_Builder::instance()->form ?? null; return $form instanceof WP_Post ? $form : null; } /** * Merge a single preview dropdown item with its live addon status. * * @since 1.10.1 * * @param array $item Education item (see PreviewDropdownEducationItems::get_items()). * @param Addons|null $addons_obj WPForms addons handler, or null if unavailable. * * @return array Data consumable by the education JS, or empty array when the item has no addon slug. */ private function prepare_addon_item_data( array $item, $addons_obj ): array { $addon_slug = $item['addon_slug'] ?? ''; if ( $addon_slug === '' ) { return []; } $addon = $addons_obj ? $addons_obj->get_addon( $addon_slug ) : []; $status = $addon['status'] ?? 'missing'; return [ 'slug' => $addon_slug, 'name' => $this->get_addon_item_name( $item ), 'icon' => $item['icon'] ?? '', 'demo_url' => PreviewDropdownEducationItems::get_demo_url( $item ), 'install_url' => $addon['url'] ?? '', 'path' => $addon['path'] ?? '', 'section' => $item['section'] ?? '', 'status' => $status, 'is_active' => $status === 'active', 'is_installed' => in_array( $status, [ 'active', 'installed' ], true ), ]; } /** * Resolve the user-facing name for an addon education item. * * Falls back to the regular label when a modal-specific label is not provided. * * @since 1.10.1 * * @param array $item Education item (see PreviewDropdownEducationItems::get_items()). * * @return string */ private function get_addon_item_name( array $item ): string { return $item['modal_label'] ?? ( $item['label'] ?? '' ); } } Builder/ContextMenu.php 0000644 00000001671 15252506741 0011130 0 ustar 00 <?php namespace WPForms\Admin\Builder; /** * Context Menu class. * * @since 1.8.6 */ class ContextMenu { /** * Init class. * * @since 1.8.6 */ public function init() { $this->hooks(); } /** * Register hooks. * * @since 1.8.6 */ protected function hooks() { add_action( 'wpforms_builder_enqueues', [ $this, 'enqueues' ] ); add_action( 'wpforms_admin_page', [ $this, 'output' ], 20 ); } /** * Enqueue assets. * * @since 1.8.6 */ public function enqueues() { $min = wpforms_get_min_suffix(); wp_enqueue_script( 'wpforms-builder-context-menu', WPFORMS_PLUGIN_URL . "assets/js/admin/builder/context-menu{$min}.js", [ 'wpforms-builder' ], WPFORMS_VERSION, true ); } /** * Output context menu markup. * * @since 1.8.6 */ public function output() { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wpforms_render( 'builder/field-context-menu' ); } } Builder/TemplatesCache.php 0000644 00000026074 15252506741 0011545 0 ustar 00 <?php namespace WPForms\Admin\Builder; use WPForms\Helpers\CacheBase; use WPForms\Helpers\File; use WPForms\Helpers\Transient; /** * Form templates cache handler. * * @since 1.6.8 */ class TemplatesCache extends CacheBase { /** * Templates list content cache files. * * @since 1.8.6 * * @var array */ public const CONTENT_CACHE_FILES = [ 'admin-page' => 'templates-admin-page.html', 'builder' => 'templates-builder.html', ]; /** * Cache key for prepared templates' data. * * @since 2.0.0 * * @var string */ private const PREPARED_DATA_KEY = 'wpforms_prepared_templates_data'; /** * Cache key prefix for rendered HTML batches. * * @since 2.0.0 * * @var string */ private const HTML_BATCH_PREFIX = 'wpforms_template_batch_'; /** * Registry key for tracking active HTML batch cache keys. * * @since 2.0.0 * * @var string */ private const HTML_BATCH_REGISTRY = 'wpforms_template_batch_registry'; /** * Registry key for tracking active prepared templates cache keys. * * @since 2.0.0 * * @var string */ private const PREPARED_DATA_REGISTRY = 'wpforms_prepared_templates_registry'; /** * List of plugins that can use the templates cache. * * @since 1.8.7 * * @var array */ public const PLUGINS = [ 'wpforms', 'wpforms-lite', ]; /** * Determine if the class is allowed to load. * * @since 1.6.8 * * @return bool */ protected function allow_load(): bool { $has_permissions = wpforms_current_user_can( [ 'create_forms', 'edit_forms' ] ); $allowed_requests = wpforms_is_admin_ajax() || wpforms_is_admin_page( 'builder' ) || wpforms_is_admin_page( 'templates' ) || wpforms_is_admin_page( 'tools', 'action-scheduler' ); $allow = wp_doing_cron() || wpforms_doing_wp_cli() || ( $has_permissions && $allowed_requests ); /** * Whether to load this class. * * @since 1.7.2 * * @param bool $allow True or false. */ return (bool) apply_filters( 'wpforms_admin_builder_templatescache_allow_load', $allow ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName } /** * Initialize the class. * * @since 1.8.7 */ public function init(): void { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks parent::init(); // Upgrade cached templates data after the plugin update. add_action( 'upgrader_process_complete', [ $this, 'upgrade_templates' ] ); } /** * Upgrade cached templates data after the plugin update. * * @since 1.8.7 * * @param object $upgrader WP_Upgrader instance. */ public function upgrade_templates( $upgrader ): void { if ( $this->allow_update_cache( $upgrader ) ) { $this->update( true ); } } /** * Determine if allowed to update the cache. * * @since 1.8.7 * * @param object $upgrader WP_Upgrader instance. * * @return bool */ private function allow_update_cache( $upgrader ): bool { $result = $upgrader->result ?? null; // Check if the plugin was updated. if ( ! $result ) { return false; } // Check if updated plugin is WPForms. if ( ! in_array( $result['destination_name'], self::PLUGINS, true ) ) { return false; } return true; } /** * Provide settings. * * @since 1.6.8 * * @return array Settings array. */ protected function setup(): array { return [ // Remote source URL. 'remote_source' => 'https://wpforms.com/templates/api/get/', // Cache file. 'cache_file' => 'templates.json', /** * Time-to-live of the templates cache files in seconds. * * This applies to `uploads/wpforms/cache/templates.json` * and all *.json files in `uploads/wpforms/cache/templates/` directory. * * @since 1.6.8 * * @param integer $cache_ttl Cache time-to-live, in seconds. * Default value: WEEK_IN_SECONDS. */ 'cache_ttl' => (int) apply_filters( 'wpforms_admin_builder_templates_cache_ttl', WEEK_IN_SECONDS ), /** * Time-to-live of AJAX-related cache (prepared templates and HTML batches) in seconds. * * This applies to prepared templates cache, HTML batch cache, and their registries. * * @since 2.0.0 * * @param int $ajax_cache_ttl AJAX cache time-to-live, in seconds. * Default value: DAY_IN_SECONDS. */ 'ajax_cache_ttl' => (int) apply_filters( 'wpforms_admin_builder_templates_cache_ajax_cache_ttl', DAY_IN_SECONDS ), // Scheduled update action. 'update_action' => 'wpforms_admin_builder_templates_cache_update', ]; } /** * Prepare data to store in a local cache. * * @since 1.6.8 * * @param array $data Raw data received by the remote request. * * @return array Prepared data for caching. */ protected function prepare_cache_data( $data ): array { if ( empty( $data ) || ! is_array( $data ) || empty( $data['status'] ) || $data['status'] !== 'success' || empty( $data['data'] ) ) { return []; } $cache_data = $data['data']; // Strip the word "Template" from the end of each template name. foreach ( $cache_data['templates'] as $slug => $template ) { $cache_data['templates'][ $slug ]['name'] = preg_replace( '/\sTemplate$/', '', $template['name'] ); } return $cache_data; } /** * Update the cache. * * @since 1.8.6 * * @param bool $force Whether to force cache update. * * @return bool */ public function update( bool $force = false ): bool { $result = parent::update( $force ); if ( ! $result ) { return false; } $this->wipe_content_cache(); $this->clear_prepared_templates_cache(); $this->clear_html_batch_cache(); return true; } /** * Get cached templates content. * * @since 1.8.6 * * @return string */ public function get_content_cache(): string { // phpcs:ignore Universal.Operators.DisallowShortTernary.Found return File::get_contents( $this->get_content_cache_file() ) ?: ''; } /** * Save templates content cache. * * @since 1.8.6 * * @param string|mixed $content Templates content. * * @return bool */ public function save_content_cache( $content ): bool { return File::put_contents( $this->get_content_cache_file(), (string) $content ); } /** * Wipe cached templates content. * * @since 1.8.6 */ public function wipe_content_cache(): void { $cache_dir = $this->get_cache_dir(); // Delete the template content cache files. They will be regenerated on the first visit. foreach ( self::CONTENT_CACHE_FILES as $file ) { $cache_file = $cache_dir . $file; if ( is_file( $cache_file ) && is_readable( $cache_file ) ) { // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink unlink( $cache_file ); } } } /** * Get templates content cache file path. * * @since 1.8.6 * * @return string */ private function get_content_cache_file(): string { $context = wpforms_is_admin_page( 'templates' ) ? 'admin-page' : 'builder'; return File::get_cache_dir() . self::CONTENT_CACHE_FILES[ $context ]; } /** * Get prepared templates data from the cache. * * @since 2.0.0 * * @return array Cached templates data or empty array. */ public function get_prepared_templates_cache(): array { $cache = Transient::get( $this->get_prepared_cache_key() ); if ( ! is_array( $cache ) || empty( $cache ) ) { return []; } if ( ! isset( $cache['templates'], $cache['version'], $cache['timestamp'] ) ) { return []; } if ( $cache['version'] !== WPFORMS_VERSION ) { $this->clear_prepared_templates_cache(); return []; } return $cache['templates']; } /** * Save prepared templates data to cache. * * @since 2.0.0 * * @param array|mixed $templates Prepared templates data. * * @return bool True on success. */ public function save_prepared_templates_cache( $templates ): bool { if ( ! is_array( $templates ) ) { return false; } $cache_key = $this->get_prepared_cache_key(); $cache_data = [ 'templates' => $templates, 'version' => WPFORMS_VERSION, 'timestamp' => time(), ]; $result = Transient::set( $cache_key, $cache_data, $this->settings['ajax_cache_ttl'] ); // Register the key for bulk clearing. $this->register_cache_key( self::PREPARED_DATA_REGISTRY, $cache_key ); return $result; } /** * Clear prepared templates cache. * * @since 2.0.0 * * @return bool True on success. */ public function clear_prepared_templates_cache(): bool { $keys = Transient::get( self::PREPARED_DATA_REGISTRY ); if ( is_array( $keys ) ) { foreach ( $keys as $key ) { Transient::delete( $key ); } } Transient::delete( self::PREPARED_DATA_REGISTRY ); return true; } /** * Get user-specific prepared templates cache key. * * @since 2.0.0 * * @return string Transient key. */ private function get_prepared_cache_key(): string { return self::PREPARED_DATA_KEY . '_' . get_current_user_id(); } /** * Get rendered HTML batch from cache. * * @since 2.0.0 * * @param string $cache_key Unique cache key for this batch. * * @return array|false Cached batch data or false. */ public function get_html_batch_cache( string $cache_key ) { $transient_key = self::HTML_BATCH_PREFIX . md5( $cache_key ); return Transient::get( $transient_key ); } /** * Save a rendered HTML batch to cache. * * @since 2.0.0 * * @param string $cache_key Unique cache key for this batch. * @param array $batch_data Batch response data. * * @return bool True on success. */ public function save_html_batch_cache( string $cache_key, array $batch_data ): bool { $transient_key = self::HTML_BATCH_PREFIX . md5( $cache_key ); $result = Transient::set( $transient_key, $batch_data, $this->settings['ajax_cache_ttl'] ); // Register the key for bulk clearing. $this->register_cache_key( self::HTML_BATCH_REGISTRY, $transient_key ); return $result; } /** * Clear all HTML batch caches. * * @since 2.0.0 * * @return int Number of deleted transients. */ private function clear_html_batch_cache(): int { $keys = Transient::get( self::HTML_BATCH_REGISTRY ); $deleted = 0; if ( is_array( $keys ) ) { foreach ( $keys as $key ) { if ( Transient::delete( $key ) ) { ++$deleted; } } } Transient::delete( self::HTML_BATCH_REGISTRY ); return $deleted; } /** * Register a cache key in a registry transient for bulk clearing. * * @since 2.0.0 * * @param string $registry_key Registry transient key. * @param string $cache_key Cache key to register. */ private function register_cache_key( string $registry_key, string $cache_key ): void { $keys = Transient::get( $registry_key ); if ( ! is_array( $keys ) ) { $keys = []; } if ( in_array( $cache_key, $keys, true ) ) { return; } $keys[] = $cache_key; // Set expiration matching the AJAX cache TTL to prevent indefinite growth. Transient::set( $registry_key, $keys, $this->settings['ajax_cache_ttl'] ); } /** * Clear all caches. * * @since 2.0.0 */ public function clear_all(): void { $this->clear_prepared_templates_cache(); $this->clear_html_batch_cache(); $this->wipe_content_cache(); } } Builder/ImageUpload.php 0000644 00000001425 15252506741 0011043 0 ustar 00 <?php namespace WPForms\Admin\Builder; /** * Image Upload functionality for the Form Builder Settings. * * @since 1.9.7.3 */ class ImageUpload { /** * Initialize class. * * @since 1.9.7.3 */ public function init(): void { $this->hooks(); } /** * Register hooks. * * @since 1.9.7.3 */ public function hooks(): void { add_action( 'wpforms_builder_enqueues', [ $this, 'enqueues' ] ); } /** * Enqueue assets for the Form Builder. * * @since 1.9.7.3 */ public function enqueues(): void { $min = wpforms_get_min_suffix(); wp_enqueue_script( 'wpforms-builder-settings-image-upload', WPFORMS_PLUGIN_URL . "assets/js/admin/builder/image-upload{$min}.js", [ 'wp-util', 'wpforms-builder-settings' ], WPFORMS_VERSION, true ); } } Builder/AntiSpam.php 0000644 00000032742 15252506741 0010376 0 ustar 00 <?php namespace WPForms\Admin\Builder; use WPForms\Education\ActiveLayer\Helper as ActiveLayer; use WPForms\Forms\Akismet; use WPForms_Builder_Panel_Settings; /** * AntiSpam class. * * @since 1.7.8 */ class AntiSpam { /** * Form data and settings. * * @since 1.7.8 * * @var array */ private $form_data; /** * Init class. * * @since 1.7.8 */ public function init() { $this->hooks(); } /** * Register hooks. * * @since 1.7.8 */ protected function hooks() { add_action( 'wpforms_form_settings_panel_content', [ $this, 'panel_content' ], 10, 2 ); } /** * Add a content for `Spam Protection and Security` panel. * * @since 1.7.8 * * @param WPForms_Builder_Panel_Settings $instance Settings panel instance. */ public function panel_content( $instance ) { $this->form_data = $this->update_settings_form_data( $instance->form_data ); echo '<div class="wpforms-panel-content-section wpforms-panel-content-section-anti_spam">'; echo '<div class="wpforms-panel-content-section-title">'; esc_html_e( 'Spam Protection and Security', 'wpforms-lite' ); echo '</div>'; $antispam = wpforms_panel_field( 'toggle', 'settings', 'antispam_v3', $this->form_data, __( 'Enable modern anti-spam protection', 'wpforms-lite' ), [ 'value' => (int) ! empty( $this->form_data['settings']['antispam_v3'] ), 'tooltip' => __( 'Turn on invisible modern spam protection.', 'wpforms-lite' ), ], false ); wpforms_panel_fields_group( $antispam, [ 'description' => __( 'Behind-the-scenes spam filtering that\'s invisible to your visitors.', 'wpforms-lite' ), 'title' => __( 'Protection', 'wpforms-lite' ), ] ); if ( ! empty( $this->form_data['settings']['antispam'] ) && empty( $this->form_data['settings']['antispam_v3'] ) ) { wpforms_panel_field( 'toggle', 'settings', 'antispam', $this->form_data, __( 'Enable anti-spam protection', 'wpforms-lite' ), [ 'tooltip' => __( 'Turn on invisible spam protection.', 'wpforms-lite' ), ] ); } if ( ! empty( $this->form_data['settings']['honeypot'] ) && empty( $this->form_data['settings']['antispam_v3'] ) ) { wpforms_panel_field( 'toggle', 'settings', 'honeypot', $this->form_data, __( 'Enable anti-spam honeypot', 'wpforms-lite' ) ); } $this->akismet_settings(); $this->store_spam_entries_settings(); $this->time_limit_settings(); $this->captcha_settings(); // Hidden setting to store blocked entries by filtering as a spam. // This setting is needed to keep backward compatibility with old forms. wpforms_panel_field( 'checkbox', 'anti_spam', 'filtering_store_spam', $this->form_data, '', [ 'parent' => 'settings', 'class' => 'wpforms-hidden', ] ); /** * Fires once in the end of content panel before Also Available section. * * @since 1.7.8 * * @param array $form_data Form data and settings. */ do_action( 'wpforms_admin_builder_anti_spam_panel_content', $this->form_data ); wpforms_panel_fields_group( $this->get_also_available_block(), [ 'unfoldable' => true, 'default' => 'opened', 'group' => 'also_available', 'title' => __( 'Also Available', 'wpforms-lite' ), 'borders' => [ 'top' ], ] ); echo '</div>'; } /** * Update the form data on the builder settings panel. * * @since 1.9.2 * * @param array $form_data Form data. * * @return array */ private function update_settings_form_data( array $form_data ): array { if ( ! $form_data ) { return $form_data; } // Update `Filtering` store spam entries behaviour. // Enable for new forms and old forms without any `Filtering` setting enabled. if ( empty( $form_data['settings']['anti_spam']['filtering_store_spam'] ) && empty( $form_data['settings']['anti_spam']['country_filter']['enable'] ) && empty( $form_data['settings']['anti_spam']['keyword_filter']['enable'] ) ) { $form_data['settings']['anti_spam']['filtering_store_spam'] = true; } return $form_data; } /** * Output the *CAPTCHA settings. * * @since 1.7.8 */ private function captcha_settings() { $captcha_settings = wpforms_get_captcha_settings(); if ( empty( $captcha_settings['provider'] ) || $captcha_settings['provider'] === 'none' ) { return; } if ( $captcha_settings['provider'] !== 'hcaptcha' && ( empty( $captcha_settings['site_key'] ) || empty( $captcha_settings['secret_key'] ) ) ) { return; } if ( $captcha_settings['provider'] === 'hcaptcha' && empty( $captcha_settings['site_key'] ) ) { return; } $captcha_types = [ 'hcaptcha' => __( 'Enable hCaptcha', 'wpforms-lite' ), 'turnstile' => __( 'Enable Cloudflare Turnstile', 'wpforms-lite' ), 'recaptcha' => [ 'v2' => __( 'Enable Google Checkbox v2 reCAPTCHA', 'wpforms-lite' ), 'invisible' => __( 'Enable Google Invisible v2 reCAPTCHA', 'wpforms-lite' ), 'v3' => __( 'Enable Google v3 reCAPTCHA', 'wpforms-lite' ), ], ]; $is_recaptcha = $captcha_settings['provider'] === 'recaptcha'; $captcha_types = $is_recaptcha ? $captcha_types['recaptcha'] : $captcha_types; $captcha_key = $is_recaptcha ? $captcha_settings['recaptcha_type'] : $captcha_settings['provider']; $label = ! empty( $captcha_types[ $captcha_key ] ) ? $captcha_types[ $captcha_key ] : ''; $recaptcha = wpforms_panel_field( 'toggle', 'settings', 'recaptcha', $this->form_data, $label, [ 'data' => [ 'provider' => $captcha_settings['provider'], ], 'tooltip' => __( 'Enable third-party CAPTCHAs to prevent form submissions from bots.', 'wpforms-lite' ), ], false ); wpforms_panel_fields_group( $recaptcha, [ 'description' => __( 'Automated tests that help to prevent bots from submitting your forms.', 'wpforms-lite' ), 'title' => __( 'CAPTCHA', 'wpforms-lite' ), 'borders' => [ 'top' ], ] ); } /** * Output the Spam Entries Store settings. * * @since 1.8.3 */ public function store_spam_entries_settings() { if ( ! wpforms()->is_pro() ) { return; } $disable_entries = $this->form_data['settings']['disable_entries'] ?? 0; wpforms_panel_field( 'toggle', 'settings', 'store_spam_entries', $this->form_data, __( 'Store spam entries in the database', 'wpforms-lite' ), [ 'value' => $this->form_data['settings']['store_spam_entries'] ?? 0, 'class' => $disable_entries ? 'wpforms-hidden' : '', ] ); } /** * Output the Time Limit settings. * * @since 1.8.3 */ private function time_limit_settings() { wpforms_panel_field( 'toggle', 'anti_spam', 'enable', $this->form_data, __( 'Enable minimum time to submit', 'wpforms-lite' ), [ 'parent' => 'settings', 'subsection' => 'time_limit', 'tooltip' => __( 'Set a minimum amount of time a user must spend on a form before submitting.', 'wpforms-lite' ), 'input_class' => 'wpforms-panel-field-toggle-next-field', ] ); wpforms_panel_field( 'text', 'anti_spam', 'duration', $this->form_data, __( 'Minimum time to submit', 'wpforms-lite' ), [ 'parent' => 'settings', 'subsection' => 'time_limit', 'type' => 'number', 'min' => 1, 'default' => 2, 'after' => sprintf( '<span class="wpforms-panel-field-after">%s</span>', __( 'seconds', 'wpforms-lite' ) ), ] ); } /** * Output the Akismet settings. * * @since 1.7.8 */ private function akismet_settings() { if ( ! Akismet::is_installed() ) { return; } $args = []; if ( ! Akismet::is_configured() ) { $args['data']['akismet-status'] = 'akismet_no_api_key'; } if ( ! Akismet::is_activated() ) { $args['data']['akismet-status'] = 'akismet_not_activated'; } // If Akismet isn't available, disable the Akismet toggle. if ( isset( $args['data'] ) ) { $args['input_class'] = 'wpforms-akismet-disabled'; $args['value'] = '0'; } wpforms_panel_field( 'toggle', 'settings', 'akismet', $this->form_data, __( 'Enable Akismet anti-spam protection', 'wpforms-lite' ), $args ); } /** * Get the Also Available block. * * @since 1.7.8 * * @return string */ private function get_also_available_block() { $get_started_button_text = __( 'Get Started →', 'wpforms-lite' ); $upgrade_to_pro_text = __( 'Upgrade to Pro', 'wpforms-lite' ); $captcha_settings = wpforms_get_captcha_settings(); $utm_medium = 'Builder Settings'; $blocks = [ 'country_filter' => [ 'logo' => WPFORMS_PLUGIN_URL . 'assets/images/anti-spam/country-filter.svg', 'title' => __( 'Country Filter', 'wpforms-lite' ), 'description' => __( 'Stop spam at its source. Allow or deny entries from specific countries.', 'wpforms-lite' ), 'link' => wpforms_admin_upgrade_link( $utm_medium, 'Country Filter Feature' ), 'link_text' => $upgrade_to_pro_text, 'class' => 'wpforms-panel-content-also-available-item-upgrade-to-pro', 'show' => ! wpforms()->is_pro(), ], 'keyword_filter' => [ 'logo' => WPFORMS_PLUGIN_URL . 'assets/images/anti-spam/keyword-filter.svg', 'title' => __( 'Keyword Filter', 'wpforms-lite' ), 'description' => __( 'Block form entries that contain specific words or phrases that you define.', 'wpforms-lite' ), 'link' => wpforms_admin_upgrade_link( $utm_medium, 'Keyword Filter Feature' ), 'link_text' => $upgrade_to_pro_text, 'class' => 'wpforms-panel-content-also-available-item-upgrade-to-pro', 'show' => ! wpforms()->is_pro(), ], 'custom_captcha' => [ 'logo' => WPFORMS_PLUGIN_URL . 'assets/images/anti-spam/custom-captcha.svg', 'title' => __( 'Custom Captcha', 'wpforms-lite' ), 'description' => __( 'Ask custom questions or require your visitor to answer a random math puzzle.', 'wpforms-lite' ), 'link' => wpforms()->is_pro() ? '#' : wpforms_admin_upgrade_link( $utm_medium, 'Custom Captcha Addon' ), 'link_text' => wpforms()->is_pro() ? __( 'Add to Form', 'wpforms-lite' ) : $upgrade_to_pro_text, 'class' => wpforms()->is_pro() ? 'wpforms-panel-content-also-available-item-add-captcha' : 'wpforms-panel-content-also-available-item-upgrade-to-pro', 'show' => true, ], 'activelayer' => $this->get_activelayer_block(), 'reCAPTCHA' => [ 'logo' => WPFORMS_PLUGIN_URL . 'assets/images/anti-spam/recaptcha.svg', 'title' => 'reCAPTCHA', 'description' => __( 'Add Google\'s free anti-spam service and choose between visible or invisible CAPTCHAs.','wpforms-lite' ), 'link' => wpforms_utm_link( 'https://wpforms.com/docs/how-to-set-up-and-use-recaptcha-in-wpforms/', $utm_medium, 'reCAPTCHA Feature' ), 'link_text' => $get_started_button_text, 'show' => $captcha_settings['provider'] !== 'recaptcha' || empty( wpforms_setting( 'captcha-provider' ) ), ], 'hCaptcha' => [ 'logo' => WPFORMS_PLUGIN_URL . 'assets/images/anti-spam/hcaptcha.svg', 'title' => 'hCaptcha', 'description' => __( 'Turn on free, privacy-oriented spam prevention that displays a visual CAPTCHA.','wpforms-lite' ), 'link' => wpforms_utm_link( 'https://wpforms.com/docs/how-to-set-up-and-use-hcaptcha-in-wpforms/', $utm_medium, 'hCaptcha Feature' ), 'link_text' => $get_started_button_text, 'show' => $captcha_settings['provider'] !== 'hcaptcha', ], 'turnstile' => [ 'logo' => WPFORMS_PLUGIN_URL . 'assets/images/anti-spam/cloudflare.svg', 'title' => 'Cloudflare Turnstile', 'description' => __( 'Enable free, CAPTCHA-like spam protection that protects data privacy.','wpforms-lite' ), 'link' => wpforms_utm_link( 'https://wpforms.com/docs/setting-up-cloudflare-turnstile/', $utm_medium, 'Cloudflare Turnstile Feature' ), 'link_text' => $get_started_button_text, 'show' => $captcha_settings['provider'] !== 'turnstile', ], 'akismet' => [ 'logo' => WPFORMS_PLUGIN_URL . 'assets/images/anti-spam/akismet.svg', 'title' => 'Akismet', 'description' => __( 'Integrate the powerful spam-fighting service trusted by millions of sites.','wpforms-lite' ), 'link' => wpforms_utm_link( 'https://wpforms.com/docs/setting-up-akismet-anti-spam-protection/', $utm_medium, 'Akismet Feature' ), 'link_text' => $get_started_button_text, 'show' => ! Akismet::is_installed(), ], ]; return wpforms_render( 'builder/antispam/also-available', [ 'blocks' => $blocks ], true ); } /** * Build the ActiveLayer entry for the Also Available block. Wraps * `Helper::get_modal_data()` and overrides the CTA text with copy * specific to the form builder surface. * * @since 1.10.0.5 * * @return array */ private function get_activelayer_block(): array { $modal = ActiveLayer::get_modal_data(); $action = $modal['attrs']['data-action'] ?? ''; if ( $action === 'install' ) { $modal['link_text'] = __( 'Install & Activate', 'wpforms-lite' ); } elseif ( $action === 'activate' ) { $modal['link_text'] = __( 'Activate', 'wpforms-lite' ); } return array_merge( [ 'logo' => WPFORMS_PLUGIN_URL . 'assets/images/anti-spam/activelayer.svg', 'title' => 'ActiveLayer', 'description' => __( 'AI-powered spam protection. No friction for real visitors, higher form conversions.', 'wpforms-lite' ), 'badge' => __( 'Recommended', 'wpforms-lite' ), 'show' => true, ], $modal ); } } Builder/Settings/Themes.php 0000644 00000103721 15252506741 0011703 0 ustar 00 <?php namespace WPForms\Admin\Builder\Settings; use WPForms\Frontend\CSSVars; use WPForms\Integrations\Gutenberg\RestApi; use WPForms\Integrations\Gutenberg\ThemesData; use WPForms_Builder_Panel_Settings; /** * Themes panel. * * @since 1.8.8 */ class Themes { /** * Form data. * * @since 1.9.7 * * @var array */ public $form_data; /** * CSS vars class instance. * * @since 1.9.7 * * @var CSSVars */ protected $css_vars_obj; /** * Rest API class instance. * * @since 1.9.7 * * @var ThemesData */ protected $themes_data_obj; /** * Size options for themes settings. * * @since 1.9.7 * * @var array */ protected $size_options; /** * Border type options for themes settings. * * @since 1.9.7 * * @var array */ private $border_options; /** * Is admin. * * @since 1.9.7 * * @var bool */ private $is_admin; /** * Whether a modern engine is enabled. * * @since 1.9.7 * * @var bool */ private $is_modern; /** * Whether full style is used. * * @since 1.9.7 * * @var bool */ private $is_full_styles; /** * Init class. * * @since 1.8.8 */ public function init(): void { $this->css_vars_obj = wpforms()->obj( 'css_vars' ); $this->is_admin = current_user_can( 'manage_options' ); $this->is_modern = wpforms_get_render_engine() === 'modern'; $this->is_full_styles = (int) wpforms_setting( 'disable-css', '1' ) === 1; $this->size_options = [ 'small' => esc_html__( 'Small', 'wpforms-lite' ), 'medium' => esc_html__( 'Medium', 'wpforms-lite' ), 'large' => esc_html__( 'Large', 'wpforms-lite' ), ]; $this->border_options = [ 'none' => esc_html__( 'None', 'wpforms-lite' ), 'solid' => esc_html__( 'Solid', 'wpforms-lite' ), 'dashed' => esc_html__( 'Dashed', 'wpforms-lite' ), 'dotted' => esc_html__( 'Dotted', 'wpforms-lite' ), ]; $this->hooks(); } /** * Register hooks. * * @since 1.8.8 */ protected function hooks(): void { // If the current user can't add posts, he can't save themes either. Enqueue no-access assets. if ( ! current_user_can( 'edit_posts' ) ) { add_action( 'admin_enqueue_scripts', [ $this, 'enqueues_no_access' ] ); add_filter( 'wpforms_builder_panel_sidebar_section_classes', [ $this, 'add_pro_class' ], 10, 3 ); return; } add_action( 'wpforms_form_settings_panel_content', [ $this, 'panel_content' ] ); add_action( 'wpforms_builder_panel_sidebar_after', [ $this, 'sidebar_content' ], 10, 2 ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueues' ] ); } /** * Enqueue assets for the builder themes. * * @since 1.9.7 */ public function enqueues(): void { $min = wpforms_get_min_suffix(); wp_enqueue_script( 'wpforms-builder-themes', WPFORMS_PLUGIN_URL . "assets/js/admin/builder/themes/builder-themes{$min}.js", [ 'wpforms-builder', 'wp-api-fetch' ], WPFORMS_VERSION, true ); wp_enqueue_style( 'wpforms-full', WPFORMS_PLUGIN_URL . "assets/css/frontend/modern/wpforms-full{$min}.css", [], WPFORMS_VERSION ); wp_localize_script( 'wpforms-builder-themes', 'wpforms_builder_themes', $this->get_localize_data() ); wp_add_inline_style( 'wpforms-full', $this->css_vars_obj->get_root_vars_css() ); } /** * Enqueue assets for the builder themes for the users who don't have access to the theme settings. * * @since 1.9.8 */ public function enqueues_no_access(): void { $min = wpforms_get_min_suffix(); wp_enqueue_script( 'wpforms-builder-themes-no-access', WPFORMS_PLUGIN_URL . "assets/js/admin/builder/themes/builder-themes-no-access{$min}.js", [ 'wpforms-builder', 'wp-api-fetch' ], WPFORMS_VERSION, true ); wp_localize_script( 'wpforms-builder-themes-no-access', 'wpforms_builder_themes_no_access', $this->get_localize_data() ); } /** * Add a class to the themes section if the user doesn't have access to it. * * @since 1.9.8 * * @param array $classes Sidebar section classes. * @param string $name Sidebar section name. * @param string $slug Sidebar section slug. * * @return array * @noinspection PhpUnusedParameterInspection */ public function add_pro_class( array $classes, string $name, string $slug ): array { if ( $slug !== 'themes' ) { return $classes; } return array_merge( $classes, [ 'wpforms-panel-sidebar-section-no-access' ] ); } /** * Get localize data. * * @since 1.9.7 * * @return array */ protected function get_localize_data(): array { return [ 'modules' => $this->get_modules(), 'sizes' => [ 'field-size' => CSSVars::FIELD_SIZE, 'label-size' => CSSVars::LABEL_SIZE, 'button-size' => CSSVars::BUTTON_SIZE, 'container-shadow-size' => CSSVars::CONTAINER_SHADOW_SIZE, ], 'strings' => [ 'heads_up' => esc_html__( 'Heads Up!', 'wpforms-lite' ), 'themes_error' => esc_html__( 'Error loading themes. Please try again later.', 'wpforms-lite' ), 'button_background' => esc_html__( 'Button Background', 'wpforms-lite' ), 'button_text' => esc_html__( 'Button Text', 'wpforms-lite' ), 'copy_paste_error' => esc_html__( 'There was an error parsing your JSON code. Please check your code and try again.', 'wpforms-lite' ), 'field_label' => esc_html__( 'Field Label', 'wpforms-lite' ), 'field_sublabel' => esc_html__( 'Field Sublabel', 'wpforms-lite' ), 'field_border' => esc_html__( 'Field Border', 'wpforms-lite' ), 'theme_delete_title' => esc_html__( 'Delete Form Theme', 'wpforms-lite' ), // Translators: %1$s: Theme name. 'theme_delete_confirm' => esc_html__( 'Are you sure you want to delete the %1$s theme?', 'wpforms-lite' ), 'theme_delete_cant_undone' => esc_html__( 'This cannot be undone.', 'wpforms-lite' ), 'theme_delete_yes' => esc_html__( 'Yes, Delete', 'wpforms-lite' ), 'theme_copy' => esc_html__( 'Copy', 'wpforms-lite' ), 'theme_custom' => esc_html__( 'Custom Theme', 'wpforms-lite' ), 'theme_noname' => esc_html__( 'Noname Theme', 'wpforms-lite' ), 'pro_sections' => [ 'background' => esc_html__( 'Background Styles', 'wpforms-lite' ), 'container' => esc_html__( 'Container Styles', 'wpforms-lite' ), 'themes' => esc_html__( 'Themes', 'wpforms-lite' ), ], 'permission_modal' => [ 'title' => esc_html__( 'Insufficient Permissions', 'wpforms-lite' ), 'content' => esc_html__( "Sorry, your user role doesn't have permission to access this feature.", 'wpforms-lite' ), 'confirm' => esc_html__( 'OK', 'wpforms-lite' ), ], ], 'isAdmin' => $this->is_admin, 'isPro' => wpforms()->is_pro(), 'isModern' => $this->is_modern, 'isFullStyles' => $this->is_full_styles, 'route_namespace' => RestApi::ROUTE_NAMESPACE, ]; } /** * Get Form Builder themes modules. * * @since 1.9.7 * * @return array Modules list. */ public function get_modules(): array { $min = wpforms_get_min_suffix(); return [ [ 'name' => 'common', 'path' => "./modules/common{$min}.js", ], [ 'name' => 'themes', 'path' => "./modules/themes{$min}.js", ], [ 'name' => 'stockPhotos', 'path' => "./modules/stock-photos{$min}.js", ], [ 'name' => 'background', 'path' => "./modules/background{$min}.js", ], [ 'name' => 'advancedSettings', 'path' => "./modules/advanced-settings{$min}.js", ], ]; } /** * Add a content for `Themes` panel. * * @since 1.8.8 * * @param WPForms_Builder_Panel_Settings $instance Settings panel instance. * * @noinspection HtmlUnknownTarget */ public function panel_content( WPForms_Builder_Panel_Settings $instance ): void { $this->form_data = $instance->form_data; $url = wpforms_utm_link( 'https://wpforms.com/docs/styling-your-forms/', 'Builder Themes', 'Description Link' ); ?> <div class="wpforms-panel-content-section wpforms-panel-content-section-themes"> <div class="wpforms-panel-content-section-themes-inner"> <div class="wpforms-panel-content-section-themes-top"> <div class="wpforms-panel-content-section-title"> <?php esc_html_e( 'Form Themes', 'wpforms-lite' ); ?> </div> <div class="wpforms-panel-content-section-themes-preview"> <p class="wpforms-panel-content-section-themes-preview-description"> <?php echo wp_kses_post( sprintf( /* translators: %s - URL to the documentation. */ __( 'Customize the look and feel of your form with premade themes or simple style settings that allow you to use your own colors to match your brand. Themes and style settings are also available in the Block Editor and Elementor, where you can see a realtime preview. <a href="%s" target="_blank">Learn more about styling your forms</a>.', 'wpforms-lite' ), $url ) ); ?> </p> <div class="wpforms-alert wpforms-alert-warning wpforms-alert-warning-wide wpforms-builder-themes-preview-notice"> <h4> <?php esc_html_e( 'Preview only', 'wpforms-lite' ); ?> </h4> <p> <?php esc_html_e( 'The fields shown below are for demo purposes and do not reflect the fields in your actual form.', 'wpforms-lite' ); ?> </p> </div> <?php echo wpforms_render( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 'builder/themes/notices', [ 'is_modern' => $this->is_modern, 'is_full_styles' => $this->is_full_styles, ], true ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wpforms_render( 'builder/themes/preview' ); ?> </div> </div> <!-- .wpforms-panel-content-section-themes-top --> </div> <!-- .wpforms-panel-content-section-themes-inner --> </div> <!-- .wpforms-panel-content-section-themes --> <?php } /** * Add content for the Themes Sidebar. * * @param object $form Current form object. * @param string $slug Current panel slug. * * @since 1.9.7 */ public function sidebar_content( $form, $slug ): void { if ( $slug !== 'settings' ) { return; } $form_obj = wpforms()->obj( 'form' ); if ( ! $form_obj || ! isset( $form->ID ) ) { return; } $form_data = $form_obj->get( $form->ID, [ 'content_only' => true ] ); $this->form_data = $form_data; $this->show_sidebar_html(); } /** * Show sidebar HTML. * * @since 1.9.7 */ private function show_sidebar_html(): void { ?> <div id="wpforms-builder-themes-sidebar" class="wpforms-hidden"> <div class="wpforms-builder-themes-sidebar-head"> <button id="wpforms-builder-themes-back"> <?php esc_html_e( 'Back to Settings', 'wpforms-lite' ); ?></button> </div> <div id="wpforms-builder-themes-sidebar-tabs"> <a href="#" class="active"><?php esc_html_e( 'General', 'wpforms-lite' ); ?></a> <?php if ( $this->is_admin ) : ?> <a href="#"><?php esc_html_e( 'Advanced', 'wpforms-lite' ); ?></a> <?php endif; ?> </div> <div class="wpforms-builder-themes-sidebar-content"> <div class="wpforms-builder-themes-sidebar-general wpforms-builder-themes-sidebar-tab-content"> <?php $this->show_sidebar_themes(); ?> <div class="wpforms-builder-themes-restricted <?php echo esc_attr( ! $this->is_admin ? 'wpforms-hidden' : '' ); ?>"> <?php $this->show_sidebar_field_styles(); ?> <?php $this->show_sidebar_label_styles(); ?> <?php $this->show_sidebar_button_styles(); ?> <?php $this->show_sidebar_container_styles(); ?> <?php $this->show_sidebar_background_styles(); ?> <?php $this->show_sidebar_other_styles(); ?> </div> </div> <div class="wpforms-builder-themes-sidebar-advanced wpforms-builder-themes-sidebar-tab-content wpforms-hidden"> <?php $this->show_sidebar_advanced(); ?> </div> </div> </div> <?php } /** * Show sidebar themes. * * @since 1.9.7 * * @return void */ private function show_sidebar_themes(): void { ?> <div class="wpforms-add-fields-group"> <a href="#" class="wpforms-add-fields-heading" data-group="themes"> <span><?php esc_html_e( 'Themes', 'wpforms-lite' ); ?></span> <i class="fa fa-angle-down"></i> </a> <div class="wpforms-add-fields-buttons"> <?php wpforms_panel_field( 'text', 'themes', 'wpformsTheme', $this->form_data, esc_html__( 'Theme', 'wpforms-lite' ), [ 'parent' => 'settings', 'type' => 'hidden', 'value' => $this->form_data['settings']['themes']['wpformsTheme'] ?? 'default', 'class' => 'wpforms-hidden', ] ); wpforms_panel_field( 'text', 'themes', 'isCustomTheme', $this->form_data, false, [ 'parent' => 'settings', 'type' => 'hidden', 'value' => $this->form_data['settings']['themes']['isCustomTheme'] ?? '', 'class' => 'wpforms-hidden', ] ); ?> <div class="wpforms-builder-themes-control"></div> <?php wpforms_panel_field( 'text', 'themes', 'themeName', $this->form_data, esc_html__( 'Theme Name', 'wpforms-lite' ), [ 'parent' => 'settings', 'type' => 'text', 'value' => $this->form_data['settings']['themes']['themeName'] ?? '', 'class' => 'wpforms-hidden', ] ); ?> <button id="wpforms-builder-themer-remove-theme" class="wpforms-hidden"><?php esc_html_e( 'Delete Theme', 'wpforms-lite' ); ?></button> </div> </div> <?php } /** * Show sidebar field styles. * * @since 1.9.7 * * @return void */ private function show_sidebar_field_styles(): void { ?> <div class="wpforms-add-fields-group"> <a href="#" class="wpforms-add-fields-heading" data-group="field_styles"> <span><?php esc_html_e( 'Field Styles', 'wpforms-lite' ); ?></span> <i class="fa fa-angle-down"></i> </a> <div class="wpforms-add-fields-buttons"> <div class="wpforms-builder-themes-fields-row"> <?php wpforms_panel_field( 'select', 'themes', 'fieldSize', $this->form_data, esc_html__( 'Size', 'wpforms-lite' ), [ 'parent' => 'settings', 'options' => $this->size_options, 'value' => $this->form_data['settings']['themes']['fieldSize'] ?? 'medium', ] ); wpforms_panel_field( 'select', 'themes', 'fieldBorderStyle', $this->form_data, esc_html__( 'Border', 'wpforms-lite' ), [ 'parent' => 'settings', 'options' => $this->border_options, 'value' => $this->form_data['settings']['themes']['fieldBorderStyle'] ?? 'solid', ] ); ?> </div> <div class="wpforms-builder-themes-fields-row"> <?php wpforms_panel_field( 'text', 'themes', 'fieldBorderSize', $this->form_data, esc_html__( 'Border Size', 'wpforms-lite' ), [ 'parent' => 'settings', 'type' => 'number', 'min' => 0, 'value' => $this->form_data['settings']['themes']['fieldBorderSize'] ?? '1', 'input_class' => 'wpforms-builder-themes-number-input', 'class' => 'wpforms-builder-themes-number-input-wrapper', ] ); wpforms_panel_field( 'text', 'themes', 'fieldBorderRadius', $this->form_data, esc_html__( 'Border Radius', 'wpforms-lite' ), [ 'parent' => 'settings', 'type' => 'number', 'min' => 0, 'value' => $this->form_data['settings']['themes']['fieldBorderRadius'] ?? '3', 'input_class' => 'wpforms-builder-themes-number-input', 'class' => 'wpforms-builder-themes-number-input-wrapper', ] ); ?> </div> <div class="wpforms-builder-themes-fields-row"> <?php wpforms_panel_field( 'color', 'themes', 'fieldBackgroundColor', $this->form_data, esc_html__( 'Background', 'wpforms-lite' ), [ 'parent' => 'settings', 'value' => $this->form_data['settings']['themes']['fieldBackgroundColor'] ?? CSSVars::ROOT_VARS['field-background-color'], ] ); wpforms_panel_field( 'color', 'themes', 'fieldBorderColor', $this->form_data, esc_html__( 'Border', 'wpforms-lite' ), [ 'parent' => 'settings', 'value' => $this->form_data['settings']['themes']['fieldBorderColor'] ?? CSSVars::ROOT_VARS['field-border-color'], ] ); wpforms_panel_field( 'color', 'themes', 'fieldTextColor', $this->form_data, esc_html__( 'Text', 'wpforms-lite' ), [ 'parent' => 'settings', 'value' => $this->form_data['settings']['themes']['fieldTextColor'] ?? CSSVars::ROOT_VARS['field-text-color'], ] ); ?> </div> </div> </div> <?php } /** * Show sidebar label styles. * * @since 1.9.7 * * @return void */ private function show_sidebar_label_styles(): void { ?> <div class="wpforms-add-fields-group"> <a href="#" class="wpforms-add-fields-heading" data-group="label_styles"> <span><?php esc_html_e( 'Label Styles', 'wpforms-lite' ); ?></span> <i class="fa fa-angle-down"></i> </a> <div class="wpforms-add-fields-buttons"> <div class="wpforms-builder-themes-fields-row"> <?php wpforms_panel_field( 'select', 'themes', 'labelSize', $this->form_data, esc_html__( 'Size', 'wpforms-lite' ), [ 'parent' => 'settings', 'options' => $this->size_options, 'value' => $this->form_data['settings']['themes']['labelSize'] ?? 'medium', ] ); ?> </div> <div class="wpforms-builder-themes-fields-row"> <?php wpforms_panel_field( 'color', 'themes', 'labelColor', $this->form_data, esc_html__( 'Label', 'wpforms-lite' ), [ 'parent' => 'settings', 'value' => $this->form_data['settings']['themes']['labelColor'] ?? CSSVars::ROOT_VARS['label-color'], ] ); wpforms_panel_field( 'color', 'themes', 'labelSublabelColor', $this->form_data, esc_html__( 'Sublabel', 'wpforms-lite' ), [ 'parent' => 'settings', 'value' => $this->form_data['settings']['themes']['labelSublabelColor'] ?? CSSVars::ROOT_VARS['label-sublabel-color'], ] ); wpforms_panel_field( 'color', 'themes', 'labelErrorColor', $this->form_data, esc_html__( 'Error', 'wpforms-lite' ), [ 'parent' => 'settings', 'value' => $this->form_data['settings']['themes']['labelErrorColor'] ?? CSSVars::ROOT_VARS['label-error-color'], ] ); ?> </div> </div> </div> <?php } /** * Show sidebar button styles. * * @since 1.9.7 * * @return void */ private function show_sidebar_button_styles(): void { ?> <div class="wpforms-add-fields-group"> <a href="#" class="wpforms-add-fields-heading" data-group="button_styles"> <span><?php esc_html_e( 'Button Styles', 'wpforms-lite' ); ?></span> <i class="fa fa-angle-down"></i> </a> <div class="wpforms-add-fields-buttons"> <div class="wpforms-builder-themes-fields-row"> <?php wpforms_panel_field( 'select', 'themes', 'buttonSize', $this->form_data, esc_html__( 'Size', 'wpforms-lite' ), [ 'parent' => 'settings', 'options' => $this->size_options, 'value' => $this->form_data['settings']['themes']['buttonSize'] ?? 'medium', ] ); wpforms_panel_field( 'select', 'themes', 'buttonBorderStyle', $this->form_data, esc_html__( 'Border', 'wpforms-lite' ), [ 'parent' => 'settings', 'options' => $this->border_options, 'value' => $this->form_data['settings']['themes']['buttonBorderStyle'] ?? CSSVars::ROOT_VARS['button-border-style'], ] ); ?> </div> <div class="wpforms-builder-themes-fields-row"> <?php wpforms_panel_field( 'text', 'themes', 'buttonBorderSize', $this->form_data, esc_html__( 'Border Size', 'wpforms-lite' ), [ 'parent' => 'settings', 'type' => 'number', 'min' => 0, 'value' => $this->form_data['settings']['themes']['buttonBorderSize'] ?? '1', 'input_class' => 'wpforms-builder-themes-number-input', 'class' => 'wpforms-builder-themes-number-input-wrapper', ] ); wpforms_panel_field( 'text', 'themes', 'buttonBorderRadius', $this->form_data, esc_html__( 'Border Radius', 'wpforms-lite' ), [ 'parent' => 'settings', 'type' => 'number', 'min' => 0, 'value' => $this->form_data['settings']['themes']['buttonBorderRadius'] ?? '3', 'input_class' => 'wpforms-builder-themes-number-input', 'class' => 'wpforms-builder-themes-number-input-wrapper', ] ); ?> </div> <div class="wpforms-builder-themes-fields-row"> <?php wpforms_panel_field( 'color', 'themes', 'buttonBackgroundColor', $this->form_data, esc_html__( 'Background', 'wpforms-lite' ), [ 'parent' => 'settings', 'value' => $this->form_data['settings']['themes']['buttonBackgroundColor'] ?? CSSVars::ROOT_VARS['button-background-color'], ] ); wpforms_panel_field( 'color', 'themes', 'buttonBorderColor', $this->form_data, esc_html__( 'Border', 'wpforms-lite' ), [ 'parent' => 'settings', 'value' => $this->form_data['settings']['themes']['buttonBorderColor'] ?? CSSVars::ROOT_VARS['button-border-color'], ] ); wpforms_panel_field( 'color', 'themes', 'buttonTextColor', $this->form_data, esc_html__( 'Text', 'wpforms-lite' ), [ 'parent' => 'settings', 'value' => $this->form_data['settings']['themes']['buttonTextColor'] ?? CSSVars::ROOT_VARS['button-text-color'], ] ); ?> </div> </div> </div> <?php } /** * Show sidebar container styles. * * @since 1.9.7 * * @return void */ private function show_sidebar_container_styles(): void { ?> <div class="wpforms-add-fields-group"> <a href="#" class="wpforms-add-fields-heading" data-group="container_styles"> <span><?php esc_html_e( 'Container Styles', 'wpforms-lite' ); ?></span> <i class="fa fa-angle-down"></i> </a> <div class="wpforms-add-fields-buttons wpforms-builder-themes-pro-section"> <div class="wpforms-builder-themes-fields-row"> <?php wpforms_panel_field( 'text', 'themes', 'containerPadding', $this->form_data, esc_html__( 'Padding', 'wpforms-lite' ), [ 'parent' => 'settings', 'type' => 'number', 'min' => 0, 'value' => $this->form_data['settings']['themes']['containerPadding'] ?? '0', 'input_class' => 'wpforms-builder-themes-number-input', 'class' => 'wpforms-builder-themes-number-input-wrapper', ] ); wpforms_panel_field( 'select', 'themes', 'containerBorderStyle', $this->form_data, esc_html__( 'Border', 'wpforms-lite' ), [ 'parent' => 'settings', 'options' => $this->border_options, 'value' => $this->form_data['settings']['themes']['containerBorderStyle'] ?? CSSVars::ROOT_VARS['container-border-style'], ] ); ?> </div> <div class="wpforms-builder-themes-fields-row"> <?php wpforms_panel_field( 'text', 'themes', 'containerBorderWidth', $this->form_data, esc_html__( 'Border Size', 'wpforms-lite' ), [ 'parent' => 'settings', 'type' => 'number', 'min' => 0, 'value' => $this->form_data['settings']['themes']['containerBorderWidth'] ?? '1', 'input_class' => 'wpforms-builder-themes-number-input', 'class' => 'wpforms-builder-themes-number-input-wrapper', ] ); wpforms_panel_field( 'text', 'themes', 'containerBorderRadius', $this->form_data, esc_html__( 'Border Radius', 'wpforms-lite' ), [ 'parent' => 'settings', 'type' => 'number', 'min' => 0, 'value' => $this->form_data['settings']['themes']['containerBorderRadius'] ?? '3', 'input_class' => 'wpforms-builder-themes-number-input', 'class' => 'wpforms-builder-themes-number-input-wrapper', ] ); ?> </div> <div class="wpforms-builder-themes-fields-row"> <?php wpforms_panel_field( 'color', 'themes', 'containerBorderColor', $this->form_data, esc_html__( 'Border', 'wpforms-lite' ), [ 'parent' => 'settings', 'value' => $this->form_data['settings']['themes']['containerBorderColor'] ?? CSSVars::ROOT_VARS['container-border-color'], ] ); wpforms_panel_field( 'select', 'themes', 'containerShadowSize', $this->form_data, esc_html__( 'Shadow', 'wpforms-lite' ), [ 'parent' => 'settings', 'options' => [ 'none' => esc_html__( 'None', 'wpforms-lite' ), 'small' => esc_html__( 'Small', 'wpforms-lite' ), 'medium' => esc_html__( 'Medium', 'wpforms-lite' ), 'large' => esc_html__( 'Large', 'wpforms-lite' ), ], 'value' => $this->form_data['settings']['themes']['containerShadowSize'] ?? CSSVars::CONTAINER_SHADOW_SIZE['none']['box-shadow'], ] ); ?> </div> </div> </div> <?php } /** * Show sidebar background styles. * * @since 1.9.7 * * @return void */ private function show_sidebar_background_styles(): void { ?> <div class="wpforms-add-fields-group"> <a href="#" class="wpforms-add-fields-heading" data-group="background_styles"><span><?php esc_html_e( 'Background Styles', 'wpforms-lite' ); ?></span><i class="fa fa-angle-down"></i></a> <div class="wpforms-add-fields-buttons wpforms-builder-themes-pro-section"> <div class="wpforms-builder-themes-fields-row"> <?php wpforms_panel_field( 'color', 'themes', 'backgroundColor', $this->form_data, esc_html__( 'Color', 'wpforms-lite' ), [ 'parent' => 'settings', 'value' => $this->form_data['settings']['themes']['backgroundColor'] ?? CSSVars::ROOT_VARS['background-color'], ] ); ?> </div> <div class="wpforms-builder-themes-fields-row"> <?php wpforms_panel_field( 'select', 'themes', 'backgroundImage', $this->form_data, esc_html__( 'Image', 'wpforms-lite' ), [ 'parent' => 'settings', 'options' => [ 'none' => esc_html__( 'None', 'wpforms-lite' ), 'library' => esc_html__( 'Media Library', 'wpforms-lite' ), 'stock' => esc_html__( 'Stock Photo', 'wpforms-lite' ), ], 'value' => $this->form_data['settings']['themes']['backgroundImage'] ?? 'none', ] ); wpforms_panel_field( 'select', 'themes', 'backgroundPosition', $this->form_data, esc_html__( 'Position', 'wpforms-lite' ), [ 'parent' => 'settings', 'options' => [ 'top left' => esc_html__( 'Top Left', 'wpforms-lite' ), 'top center' => esc_html__( 'Top Center', 'wpforms-lite' ), 'top right' => esc_html__( 'Top Right', 'wpforms-lite' ), 'center left' => esc_html__( 'Center Left', 'wpforms-lite' ), 'center center' => esc_html__( 'Center Center', 'wpforms-lite' ), 'center right' => esc_html__( 'Center Right', 'wpforms-lite' ), 'bottom left' => esc_html__( 'Bottom Left', 'wpforms-lite' ), 'bottom center' => esc_html__( 'Bottom Center', 'wpforms-lite' ), 'bottom right' => esc_html__( 'Bottom Right', 'wpforms-lite' ), ], 'value' => $this->form_data['settings']['themes']['backgroundPosition'] ?? CSSVars::ROOT_VARS['background-position'], ] ); ?> </div> <div class="wpforms-builder-themes-fields-row wpforms-builder-themes-conditional-hide"> <?php wpforms_panel_field( 'select', 'themes', 'backgroundRepeat', $this->form_data, esc_html__( 'Repeat', 'wpforms-lite' ), [ 'parent' => 'settings', 'options' => [ 'no-repeat' => esc_html__( 'No Repeat', 'wpforms-lite' ), 'repeat' => esc_html__( 'Tile', 'wpforms-lite' ), 'repeat-x' => esc_html__( 'Repeat X', 'wpforms-lite' ), 'repeat-y' => esc_html__( 'Repeat Y', 'wpforms-lite' ), ], 'value' => $this->form_data['settings']['themes']['backgroundRepeat'] ?? CSSVars::ROOT_VARS['background-repeat'], ] ); wpforms_panel_field( 'select', 'themes', 'backgroundSizeMode', $this->form_data, esc_html__( 'Size', 'wpforms-lite' ), [ 'parent' => 'settings', 'options' => [ 'dimensions' => esc_html__( 'Dimensions', 'wpforms-lite' ), 'cover' => esc_html__( 'Cover', 'wpforms-lite' ), ], 'value' => $this->form_data['settings']['themes']['backgroundSizeMode'] ?? CSSVars::ROOT_VARS['background-size'], ] ); wpforms_panel_field( 'text', 'themes', 'backgroundSize', $this->form_data, false, [ 'parent' => 'settings', 'type' => 'hidden', 'value' => $this->form_data['settings']['themes']['backgroundSize'] ?? CSSVars::ROOT_VARS['background-size'], 'class' => 'wpforms-hidden', ] ); ?> </div> <div class="wpforms-builder-themes-fields-row wpforms-builder-themes-conditional-hide"> <?php wpforms_panel_field( 'text', 'themes', 'backgroundWidth', $this->form_data, esc_html__( 'Width', 'wpforms-lite' ), [ 'parent' => 'settings', 'type' => 'number', 'min' => 0, 'value' => $this->form_data['settings']['themes']['backgroundWidth'] ?? '100', 'input_class' => 'wpforms-builder-themes-number-input', 'class' => 'wpforms-builder-themes-number-input-wrapper', ] ); wpforms_panel_field( 'text', 'themes', 'backgroundHeight', $this->form_data, esc_html__( 'Height', 'wpforms-lite' ), [ 'parent' => 'settings', 'type' => 'number', 'min' => 0, 'value' => $this->form_data['settings']['themes']['backgroundHeight'] ?? '100', 'input_class' => 'wpforms-builder-themes-number-input', 'class' => 'wpforms-builder-themes-number-input-wrapper', ] ); ?> </div> <div class="wpforms-builder-themes-background-selector wpforms-hidden"> <?php wpforms_panel_field( 'text', 'themes', 'backgroundUrl', $this->form_data, false, [ 'parent' => 'settings', 'type' => 'hidden', 'value' => $this->form_data['settings']['themes']['backgroundUrl'] ?? CSSVars::ROOT_VARS['background-url'], 'class' => 'wpforms-hidden', ] ); ?> <button class="wpforms-builder-themes-bg-image-choose wpforms-hidden"><?php esc_html_e( 'Choose Image', 'wpforms-lite' ); ?></button> <div class="wpforms-builder-themes-bg-image-preview wpforms-hidden"></div> <button class="wpforms-builder-themes-bg-image-remove wpforms-hidden"><?php esc_html_e( 'Remove Image', 'wpforms-lite' ); ?></button> </div> </div> </div> <?php } /** * Show sidebar background styles. * * @since 1.9.7 * * @return void */ private function show_sidebar_other_styles(): void { ?> <div class="wpforms-add-fields-group wpforms-hidden"> <a href="#" class="wpforms-add-fields-heading" data-group="other_styles"><span><?php esc_html_e( 'Other Styles', 'wpforms-lite' ); ?></span><i class="fa fa-angle-down"></i></a> <div class="wpforms-add-fields-buttons"> <?php wpforms_panel_field( 'color', 'themes', 'fieldMenuColor', $this->form_data, false, [ 'parent' => 'settings', 'value' => $this->form_data['settings']['themes']['fieldMenuColor'] ?? CSSVars::ROOT_VARS['field-menu-color'], ] ); wpforms_panel_field( 'color', 'themes', 'pageBreakColor', $this->form_data, false, [ 'parent' => 'settings', 'value' => $this->form_data['settings']['themes']['pageBreakColor'] ?? CSSVars::ROOT_VARS['page-break-color'], ] ); ?> </div> </div> <?php } /** * Show sidebar background styles. * * @since 1.9.7 * * @return void */ private function show_sidebar_advanced(): void { if ( ! $this->is_admin ) { return; } $custom_css = $this->form_data['settings']['themes']['customCss'] ?? ''; $paste_json = $this->form_data['settings']['themes']['copyPasteJsonValue'] ?? ''; wpforms_panel_field( 'textarea', 'themes', 'customCss', $this->form_data, esc_html__( 'Custom CSS', 'wpforms-lite' ), [ 'parent' => 'settings', 'value' => is_string( $custom_css ) ? $custom_css : '', // Reset corrupted CSS, as it cannot be reconstructed. 'after' => sprintf( '<span class="wpforms-panel-field-after">%s</span>', __( 'Further customize the look of this form without having to edit theme files.', 'wpforms-lite' ) ), ] ); wpforms_panel_field( 'textarea', 'themes', 'copyPasteJsonValue', $this->form_data, esc_html__( 'Copy / Paste Style Settings', 'wpforms-lite' ), [ 'parent' => 'settings', 'value' => is_string( $paste_json ) ? $paste_json : (string) wp_json_encode( $paste_json ), // Re-encode corrupted JSON snapshot. 'after' => sprintf( '<span class="wpforms-panel-field-after">%s</span>', __( 'If you\'ve copied style settings from another form, you can paste them here to add the same styling to this form. Any current style settings will be overwritten.', 'wpforms-lite' ) ), ] ); } } Builder/Shortcuts.php 0000644 00000006353 15252506741 0010657 0 ustar 00 <?php namespace WPForms\Admin\Builder; /** * Form Builder Keyboard Shortcuts modal content. * * @since 1.6.9 */ class Shortcuts { /** * Initialize class. * * @since 1.6.9 */ public function init(): void { // Terminate initialization if not in the builder. if ( ! wpforms_is_admin_page( 'builder' ) ) { return; } $this->hooks(); } /** * Hooks. * * @since 1.6.9 */ private function hooks(): void { add_filter( 'wpforms_builder_strings', [ $this, 'builder_strings' ] ); add_action( 'wpforms_admin_page', [ $this, 'output' ], 30 ); } /** * Get a shortcut list. * * @since 1.6.9 * * @return array */ private function get_list(): array { return [ 'left' => [ 'ctrl s' => __( 'Save Form', 'wpforms-lite' ), 'ctrl p' => __( 'Preview Form', 'wpforms-lite' ), 'ctrl b' => __( 'Embed Form', 'wpforms-lite' ), 'ctrl f' => __( 'Search Fields', 'wpforms-lite' ), 'ctrl c' => __( 'Copy Fields', 'wpforms-lite' ), 'ctrl v' => __( 'Paste Fields', 'wpforms-lite' ), 'd' => __( 'Duplicate Fields', 'wpforms-lite' ), ], 'right' => [ 'ctrl z' => __( 'Undo', 'wpforms-lite' ), 'ctrl shift z' => __( 'Redo', 'wpforms-lite' ), 'ctrl h' => __( 'Open Help', 'wpforms-lite' ), 'ctrl t' => __( 'Toggle Sidebar', 'wpforms-lite' ), // It is 'alt s' on Windows/Linux, dynamically changed in the modal in admin-builder.js openKeyboardShortcutsModal(). 'ctrl e' => __( 'View Entries', 'wpforms-lite' ), 'ctrl q' => __( 'Close Builder', 'wpforms-lite' ), 'delete' => __( 'Delete Fields', 'wpforms-lite' ), ], ]; } /** * Add Form builder strings. * * @since 1.6.9 * * @param array|mixed $strings Form Builder strings. * * @return array */ public function builder_strings( $strings ): array { $strings = (array) $strings; $strings['shortcuts_modal_title'] = esc_html__( 'Keyboard Shortcuts', 'wpforms-lite' ); $strings['shortcuts_modal_msg'] = esc_html__( 'Handy shortcuts for common actions in the builder.', 'wpforms-lite' ); return $strings; } /** * Generate and output shortcuts modal content as the wp.template. * * @since 1.6.9 */ public function output(): void { echo ' <script type="text/html" id="tmpl-wpforms-builder-keyboard-shortcuts"> <div class="wpforms-columns wpforms-columns-2">'; foreach ( $this->get_list() as $list ) { echo "<ul class='wpforms-column'>"; foreach ( $list as $key => $label ) { $key_parts = explode( ' ', $key ); if ( count( $key_parts ) > 1 ) { printf( '<li> %1$s <span class="shortcut-key shortcut-key-%2$s"> <i>%3$s</i><i>%4$s</i><i>%5$s</i> </span> </li>', esc_html( $label ), esc_html( str_replace( ' ', '-', $key ) ), esc_html( $key_parts[0] ), esc_html( $key_parts[1] ?? '' ), esc_html( $key_parts[2] ?? '' ) ); } else { // Single key like 'delete' or 'd'. printf( '<li> %1$s <span class="shortcut-key shortcut-key-%2$s"> <i>%2$s</i> </span> </li>', esc_html( $label ), esc_html( $key ) ); } } echo '</ul>'; } echo ' </div> </script>'; } } Builder/TemplateSingleCache.php 0000644 00000012602 15252506741 0012514 0 ustar 00 <?php namespace WPForms\Admin\Builder; use WPForms\Helpers\CacheBase; use WPForms\Tasks\Actions\AsyncRequestTask; /** * Single template cache handler. * * @since 1.6.8 */ class TemplateSingleCache extends CacheBase { /** * Template Id (hash). * * @since 1.6.8 * * @var string */ private $id; /** * License data (`key` and `type`). * * @since 1.6.8 * * @var array */ private $license; /** * Determine if the class is allowed to load. * * @since 1.6.8 * * @return bool */ protected function allow_load() { $has_permissions = wpforms_current_user_can( [ 'create_forms', 'edit_forms' ] ); $allowed_requests = wpforms_is_admin_ajax() || wpforms_is_admin_page( 'builder' ) || wpforms_is_admin_page( 'templates' ); $allow = wp_doing_cron() || wpforms_doing_wp_cli() || ( $has_permissions && $allowed_requests ); // phpcs:disable WPForms.PHP.ValidateHooks.InvalidHookName /** * Whether to allow to load this class. * * @since 1.7.2 * * @param bool $allow True or false. */ return (bool) apply_filters( 'wpforms_admin_builder_templatesinglecache_allow_load', $allow ); // phpcs:enable WPForms.PHP.ValidateHooks.InvalidHookName } /** * Re-initialize object with the particular template. * * @since 1.6.8 * * @param string $template_id Template ID (hash). * @param array $license License data. * * @return TemplateSingleCache */ public function instance( $template_id, $license ) { $this->id = $template_id; $this->license = $license; $this->init(); return $this; } /** * Provide settings. * * @since 1.6.8 * * @return array Settings array. */ protected function setup() { return [ // Remote source URL. 'remote_source' => $this->remote_source(), // Cache file. 'cache_file' => $this->get_cache_file_name(), // This filter is documented in wpforms/src/Admin/Builder/TemplatesCache.php. // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName, WPForms.Comments.PHPDocHooks.RequiredHookDocumentation 'cache_ttl' => (int) apply_filters( 'wpforms_admin_builder_templates_cache_ttl', WEEK_IN_SECONDS ), ]; } /** * Generate single template remote URL. * * @since 1.6.8 * * @param bool $cache True if the cache arg should be appended to the URL. * * @return string */ private function remote_source( $cache = false ) { if ( ! isset( $this->license['key'] ) ) { return ''; } $args = [ 'license' => $this->license['key'], 'site' => site_url(), ]; if ( $cache ) { $args['cache'] = 1; } return add_query_arg( $args, 'https://wpforms.com/templates/api/get/' . $this->id ); } /** * Get cached data. * * @since 1.8.2 * * @return array Cached data. */ public function get() { $data = parent::get(); if ( ! $this->updated ) { $this->update_usage_tracking(); } return $data; } /** * Sends a request to update the form template usage tracking database. * * @since 1.7.5 */ private function update_usage_tracking() { $tasks = wpforms()->obj( 'tasks' ); if ( ! $tasks ) { return; } $url = $this->remote_source( true ); $args = [ 'blocking' => false ]; $tasks->create( AsyncRequestTask::ACTION )->async()->params( $url, $args )->register(); } /** * Get cache directory path. * * @since 1.6.8 */ protected function get_cache_dir() { return parent::get_cache_dir() . 'templates/'; } /** * Generate single template cache file name. * * @since 1.6.8 * * @return string. */ private function get_cache_file_name() { return sanitize_key( $this->id ) . '.json'; } /** * Prepare data to store in a local cache. * * @since 1.6.8 * * @param array $data Raw data received by the remote request. * * @return array Prepared data for caching. */ protected function prepare_cache_data( $data ): array { if ( empty( $data ) || ! is_array( $data ) || empty( $data['status'] ) || $data['status'] !== 'success' || empty( $data['data'] ) ) { return []; } $cache_data = $data['data']; $cache_data['data'] = empty( $cache_data['data'] ) ? [] : $cache_data['data']; $cache_data['data']['settings'] = empty( $cache_data['data']['settings'] ) ? [] : $cache_data['data']['settings']; $cache_data['data']['settings']['ajax_submit'] = '1'; // Strip the word "Template" from the end of the template name and form title setting. $cache_data['name'] = preg_replace( '/\sTemplate$/', '', $cache_data['name'] ); $cache_data['data']['settings']['form_title'] = $cache_data['name']; // Unset `From Name` field of the notification settings. // By default, the builder will use the `blogname` option value. unset( $cache_data['data']['settings']['notifications'][1]['sender_name'] ); return $cache_data; } /** * Wipe cache of an empty templates. * * @since 1.7.5 */ public function wipe_empty_templates_cache() { $cache_dir = $this->get_cache_dir(); $files = glob( $cache_dir . '*.json' ); foreach ( $files as $filename ) { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents $content = file_get_contents( $filename ); if ( empty( $content ) || trim( $content ) === '[]' ) { // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink unlink( $filename ); } } } } Builder/Templates.php 0000644 00000113753 15252506741 0010622 0 ustar 00 <?php namespace WPForms\Admin\Builder; use WP_Query; use WPForms\Integrations\Stripe\Helpers as StripeHelpers; /** * Templates class. * * @since 1.6.8 */ class Templates { /** * Templates hash option. * * @since 1.8.6 * * @var string */ const TEMPLATES_HASH_OPTION = 'wpforms_templates_hash'; /** * Favorite templates option. * * @since 1.7.7 * * @var string */ const FAVORITE_TEMPLATES_OPTION = 'wpforms_favorite_templates'; /** * All templates data from API. * * @since 1.6.8 * * @var array */ private $api_templates = []; /** * Template categories data. * * @since 1.6.8 * * @var array */ private $categories; /** * Template subcategories data. * * @since 1.8.4 * * @var array */ private $subcategories; /** * License data. * * @since 1.6.8 * * @var array */ private $license; /** * All licenses list. * * @since 1.6.8 * * @var array */ private $all_licenses; /** * Favorite templates list. * * @since 1.8.6 * * @var array */ private $favorites_list; /** * Templates hash. * * @since 1.8.6 * * @var string */ private $hash; /** * Determine if the class is allowed to load. * * @since 1.6.8 * * @return bool */ private function allow_load(): bool { $has_permissions = wpforms_current_user_can( [ 'create_forms', 'edit_forms' ] ); $allowed_requests = wpforms_is_admin_ajax() || wpforms_is_admin_page( 'builder' ) || wpforms_is_admin_page( 'templates' ); $allow = $has_permissions && $allowed_requests; /** * Whether to allow the form templates functionality to load. * * @since 1.7.2 * * @param bool $allow True or false. */ return (bool) apply_filters( 'wpforms_admin_builder_templates_allow_load', $allow ); } /** * Initialize class. * * @since 1.6.8 */ public function init(): void { if ( ! $this->allow_load() ) { return; } $this->init_license_data(); $this->init_templates_data(); $this->hooks(); } /** * Hooks. * * @since 1.6.8 */ protected function hooks(): void { add_action( 'admin_init', [ $this, 'create_form_on_request' ], 100 ); add_filter( 'wpforms_form_templates_core', [ $this, 'add_templates_to_setup_panel' ], 20 ); add_filter( 'wpforms_create_form_args', [ $this, 'apply_to_new_form' ], 10, 2 ); add_filter( 'wpforms_save_form_args', [ $this, 'apply_to_existing_form' ], 10, 3 ); // Late priority so this runs after every handler that preserves or re-adds payments (e.g. Addons::preserve_payments). add_filter( 'wpforms_save_form_args', [ $this, 'reconcile_payments_on_template_apply' ], 1000, 3 ); add_action( 'admin_print_scripts', [ $this, 'upgrade_banner_template' ] ); add_action( 'admin_print_scripts', [ $this, 'upgrade_lite_banner_template' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueues' ] ); add_action( 'wp_ajax_wpforms_templates_favorite', [ $this, 'ajax_save_favorites' ] ); add_filter( 'wpforms_form_templates', [ $this, 'add_addons_templates' ] ); } /** * Enqueue assets for the Setup panel. * * @since 1.7.7 */ public function enqueues(): void { $min = wpforms_get_min_suffix(); wp_enqueue_script( 'listjs', WPFORMS_PLUGIN_URL . 'assets/lib/list.min.js', [ 'jquery' ], '2.3.0', false ); wp_enqueue_script( 'wpforms-form-templates', WPFORMS_PLUGIN_URL . "assets/js/admin/builder/form-templates{$min}.js", [ 'underscore', 'wp-util', 'listjs' ], WPFORMS_VERSION, true ); wp_localize_script( 'wpforms-form-templates', 'wpforms_form_templates', $this->get_localized_data() ); wp_localize_script( 'wpforms-form-templates', 'wpforms_addons', $this->get_localized_addons() ); } /** * Get localized data for JavaScript. * * @since 2.0.0 * * @return array Localized data. */ private function get_localized_data(): array { $strings = [ 'ajaxurl' => admin_url( 'admin-ajax.php' ), 'admin_nonce' => wp_create_nonce( 'wpforms-admin' ), 'nonce' => wp_create_nonce( 'wpforms-form-templates' ), 'can_install_addons' => wpforms_can_install( 'addon' ), 'activating' => esc_html__( 'Activating', 'wpforms-lite' ), 'cancel' => esc_html__( 'Cancel', 'wpforms-lite' ), 'heads_up' => esc_html__( 'Heads Up!', 'wpforms-lite' ), 'install_confirm' => esc_html__( 'Install and activate', 'wpforms-lite' ), 'activate_confirm' => esc_html__( 'Activate', 'wpforms-lite' ), 'ok' => esc_html__( 'Ok', 'wpforms-lite' ), 'template_addons_error' => esc_html__( 'Could not install OR activate all the required addons. Please download from wpforms.com and install them manually. Would you like to use the template anyway?', 'wpforms-lite' ), 'use_template' => esc_html__( 'Yes, use template', 'wpforms-lite' ), 'delete_template' => esc_html__( 'Yes, Delete', 'wpforms-lite' ), 'delete_template_title' => esc_html__( 'Delete Form Template', 'wpforms-lite' ), 'delete_template_content' => esc_html__( 'Are you sure you want to delete this form template? This cannot be undone.', 'wpforms-lite' ), 'infinite_scroll_enabled' => self::is_infinite_scroll_enabled(), ]; if ( $strings['can_install_addons'] ) { /* translators: %1$s - template name, %2$s - addon name(s). */ $strings['template_addon_prompt'] = esc_html( sprintf( __( 'The %1$s template requires the %2$s. Would you like to install and activate it?', 'wpforms-lite' ), '%template%', '%addons%' ) ); /* translators: %1$s - template name, %2$s - addon name(s). */ $strings['template_addons_prompt'] = esc_html( sprintf( __( 'The %1$s template requires the %2$s. Would you like to install and activate all the required addons?', 'wpforms-lite' ), '%template%', '%addons%' ) ); /* translators: %1$s - template name, %2$s - addon name(s). */ $strings['template_addon_activate'] = esc_html( sprintf( __( 'The %1$s template requires the %2$s. Would you like to activate it?', 'wpforms-lite' ), '%template%', '%addons%' ) ); } else { /* translators: %s - addon name(s). */ $single_form = esc_html( sprintf( __( "To use all of the features in this template, you'll need the %s. Contact your site administrator to install it, then try opening this template again.", 'wpforms-lite' ), '%addons%' ) ); $strings['template_addon_prompt'] = $single_form; $strings['template_addon_activate'] = $single_form; /* translators: %s - addon name(s). */ $strings['template_addons_prompt'] = esc_html( sprintf( __( "To use all of the features in this template, you'll need the %s. Contact your site administrator to install them, then try opening this template again.", 'wpforms-lite' ), '%addons%' ) ); } return $strings; } /** * Whether to enable infinite scroll for templates. * * @since 2.0.0 * * @return bool True when infinite scroll is enabled, false otherwise. */ public static function is_infinite_scroll_enabled(): bool { /** * Whether to enable infinite scroll for templates. * * @since 2.0.0 * * @param bool $enabled True or false. */ return (bool) apply_filters( 'wpforms_admin_builder_templates_infinite_scroll_enabled', true ); } /** * Get localized addons. * * @since 1.8.2 * * @return array */ private function get_localized_addons(): array { $addons_obj = wpforms()->obj( 'addons' ); if ( ! $addons_obj ) { return []; } return wpforms_chain( $addons_obj->get_available() ) ->map( static function ( $addon ) { return [ 'title' => $addon['title'], 'action' => $addon['action'], 'url' => $addon['url'], ]; } ) ->value(); } /** * Init license data. * * @since 1.6.8 */ private function init_license_data() { $this->all_licenses = [ 'lite', 'basic', 'plus', 'pro', 'elite', 'agency', 'ultimate' ]; // User license data. $this->license['key'] = wpforms_get_license_key(); $this->license['type'] = wpforms_get_license_type(); $this->license['type'] = in_array( $this->license['type'], [ 'agency', 'ultimate' ], true ) ? 'elite' : $this->license['type']; $this->license['type'] = empty( $this->license['type'] ) ? 'lite' : $this->license['type']; $this->license['index'] = array_search( $this->license['type'], $this->all_licenses, true ); } /** * Init templates and categories data. * * @since 1.6.8 */ private function init_templates_data() { // Get cached templates data. $cache_obj = wpforms()->obj( 'builder_templates_cache' ); if ( ! $cache_obj ) { return; } $cache_data = $cache_obj->get(); $templates_all = ! empty( $cache_data['templates'] ) ? $this->sort_templates_by_created_at( $cache_data['templates'] ) : []; $this->categories = ! empty( $cache_data['categories'] ) ? $cache_data['categories'] : []; $this->subcategories = ! empty( $cache_data['subcategories'] ) ? $cache_data['subcategories'] : []; $this->init_api_templates( $templates_all ); } /** * Sort templates by their created_at value in ascending order. * * @since 1.8.4 * * @param array $templates Templates to be sorted. * * @return array Sorted templates. */ private function sort_templates_by_created_at( array $templates ): array { uasort( $templates, static function ( $template_a, $template_b ) { if ( $template_a['created_at'] === $template_b['created_at'] ) { return 0; } return $template_a['created_at'] < $template_b['created_at'] ? -1 : 1; } ); return $templates; } /** * Determine if user's license level has access to the template. * * @since 1.6.8 * * @param array $template Template data. * * @return bool */ private function has_access( $template ) { if ( ! empty( $template['has_access'] ) ) { return true; } $template_licenses = empty( $template['license'] ) ? [] : array_map( 'strtolower', (array) $template['license'] ); $has_access = true; foreach ( $template_licenses as $template_license ) { $has_access = $this->license['index'] >= array_search( $template_license, $this->all_licenses, true ); if ( $has_access ) { break; } } return $has_access; } /** * Get favorites templates list. * * @since 1.7.7 * * @param bool $all Optional. True for getting all favorites lists. False by default. * * @return array */ public function get_favorites_list( $all = false ) { $favorites_list = (array) get_option( self::FAVORITE_TEMPLATES_OPTION, [] ); if ( $all ) { return $favorites_list; } $user_id = get_current_user_id(); return $favorites_list[ $user_id ] ?? []; } /** * Update favorites templates list. * * @since 1.8.6 */ public function update_favorites_list() { $this->favorites_list = $this->get_favorites_list(); } /** * Determine if template is marked as favorite. * * @since 1.7.7 * * @param string $template_slug Template slug. * * @return bool */ public function is_favorite( $template_slug ) { if ( $this->favorites_list === null ) { $this->update_favorites_list(); } return isset( $this->favorites_list[ $template_slug ] ); } /** * Save favorites templates. * * @since 1.7.7 */ public function ajax_save_favorites(): void { if ( ! $this->is_valid_ajax_request() ) { wp_send_json_error(); } [ $template_slug, $favorite ] = $this->get_ajax_input(); $favorites = $this->get_favorites_list( true ); $user_id = get_current_user_id(); $is_favorite = $favorite === 'true'; $is_exists = isset( $favorites[ $user_id ][ $template_slug ] ); if ( $is_favorite && $is_exists ) { wp_send_json_success(); } if ( $is_favorite ) { $favorites[ $user_id ][ $template_slug ] = true; } elseif ( $is_exists ) { unset( $favorites[ $user_id ][ $template_slug ] ); } update_option( self::FAVORITE_TEMPLATES_OPTION, $favorites ); // Update and save the template content cache. $templates_cache_obj = wpforms()->obj( 'builder_templates_cache' ); if ( $templates_cache_obj ) { $templates_cache_obj->clear_all(); } wp_send_json_success(); } /** * Get AJAX input. * * @since 1.9.6 * * @return array */ protected function get_ajax_input(): array { // Nonce is checked in the is_valid_ajax_request() method. // phpcs:disable WordPress.Security.NonceVerification.Missing $template_slug = isset( $_POST['slug'] ) ? sanitize_text_field( wp_unslash( $_POST['slug'] ) ) : ''; $favorite = isset( $_POST['favorite'] ) ? sanitize_key( wp_unslash( $_POST['favorite'] ) ) : ''; return [ $template_slug, $favorite ]; // phpcs:enable WordPress.Security.NonceVerification.Missing } /** * Determine if the AJAX request is valid. * * @since 1.9.5 * * @return bool */ private function is_valid_ajax_request(): bool { return check_ajax_referer( 'wpforms-form-templates', 'nonce', false ) && wpforms_current_user_can( 'create_forms' ) && isset( $_POST['slug'], $_POST['favorite'] ); } /** * Determine if the template exists and the customer has access to it. * * @since 1.7.5.3 * * @param string $slug Template slug or ID. * * @return bool */ public function is_valid_template( $slug ) { $template = $this->get_template_by_id( $slug ); if ( ! $template ) { return ! empty( $this->get_template_by_slug( $slug ) ); } $has_cache = wpforms()->obj( 'builder_template_single' )->instance( $template['id'], $this->license )->get(); return $this->has_access( $template ) && $has_cache; } /** * Determine license level of the template. * * @since 1.6.8 * * @param array $template Template data. * * @return string */ private function get_license_level( $template ) { $licenses_pro = [ 'basic', 'plus', 'pro' ]; $licenses_template = (array) $template['license']; if ( empty( $template['license'] ) || in_array( 'lite', $licenses_template, true ) ) { return ''; } foreach ( $licenses_pro as $license ) { if ( in_array( $license, $licenses_template, true ) ) { return 'pro'; } } return 'elite'; } /** * Get categories data. * * @since 1.6.8 * * @return array */ public function get_categories() { return $this->categories; } /** * Get subcategories data. * * @since 1.8.4 * * @return array */ public function get_subcategories() { return $this->subcategories; } /** * Get templates data. * * @since 1.6.8 * * @return array */ public function get_templates(): array { static $templates = []; if ( ! empty( $templates ) ) { return $templates; } // phpcs:disable WPForms.PHP.ValidateHooks.InvalidHookName /** * Form templates available in the WPForms core plugin. * * @since 1.4.0 * * @param array $templates Core templates data. */ $core_templates = (array) apply_filters( 'wpforms_form_templates_core', [] ); /** * Form templates available with the WPForms addons. * Allows developers to provide additional templates with an addons. * * @since 1.4.0 * * @param array $templates Addons templates data. */ $additional_templates = (array) apply_filters( 'wpforms_form_templates', [] ); // phpcs:enable WPForms.PHP.ValidateHooks.InvalidHookName $templates = array_merge( $core_templates, $additional_templates ); // Generate and store the templates' hash. $this->hash = wp_hash( wp_json_encode( $templates ) ); return $templates; } /** * Get templates' hash. * * @since 1.8.6 * * @return string */ public function get_hash(): string { if ( ! $this->hash ) { $this->get_templates(); } return $this->hash; } /** * Get single template data. * * @since 1.6.8 * * @param string $slug Template slug OR Id. * * @return array */ public function get_template( $slug ) { $template = $this->get_template_by_slug( $slug ); if ( ! $template ) { $template = $this->get_template_by_id( $slug ); } if ( empty( $template ) ) { return []; } if ( empty( $template['id'] ) ) { return $template; } // Attempt to get template with form data (if available). $full_template = wpforms() ->obj( 'builder_template_single' ) ->instance( $template['id'], $this->license ) ->get(); if ( ! empty( $full_template['data'] ) ) { return $full_template; } return $template; } /** * Get template data by slug. * * @since 1.7.5.3 * * @param string $slug Template slug. * * @return array */ private function get_template_by_slug( $slug ) { foreach ( $this->get_templates() as $template ) { if ( ! empty( $template['slug'] ) && $template['slug'] === $slug ) { return $template; } } return []; } /** * Get template data by Id. * * @since 1.6.8 * * @param string $id Template id. * * @return array */ private function get_template_by_id( $id ) { foreach ( $this->api_templates as $template ) { if ( ! empty( $template['id'] ) && $template['id'] === $id ) { return $template; } } return []; } /** * Add templates to the list on the Setup panel. * * @since 1.6.8 * * @param array $templates Templates list. * * @return array */ public function add_templates_to_setup_panel( $templates ) { return array_merge( $templates, $this->api_templates ); } /** * Add template data when form is created. * * @since 1.6.8 * * @param array $args Create form arguments. * @param array $data Template data. * * @return array */ public function apply_to_new_form( $args, $data ) { if ( empty( $data ) || empty( $data['template'] ) ) { return $args; } $template = $this->get_template( $data['template'] ); if ( empty( $template['data'] ) || ! $this->has_access( $template ) ) { return $args; } $template['data']['meta']['template'] = $template['id'] ?? $template['slug']; $template['data']['meta']['category'] = $data['category'] ?? 'all'; $template['data']['meta']['subcategory'] = $data['subcategory'] ?? 'all'; // Enable Notifications by default. $template['data']['settings']['notification_enable'] = isset( $template['data']['settings']['notification_enable'] ) ? $template['data']['settings']['notification_enable'] : 1; // Unset settings that should be defined locally. unset( $template['data']['settings']['form_title'], $template['data']['settings']['conversational_forms_title'], $template['data']['settings']['form_pages_title'] ); // Unset certain values for each Notification, since: // - Email Subject Line field (subject) depends on the form name that is generated from the template name and form_id. // - From Name field (sender_name) depends on the blog name and can be replaced by WP Mail SMTP plugin. // - From Email field (sender_address) depends on the internal logic and can be replaced by WP Mail SMTP plugin. if ( ! empty( $template['data']['settings']['notifications'] ) ) { foreach ( (array) $template['data']['settings']['notifications'] as $key => $notification ) { unset( $template['data']['settings']['notifications'][ $key ]['subject'], $template['data']['settings']['notifications'][ $key ]['sender_name'], $template['data']['settings']['notifications'][ $key ]['sender_address'] ); } } /** * Allow modifying form data when a template is applied to the new form. * * @since 1.9.0 * * @param array $form_data New form data. * @param array $template Template data. */ $template['data'] = (array) apply_filters( 'wpforms_admin_builder_templates_apply_to_new_form_modify_data', $template['data'], $template ); // Encode template data to post content. $args['post_content'] = wpforms_encode( $template['data'] ); return $args; } /** * Add template data when form is updated. * * @since 1.6.8 * * @param array $form Form post data. * @param array $data Form data. * @param array $args Update form arguments. * * @return array */ public function apply_to_existing_form( $form, $data, $args ) { if ( empty( $args ) || empty( $args['template'] ) ) { return $form; } $template = $this->get_template( $args['template'] ); if ( empty( $template['data'] ) || ! $this->has_access( $template ) ) { return $form; } $form_data = wpforms_decode( wp_unslash( $form['post_content'] ) ); // Something is wrong with the form data. if ( empty( $form_data ) ) { return $form; } // Compile the new form data preserving needed data from the existing form. $new = $template['data']; $new['id'] = $form['ID'] ?? 0; $new['field_id'] = $form_data['field_id'] ?? 0; $new['settings'] = $form_data['settings'] ?? []; $new['payments'] = $form_data['payments'] ?? []; $new['meta'] = $form_data['meta'] ?? []; $template_id = $template['id'] ?? ''; // Preserve template ID `wpforms-user-template-{$form_id}` when overwriting it with another template. if ( wpforms_is_form_template( $form['ID'] ) ) { $template_id = $form_data['meta']['template'] ?? ''; } $new['meta']['template'] = $template_id; $new['meta']['category'] = ! empty( $args['category'] ) ? sanitize_text_field( $args['category'] ) : 'all'; $new['meta']['subcategory'] = ! empty( $args['subcategory'] ) ? sanitize_text_field( $args['subcategory'] ) : 'all'; /** * Allow modifying form data when a new template is applied. * * @since 1.7.9 * * @param array $new Updated form data. * @param array $form_data Current form data. * @param array $template Template data. */ $new = (array) apply_filters( 'wpforms_admin_builder_templates_apply_to_existing_form_modify_data', $new, $form_data, $template ); // Update the form with new data. $form['post_content'] = wpforms_encode( $new ); return $form; } /** * Disconnect payment gateways the form's new template can no longer use. * * Runs only when a template is applied to a form (online or offline) and after * all other save handlers — including addon payment preservation — so a stale * "connected" checkmark and enabled state are not carried over to a template * without the payment field. A gateway is kept only when the form still has a * field that gateway can use; gateways without a dedicated field (e.g. PayPal * Standard) fall back to the generic payment field types. * * @since 2.0.0 * * @param array|mixed $form Form post data. * @param array $data Form data. * @param array $args Save arguments. Contains `template` on template apply. * * @return array */ public function reconcile_payments_on_template_apply( $form, $data, $args ): array { $form = (array) $form; if ( empty( $args['template'] ) ) { return $form; } $form_data = wpforms_decode( wp_unslash( $form['post_content'] ?? '' ) ); if ( empty( $form_data['payments'] ) ) { return $form; } $gateway_fields = [ 'stripe' => [ StripeHelpers::get_field_slug() ], 'square' => [ 'square' ], 'paypal_commerce' => [ 'paypal-commerce' ], 'authorize_net' => [ 'authorize_net' ], ]; foreach ( array_keys( $form_data['payments'] ) as $gateway ) { $required_fields = $gateway_fields[ $gateway ] ?? wpforms_payment_fields(); if ( ! wpforms_has_field_type( $required_fields, $form_data ) ) { unset( $form_data['payments'][ $gateway ] ); } } $form['post_content'] = wpforms_encode( $form_data ); return $form; } /** * Create a form on request. * * @since 1.6.8 */ public function create_form_on_request() { $template = $this->get_template_on_request(); // Just return if template not found OR user doesn't have access. if ( empty( $template['has_access'] ) ) { return; } // Check if the template requires some addons. if ( $this->check_template_required_addons( $template ) ) { return; } // Set form title equal to the template's name. $form_title = ! empty( $template['name'] ) ? $template['name'] : esc_html__( 'New form', 'wpforms-lite' ); $title_query = new WP_Query( [ 'post_type' => 'wpforms', 'title' => $form_title, 'posts_per_page' => 1, 'fields' => 'ids', 'update_post_meta_cache' => false, 'update_post_term_cache' => false, 'no_found_rows' => true, ] ); $title_exists = $title_query->post_count > 0; $form_id = wpforms()->obj( 'form' )->add( $form_title, [], [ 'template' => $template['id'], ] ); // Return if something wrong. if ( ! $form_id ) { return; } // Update form title if duplicated. if ( $title_exists ) { wpforms()->obj( 'form' )->update( $form_id, [ 'settings' => [ 'form_title' => $form_title . ' (ID #' . $form_id . ')', ], ] ); } $this->create_form_on_request_redirect( $form_id ); } /** * Get template data before creating a new form on request. * * @since 1.6.8 * * @return array|bool Template OR false. */ private function get_template_on_request() { if ( ! wpforms_is_admin_page( 'builder' ) || ! wpforms_is_admin_page( 'templates' ) ) { return false; } if ( ! wpforms_current_user_can( 'create_forms' ) ) { return false; } $form_id = isset( $_GET['form_id'] ) ? (int) $_GET['form_id'] : false; // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( ! empty( $form_id ) ) { return false; } $view = isset( $_GET['view'] ) ? sanitize_key( $_GET['view'] ) : 'setup'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( $view !== 'setup' ) { return false; } $template_id = isset( $_GET['template_id'] ) ? sanitize_key( $_GET['template_id'] ) : false; // phpcs:ignore WordPress.Security.NonceVerification.Recommended // Attempt to get the template. $template = $this->get_template( $template_id ); // Just return if template is not found. if ( empty( $template ) ) { return false; } return $template; } /** * Redirect after creating the form. * * @since 1.6.8 * * @param integer $form_id Form ID. */ private function create_form_on_request_redirect( $form_id ) { // Redirect to the builder if possible. if ( wpforms_current_user_can( 'edit_form_single', $form_id ) ) { wp_safe_redirect( add_query_arg( [ 'view' => 'fields', 'form_id' => $form_id, 'newform' => '1', ], admin_url( 'admin.php?page=wpforms-builder' ) ) ); exit; } // Redirect to the forms overview admin page if possible. if ( wpforms_current_user_can( 'view_forms' ) ) { wp_safe_redirect( admin_url( 'admin.php?page=wpforms-overview' ) ); exit; } // Finally, redirect to the admin dashboard. wp_safe_redirect( admin_url() ); exit; } /** * Check if the template requires some addons and then redirect to the builder for further interaction if needed. * * @since 1.6.8 * * @param array $template Template data. * * @return bool True if template requires some addons that are not yet installed and/or activated. */ private function check_template_required_addons( $template ) { // Return false if none addons required. if ( empty( $template['addons'] ) ) { return false; } $required_addons = wpforms()->obj( 'addons' )->get_by_slugs( $template['addons'] ); foreach ( $required_addons as $i => $addon ) { if ( empty( $addon['action'] ) || ! in_array( $addon['action'], [ 'install', 'activate' ], true ) ) { unset( $required_addons[ $i ] ); } } // Return false if not need to install or activate any addons. // We can proceed with creating the form directly in this process. if ( empty( $required_addons ) ) { return false; } // Otherwise return true. return true; } /** * Render the upgrade banner template. * * This method generates the HTML template for the upgrade banner, which includes * a title, description, and a button that links to the upgrade page. * * @param string $title The title to be displayed in the banner. * @param string $description The description to be displayed in the banner. * * @since 1.9.4 */ private function render_upgrade_banner_template( string $title, string $description ): void { $medium = wpforms_is_admin_page( 'templates' ) ? 'Form Templates Subpage' : 'Builder Templates'; ?> <script type="text/html" id="tmpl-wpforms-templates-upgrade-banner"> <div class="wpforms-template-upgrade-banner"> <div class="wpforms-template-content"> <h3> <?php echo esc_html( $title ); ?> </h3> <p> <?php echo esc_html( $description ); ?> </p> </div> <div class="wpforms-template-upgrade-button"> <a href="<?php echo esc_url( wpforms_admin_upgrade_link( $medium, 'Upgrade to Pro' ) ); ?>" class="wpforms-btn wpforms-btn-orange wpforms-btn-md" target="_blank" rel="noopener noreferrer"> <?php esc_html_e( 'Upgrade to Pro', 'wpforms-lite' ); ?> </a> </div> </div> </script> <?php } /** * Render upgrade banner for basic and plus versions. * * @since 1.7.7 */ public function upgrade_banner_template(): void { if ( in_array( wpforms_get_license_type(), [ 'pro', 'elite', 'agency', 'ultimate' ], true ) || ! wpforms()->is_pro() ) { return; } $title = sprintf( /* translators: %d - templates count. */ esc_html__( 'Get Access to Our Complete Library of %d+ Form Templates', 'wpforms-lite' ), esc_html( floor( count( $this->get_templates() ) / 1000 ) * 1000 ) ); $description = esc_html__( 'Save time and reduce effort with our pre-built form templates covering popular use-cases in business operations, customer service, feedback, marketing, registrations, event planning, non-profit, healthcare, and education.', 'wpforms-lite' ); $this->render_upgrade_banner_template( $title, $description ); } /** * Render upgrade banner for lite version. * * @since 1.9.4 */ public function upgrade_lite_banner_template(): void { if ( wpforms()->is_pro() ) { return; } $title = sprintf( /* translators: %d - templates count. */ esc_html__( 'Get Access to Our Library of %d+ Pre-Made Form Templates', 'wpforms-lite' ), esc_html( floor( count( $this->get_templates() ) / 1000 ) * 1000 ) ); $description = esc_html__( 'Never start from scratch again! While WPForms Lite allows you to create any type of form, you can save even more time with WPForms Pro. Upgrade to access hundreds more form templates and advanced form fields.', 'wpforms-lite' ); $this->render_upgrade_banner_template( $title, $description ); } /** * Add additional addons templates. * * @since 1.8.9 * * @param array $templates Templates list. * * @return array */ public function add_addons_templates( array $templates ): array { // Add User Registration templates only if the addon is not active. if ( ! wpforms()->obj( 'addons' )->is_active( 'user-registration' ) ) { $templates = $this->add_user_registration_templates( $templates ); } // Add Post Submissions templates only if the addon is not active. if ( ! wpforms()->obj( 'addons' )->is_active( 'post-submissions' ) ) { $templates = $this->add_post_submissions_templates( $templates ); } // Add Survey and Poll templates only if the addon is not active. if ( ! wpforms()->obj( 'addons' )->is_active( 'surveys-polls' ) ) { $templates = $this->add_surveys_polls_templates( $templates ); } return $templates; } /** * Add User Registration templates. * * @since 1.8.9 * * @param array $templates Templates list. * * @return array */ private function add_user_registration_templates( array $templates ): array { $user_registration_templates = [ [ 'name' => esc_html__( 'User Registration Form', 'wpforms-lite' ), 'slug' => 'user_registration', 'addons' => [ 'user-registration' ], 'license' => $this->get_license_level( [ 'license' => [ 'pro' ] ] ), 'has_access' => $this->has_access( [ 'license' => [ 'pro' ] ] ), 'source' => 'wpforms-addon', 'description' => esc_html__( 'Create customized WordPress user registration forms and add them anywhere on your website.', 'wpforms-lite' ), ], [ 'name' => esc_html__( 'User Login Form', 'wpforms-lite' ), 'slug' => 'user_login', 'addons' => [ 'user-registration' ], 'license' => $this->get_license_level( [ 'license' => [ 'pro' ] ] ), 'has_access' => $this->has_access( [ 'license' => [ 'pro' ] ] ), 'source' => 'wpforms-addon', 'description' => esc_html__( 'Allow your users to easily log in to your site with their username and password.', 'wpforms-lite' ), ], [ 'name' => esc_html__( 'User Password Reset Form', 'wpforms-lite' ), 'slug' => 'user_reset', 'addons' => [ 'user-registration' ], 'license' => $this->get_license_level( [ 'license' => [ 'pro' ] ] ), 'has_access' => $this->has_access( [ 'license' => [ 'pro' ] ] ), 'source' => 'wpforms-addon', 'description' => esc_html__( 'Allow your users to easily reset their password.', 'wpforms-lite' ), ], ]; return array_merge( $templates, $user_registration_templates ); } /** * Add Post Submissions templates. * * @since 1.8.9 * * @param array $templates Templates list. * * @return array */ private function add_post_submissions_templates( array $templates ): array { $post_submissions_templates = [ [ 'name' => esc_html__( 'Blog Post Submission Form', 'wpforms-lite' ), 'slug' => 'post_submission', 'addons' => [ 'post-submissions' ], 'license' => $this->get_license_level( [ 'license' => [ 'pro' ] ] ), 'has_access' => $this->has_access( [ 'license' => [ 'pro' ] ] ), 'source' => 'wpforms-addon', 'description' => esc_html__( 'User-submitted content made easy. Allow your users to submit guest blog posts in WordPress. You can add and remove fields as needed.', 'wpforms-lite' ), ], ]; return array_merge( $templates, $post_submissions_templates ); } /** * Add Surveys and Polls templates. * * @since 1.8.9 * * @param array $templates Templates list. * * @return array */ private function add_surveys_polls_templates( array $templates ): array { $surveys_polls_templates = [ [ 'name' => esc_html__( 'Survey Form', 'wpforms-lite' ), 'slug' => 'survey', 'addons' => [ 'surveys-polls' ], 'license' => $this->get_license_level( [ 'license' => [ 'pro' ] ] ), 'has_access' => $this->has_access( [ 'license' => [ 'pro' ] ] ), 'source' => 'wpforms-addon', 'description' => esc_html__( 'Collect customer feedback, then generate survey reports to determine satisfaction and spot trends.', 'wpforms-lite' ), ], [ 'name' => esc_html__( 'Poll Form', 'wpforms-lite' ), 'slug' => 'poll', 'addons' => [ 'surveys-polls' ], 'license' => $this->get_license_level( [ 'license' => [ 'pro' ] ] ), 'has_access' => $this->has_access( [ 'license' => [ 'pro' ] ] ), 'source' => 'wpforms-addon', 'description' => esc_html__( 'Ask visitors a question and display the results after they provide an answer.', 'wpforms-lite' ), ], [ 'name' => esc_html__( 'NPS Survey Simple Form', 'wpforms-lite' ), 'slug' => 'nps-survey-simple', 'addons' => [ 'surveys-polls' ], 'license' => $this->get_license_level( [ 'license' => [ 'pro' ] ] ), 'has_access' => $this->has_access( [ 'license' => [ 'pro' ] ] ), 'source' => 'wpforms-addon', 'description' => esc_html__( 'Find out if your clients or customers would recommend you to someone else with this basic Net Promoter Score survey template.', 'wpforms-lite' ), ], [ 'name' => esc_html__( 'NPS Survey Enhanced Form', 'wpforms-lite' ), 'slug' => 'nps-survey-enhanced', 'addons' => [ 'surveys-polls' ], 'license' => $this->get_license_level( [ 'license' => [ 'pro' ] ] ), 'has_access' => $this->has_access( [ 'license' => [ 'pro' ] ] ), 'source' => 'wpforms-addon', 'description' => esc_html__( 'Measure customer loyalty and find out exactly what they are thinking with this enhanced Net Promoter Score survey template.', 'wpforms-lite' ), ], ]; return array_merge( $templates, $surveys_polls_templates ); } /** * Init API templates. * * @since 1.9.1 * * @param array $templates_all All templates. * * @return void */ private function init_api_templates( array $templates_all ) { // Higher priority templates slugs. // These remote templates are the replication of the default templates, // which were previously included with the WPForms plugin. $higher_templates_slugs = [ 'simple-contact-form-template', 'request-a-quote-form-template', 'donation-form-template', 'billing-order-form-template', 'newsletter-signup-form-template', 'suggestion-form-template', ]; $templates_access_higher = []; $templates_access = []; $templates_deny_higher = []; $templates_deny = []; /** * The form template was moved to wpforms/includes/templates/class-simple-contact-form.php file. * * @since 1.7.5.3 */ unset( $templates_all['simple-contact-form-template'] ); foreach ( $templates_all as $i => $template ) { $template['has_access'] = $this->has_access( $template ); $template['favorite'] = $this->is_favorite( $i ); $template['license'] = $this->get_license_level( $template ); $template['source'] = 'wpforms-api'; $template['categories'] = ! empty( $template['categories'] ) ? array_keys( $template['categories'] ) : []; $is_higher = in_array( $i, $higher_templates_slugs, true ); if ( $template['has_access'] ) { if ( $is_higher ) { $templates_access_higher[ $i ] = $template; } else { $templates_access[ $i ] = $template; } } elseif ( $is_higher ) { $templates_deny_higher[ $i ] = $template; } else { $templates_deny[ $i ] = $template; } } // Sort higher priority templates according to the slug order. $templates_access_higher = array_replace( array_flip( $higher_templates_slugs ), $templates_access_higher ); $templates_access_higher = array_filter( $templates_access_higher, 'is_array' ); // Finally, merge templates from API. $this->api_templates = array_merge( $templates_access_higher, $templates_access, $templates_deny_higher, $templates_deny ); } } Builder/Notifications/Advanced/EmailTemplate.php 0000644 00000012517 15252506741 0015721 0 ustar 00 <?php namespace WPForms\Admin\Builder\Notifications\Advanced; use WPForms_Builder_Panel_Settings; use WPForms\Emails\Helpers; use WPForms\Admin\Education\Helpers as EducationHelpers; /** * Email Template. * This class will register the Email Template field in the "Notification" → "Advanced" settings. * The Email Template field will allow users to override the default email template for a specific notification. * * @since 1.8.5 */ class EmailTemplate { /** * Initialize class. * * @since 1.8.5 */ public function init() { $this->hooks(); } /** * Hooks. * * @since 1.8.5 */ private function hooks() { add_action( 'wpforms_builder_enqueues', [ $this, 'builder_assets' ] ); add_action( 'wpforms_builder_print_footer_scripts', [ $this, 'builder_footer_scripts' ] ); add_filter( 'wpforms_lite_admin_education_builder_notifications_advanced_settings_content', [ $this, 'settings' ], 5, 3 ); add_filter( 'wpforms_pro_admin_builder_notifications_advanced_settings_content', [ $this, 'settings' ], 5, 3 ); } /** * Enqueue assets for the builder. * * @since 1.8.5 */ public function builder_assets() { $min = wpforms_get_min_suffix(); wp_enqueue_script( 'wpforms-builder-email-template', WPFORMS_PLUGIN_URL . "assets/js/admin/builder/email-template{$min}.js", [ 'jquery', 'jquery-confirm', 'wpforms-builder' ], WPFORMS_VERSION, true ); wp_localize_script( 'wpforms-builder-email-template', 'wpforms_builder_email_template', [ 'is_pro' => wpforms()->is_pro(), 'templates' => Helpers::get_email_template_choices( false ), ] ); } /** * Output Email Template modal. * * @since 1.8.5 */ public function builder_footer_scripts() { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wpforms_render( 'builder/notifications/email-template-modal', [ 'pro_badge' => ! wpforms()->is_pro() ? EducationHelpers::get_badge( 'Pro' ) : '', ], true ); } /** * Add Email Template settings. * * @since 1.8.5 * * @param string $content Notification → Advanced content. * @param WPForms_Builder_Panel_Settings $settings Builder panel settings. * @param int $id Notification id. * * @return string */ public function settings( $content, $settings, $id ) { // Retrieve email template choices and disabled choices. // A few of the email templates are only available in the Pro version and will be disabled for non-Pro users. // The disabled choices will be added to the select field with a "(Pro)" label appended to the name. list( $options, $disabled_options ) = $this->get_email_template_options(); // Add Email Template field. $content .= wpforms_panel_field( 'select', 'notifications', 'template', $settings->form_data, esc_html__( 'Email Template', 'wpforms-lite' ), [ 'default' => '', 'options' => $options, 'disabled_options' => $disabled_options, 'class' => 'wpforms-panel-field-email-template-wrap', 'input_class' => 'wpforms-panel-field-email-template', 'parent' => 'settings', 'subsection' => $id, 'after' => $this->get_template_modal_link_content(), 'tooltip' => esc_html__( 'Override the default email template for this specific notification.', 'wpforms-lite' ), ], false ); return $content; } /** * Get Email template choices. * * This function will return an array of email template choices and an array of disabled choices. * The disabled choices are templates that are only available in the Pro version. * * @since 1.8.5 * * @return array */ private function get_email_template_options() { // Retrieve the available email template choices. $choices = Helpers::get_email_template_choices( false ); // If there are no templates or the choices are not an array, return empty arrays. if ( empty( $choices ) || ! is_array( $choices ) ) { return [ [], [] ]; } // Check if the Pro version is active. $is_pro = wpforms()->is_pro(); // Initialize arrays for options and disabled options. $options = []; $disabled_options = []; // Iterate through the templates and build the $options array. foreach ( $choices as $key => $choice ) { $value = esc_attr( $key ); $name = esc_html( $choice['name'] ); $is_disabled = ! $is_pro && isset( $choice['is_pro'] ) && $choice['is_pro']; // If the option is disabled for non-Pro users, add it to the disabled options array. if ( $is_disabled ) { $disabled_options[] = $value; } // Build the $options array with appropriate labels. // Pro badge labels are not meant to be translated. $options[ $key ] = $is_disabled ? sprintf( '%s (Pro)', $name ) : $name; } // Add an empty option to the beginning of the $options array. // This is a placeholder option that will be replaced with the default template name. $options = array_merge( [ '' => esc_html__( 'Default Template', 'wpforms-lite' ) ], $options ); // Return the options and disabled options arrays. return [ $options, $disabled_options ]; } /** * Get Email template modal link content. * * @since 1.8.5 * * @return string */ private function get_template_modal_link_content() { return wpforms_render( 'builder/notifications/email-template-link' ); } } Builder/Addons.php 0000644 00000024244 15252506741 0010070 0 ustar 00 <?php namespace WPForms\Admin\Builder; use WPForms\Requirements\Requirements; /** * Addons class. * * @since 1.9.2 */ class Addons { /** * List of addon options. * * @since 1.9.2 */ private const FIELD_OPTIONS = [ 'calculations' => [ 'calculation_code', 'calculation_code_js', 'calculation_code_php', 'calculation_is_enabled', ], 'form-locker' => [ 'unique_answer', ], 'geolocation' => [ 'display_map', 'enable_address_autocomplete', 'map_position', ], 'surveys-polls' => [ 'survey', ], 'quiz' => [ 'quiz_enabled', 'choices' => [ 'quiz_personality', 'quiz_weight', ], ], ]; /** * Initialize. * * @since 1.9.2 * * @noinspection ReturnTypeCanBeDeclaredInspection */ public function init() { $this->hooks(); } /** * Add hooks. * * @since 1.9.2 */ private function hooks(): void { add_filter( 'wpforms_save_form_args', [ $this, 'save_disabled_addons_options' ], 10, 3 ); } /** * Field's options added by an addon can be deleted when the addon is deactivated or have incompatible status. * The options are fully controlled by the addon when addon is active and compatible. * * @since 1.9.2 * * @param array|mixed $post_data Post data. * * @return array */ public function save_disabled_addons_options( $post_data ): array { $post_data = (array) $post_data; $form_obj = wpforms()->obj( 'form' ); $form_data = json_decode( stripslashes( $post_data['post_content'] ?? '' ), true ); $form_id = $form_data['id'] ?? ''; if ( ! $form_obj || ! $form_id ) { return $post_data; } $previous_form_data = $form_obj->get( $form_id, [ 'content_only' => true ] ); $not_validated_addons = Requirements::get_instance()->get_not_validated_addons(); if ( empty( $previous_form_data ) || empty( $not_validated_addons ) ) { return $post_data; } foreach ( $not_validated_addons as $path ) { $slug = str_replace( 'wpforms-', '', basename( $path, '.php' ) ); $this->preserve_addon( $slug, $form_data, $previous_form_data ); } $this->preserve_providers( $form_data, $previous_form_data ); $this->preserve_payments( $form_data, $previous_form_data ); $post_data['post_content'] = wpforms_encode( $form_data ); return $post_data; } /** * Preserve addon fields, settings, panels, notifications, etc. * * @since 1.9.3 * * @param string $slug Addon slug. * @param array $form_data Form data. * @param array $previous_form_data Previous form data. * * @return void */ private function preserve_addon( string $slug, array &$form_data, array $previous_form_data ): void { if ( ! empty( $form_data['fields'] ) && ! empty( $previous_form_data['fields'] ) ) { $this->preserve_addon_fields_settings( $slug, $form_data['fields'], $previous_form_data['fields'] ); } $this->preserve_addon_panel( $slug, $form_data, $previous_form_data ); if ( ! empty( $form_data['settings'] ) && ! empty( $previous_form_data['settings'] ) ) { $this->preserve_addon_settings( $slug, $form_data['settings'], $previous_form_data['settings'] ); } if ( ! empty( $form_data['settings']['notifications'] ) && ! empty( $previous_form_data['settings']['notifications'] ) ) { $this->preserve_addon_notifications( $slug, $form_data['settings']['notifications'], $previous_form_data['settings']['notifications'] ); } } /** * Preserve addon fields. * * @since 1.9.5 * * @param string $slug Addon slug. * @param array $new_fields Form fields settings. * @param array $previous_fields Previous form fields settings. * * @return void */ private function preserve_addon_fields_settings( string $slug, array &$new_fields, array $previous_fields ): void { foreach ( $previous_fields as $field_id => $previous_field ) { $new_field = $new_fields[ $field_id ] ?? []; if ( empty( $new_field ) ) { continue; } $this->preserve_addon_field_settings( $slug, $new_field, $previous_field ); $new_fields[ $field_id ] = $new_field; } } /** * Preserve addon field. * * @since 1.9.5 * * @param string $slug Addon slug. * @param array $new_field Previous form fields settings. * @param array $previous_field Form fields settings. * * @return void */ private function preserve_addon_field_settings( string $slug, array &$new_field, array $previous_field ): void { $prefix = $this->prepare_prefix( $slug ); $changed_settings = array_diff_key( $previous_field, $new_field ); $preserve_fields = self::FIELD_OPTIONS[ $slug ] ?? []; foreach ( $changed_settings as $setting_name => $setting_value ) { if ( strpos( $setting_name, $prefix ) === 0 || in_array( $setting_name, $preserve_fields, true ) ) { $new_field[ $setting_name ] = $setting_value; } } if ( ! empty( $preserve_fields['choices'] ) && is_array( $preserve_fields['choices'] ) && ! empty( $new_field['choices'] ) && is_array( $new_field['choices'] ) ) { $this->preserve_addon_field_choices_settings( $preserve_fields['choices'], $new_field, $previous_field ); } } /** * Preserve addon field choices settings. * * @since 1.9.9 * * @param array $choice_settings Choice settings. * @param array $new_field Previous form fields settings. * @param array $previous_field Form fields settings. * * @return void */ private function preserve_addon_field_choices_settings( array $choice_settings, array &$new_field, array $previous_field ): void { if ( ! isset( $previous_field['choices'] ) || ! is_array( $previous_field['choices'] ) ) { return; } $previous_choices = $previous_field['choices']; foreach ( $new_field['choices'] as $choice_id => $choice ) { foreach ( $choice_settings as $setting_name ) { if ( isset( $previous_choices[ $choice_id ][ $setting_name ] ) ) { $new_field['choices'][ $choice_id ][ $setting_name ] = $previous_choices[ $choice_id ][ $setting_name ]; } } } } /** * Preserve addon panel. * * @since 1.9.3 * * @param string $slug Addon slug. * @param array $form_data Form data. * @param array $previous_form_data Previous form data. */ private function preserve_addon_panel( string $slug, array &$form_data, array $previous_form_data ): void { $panel = $this->prepare_prefix( $slug ); // The addon settings stored its own panel, e.g., $form_data[lead_forms], $form_data[webhooks], etc. if ( ! empty( $previous_form_data[ $panel ] ) ) { $form_data[ $panel ] = $previous_form_data[ $panel ]; } } /** * Preserve addon settings stored inside the settings panel with a specific prefix. * e.g. $form_data[settings][{$prefix}_enabled], $form_data[settings][{$prefix}_email], etc. * * @since 1.9.4 * * @param string $slug Addon option prefix. * @param array $new_settings Form settings. * @param array $previous_settings Previous form settings. */ private function preserve_addon_settings( string $slug, array &$new_settings, array $previous_settings ): void { $prefix = $this->prepare_prefix( $slug ); static $legacy_options = [ 'offline_forms' => [ 'offline_form' ], 'user_registration' => [ 'user_login_hide', 'user_reset_hide' ], 'surveys_polls' => [ 'survey_enable', 'poll_enable' ], ]; // BC: User Registration addon has `registration_` prefix instead of `user_registration`. if ( $prefix === 'user_registration' ) { $prefix = 'registration'; } foreach ( $previous_settings as $setting_name => $value ) { if ( strpos( $setting_name, $prefix ) === 0 ) { $new_settings[ $setting_name ] = $value; continue; } // BC: The options don't have a prefix and hard-coded in the `$legacy_options` variable. if ( isset( $legacy_options[ $prefix ] ) && in_array( $setting_name, $legacy_options[ $prefix ], true ) ) { $new_settings[ $setting_name ] = $value; } } } /** * Preserve addon notifications. * * @since 1.9.4 * * @param string $slug Addon slug. * @param array $new_notifications List of form notifications. * @param array $previous_notifications Previously saved list of form notifications. * * @return void */ private function preserve_addon_notifications( string $slug, array &$new_notifications, array $previous_notifications ): void { $prefix = $this->prepare_prefix( $slug ); foreach ( $previous_notifications as $notification_id => $notification_settings ) { if ( empty( $new_notifications[ $notification_id ] ) ) { continue; } $changed_notification_settings = array_diff_key( $notification_settings, $new_notifications[ $notification_id ] ); foreach ( $changed_notification_settings as $setting_name => $value ) { if ( strpos( $setting_name, $prefix ) === 0 ) { $new_notifications[ $notification_id ][ $setting_name ] = $value; } } } } /** * Preserve Providers that are not active. * * @since 1.9.4 * * @param array $form_data Form data. * @param array $previous_form_data Previous form data. */ private function preserve_providers( array &$form_data, array $previous_form_data ): void { if ( empty( $previous_form_data['providers'] ) ) { return; } $active_providers = wpforms_get_providers_available(); foreach ( $previous_form_data['providers'] as $slug => $provider ) { if ( ! empty( $active_providers[ $slug ] ) ) { continue; } $form_data['providers'][ $slug ] = $provider; } } /** * Preserve Payments providers that are not active. * * @since 1.9.4 * * @param array $form_data Form data. * @param array $previous_form_data Previous form data. */ private function preserve_payments( array &$form_data, array $previous_form_data ): void { if ( empty( $previous_form_data['payments'] ) ) { return; } foreach ( $previous_form_data['payments'] as $slug => $value ) { if ( ! empty( $form_data['payments'][ $slug ] ) ) { continue; } $form_data['payments'][ $slug ] = $value; } } /** * Convert slug to a addon prefix. * * @since 1.9.4 * * @param string $slug Addon slug. * * @return string */ private function prepare_prefix( string $slug ): string { return str_replace( '-', '_', $slug ); } } Builder/PreviewDropdownEducationItems.php 0000644 00000022163 15252506741 0014652 0 ustar 00 <?php namespace WPForms\Admin\Builder; use WP_Post; /** * Provider for the preview dropdown education items. * * Centralizes the canonical item list shown in the Form Builder preview * dropdown and the Form Embed wizard card grid, together with the helpers * used to derive per-item state (addon status, demo URL). * * Results are memoized per form ID so the `wpforms_admin_builder_preview_dropdown_education_items_get_items` * filter runs once per request regardless of how many consumers ask for the list. * * @since 1.10.1 */ class PreviewDropdownEducationItems { /** * UTM medium shared by every preview dropdown education item. * * @since 1.10.1 */ private const UTM_MEDIUM = 'Builder Preview Modal'; /** * Memoized items keyed by form ID (0 when no form is available). * * @since 1.10.1 * * @var array<int, array> */ private static $cache = []; /** * Form the items are built for. * * @since 1.10.1 * * @var WP_Post|null */ private $form; /** * Constructor. * * @since 1.10.1 * * @param WP_Post|null $form Current form object, or null when unavailable. */ public function __construct( ?WP_Post $form = null ) { $this->form = $form; } /** * Get the preview dropdown education items. * * Each item is an associative array with the following keys: * - slug: Unique item identifier. * - icon: Font Awesome class (e.g. `fa-commenting-o`). * - label: Translated item label. * - modal_label: Translated addon name used in the Addons Required modal. * - description: Short item description. * - addon_slug: WPForms addons slug (e.g. `wpforms-conversational-forms`). * - link: Feature page URL on wpforms.com (used for demo links). * - utm_content: UTM content string passed to the upgrade URL. * - demo_utm_content: UTM content string passed to the demo URL. * - section: Settings panel section slug to navigate to. * - toggle_id: ID of the settings toggle that activates the feature. * - preview_url: Frontend preview URL once the addon is enabled. * - disabled_notice: Notice shown in the settings section when the user * arrives from the preview menu while the feature * toggle is disabled. * * @since 1.10.1 * * @return array */ public function get_items(): array { $form_id = $this->form instanceof WP_Post ? (int) $this->form->ID : 0; if ( isset( self::$cache[ $form_id ] ) ) { return self::$cache[ $form_id ]; } $items = $this->build_default_items(); /** * Filter the list of Pro education items shown in the Form Builder preview dropdown. * * @since 1.10.1 * * @param array $items Education items. * @param WP_Post|null $form Current form object. */ $items = (array) apply_filters( 'wpforms_admin_builder_preview_dropdown_education_items_get_items', $items, $this->form ); self::$cache[ $form_id ] = $items; return $items; } /** * Build the default (unfiltered) item list. * * @since 1.10.1 * * @return array */ private function build_default_items(): array { $lead_form_preview = $this->get_lead_form_preview_url(); $form_pages_preview = $this->get_form_pages_preview_url(); $conversational_preview = $this->get_conversational_forms_preview_url(); return [ [ 'slug' => 'conversational-forms', 'icon' => 'fa-regular fa-message', 'label' => esc_html__( 'Conversational Form', 'wpforms-lite' ), 'modal_label' => esc_html__( 'Conversational Forms', 'wpforms-lite' ), 'description' => esc_html__( 'Make forms feel more natural.', 'wpforms-lite' ), 'addon_slug' => 'wpforms-conversational-forms', 'link' => 'https://wpforms.com/conversational-forms-demo/', 'utm_medium' => self::UTM_MEDIUM, 'utm_content' => 'Conversational Forms - Upgrade to Pro Button', 'demo_utm_content' => 'Conversational Forms - View Demo Button', 'section' => 'conversational_forms', 'toggle_id' => 'wpforms-panel-field-settings-conversational_forms_enable', 'preview_url' => $conversational_preview, 'disabled_notice' => esc_html__( 'In order to preview your form as a Conversational Form, you must first enable the setting.', 'wpforms-lite' ), ], [ 'slug' => 'form-pages', 'icon' => 'fa-file-text-o', 'label' => esc_html__( 'Form Landing Page', 'wpforms-lite' ), 'modal_label' => esc_html__( 'Form Landing Pages', 'wpforms-lite' ), 'description' => esc_html__( 'Build distraction-free forms.', 'wpforms-lite' ), 'addon_slug' => 'wpforms-form-pages', 'link' => 'https://wpforms.com/form-pages-demo/', 'utm_medium' => self::UTM_MEDIUM, 'utm_content' => 'Form Pages - Upgrade to Pro Button', 'demo_utm_content' => 'Form Pages - View Demo Button', 'section' => 'form_pages', 'toggle_id' => 'wpforms-panel-field-settings-form_pages_enable', 'preview_url' => $form_pages_preview, 'disabled_notice' => esc_html__( 'In order to preview your form as a Form Page, you must first enable the setting.', 'wpforms-lite' ), ], [ 'slug' => 'lead-forms', 'icon' => 'fa-bookmark-o', 'label' => esc_html__( 'Lead Form', 'wpforms-lite' ), 'modal_label' => esc_html__( 'Lead Forms', 'wpforms-lite' ), 'description' => esc_html__( 'Create engaging experiences.', 'wpforms-lite' ), 'addon_slug' => 'wpforms-lead-forms', 'link' => 'https://wpforms.com/templates/lead-generation-form-template/', 'utm_medium' => self::UTM_MEDIUM, 'utm_content' => 'Lead Forms - Upgrade to Pro Button', 'demo_utm_content' => 'Lead Forms - View Demo Button', 'section' => 'lead_forms', 'toggle_id' => 'wpforms-panel-field-lead_forms-enable', 'preview_url' => $lead_form_preview, 'disabled_notice' => esc_html__( 'In order to preview your form as a Lead Form, you must first enable the setting.', 'wpforms-lite' ), ], ]; } /** * Lead Form preview URL for the current form. * * @since 1.10.1 * * @return string */ private function get_lead_form_preview_url(): string { if ( ! $this->form instanceof WP_Post ) { return ''; } return (string) wpforms_get_form_preview_url( $this->form->ID, true ); } /** * Form Pages preview URL for the current form. * * Matches the URL used by the "Preview Form Page" button in the Form Pages * settings section (`#wpforms-form-pages-preview-form-page`). Returns an * empty string when pretty permalinks are disabled, in which case the * dropdown falls back to navigating to the Form Pages settings section. * * @since 1.10.1 * * @return string */ private function get_form_pages_preview_url(): string { return $this->get_post_name_preview_url(); } /** * Conversational Form preview URL for the current form. * * Matches the URL used by the "Preview Conversational Form" button in the * Conversational Forms settings section * (`#wpforms-conversational-forms-preview-conversational-form`). Returns an * empty string when pretty permalinks are disabled, in which case the * dropdown falls back to navigating to the Conversational Forms settings * section. * * @since 1.10.1 * * @return string */ private function get_conversational_forms_preview_url(): string { return $this->get_post_name_preview_url(); } /** * Build a `home_url( $form->post_name )` preview URL. * * Shared by addons that serve the form at a dedicated frontend slug * (Form Pages, Conversational Forms) and require pretty permalinks. * * @since 1.10.1 * * @return string */ private function get_post_name_preview_url(): string { if ( ! $this->form instanceof WP_Post ) { return ''; } if ( empty( get_option( 'permalink_structure' ) ) ) { return ''; } return (string) home_url( $this->form->post_name ); } /** * Check whether a WPForms addon is installed and active. * * @since 1.10.1 * * @param string $addon_slug Addon slug (e.g. `wpforms-conversational-forms`). * * @return bool */ public static function is_addon_active( string $addon_slug ): bool { if ( $addon_slug === '' ) { return false; } $addons = wpforms()->obj( 'addons' ); if ( ! $addons ) { return false; } return $addons->is_active( $addon_slug ); } /** * Build the "View Demo" link for an item. * * @since 1.10.1 * * @param array $item Education item. * * @return string */ public static function get_demo_url( array $item ): string { $link = $item['link'] ?? ''; if ( $link === '' ) { return ''; } return wpforms_utm_link( $link, $item['utm_medium'] ?? '', $item['demo_utm_content'] ?? ( $item['utm_content'] ?? '' ) ); } /** * Reset the memoized cache. * * Primarily useful in tests. Callers should not rely on cache invalidation * during a single request. * * @since 1.10.1 */ public static function flush_cache(): void { self::$cache = []; } } AdminBarMenu.php 0000644 00000007470 15252506741 0007576 0 ustar 00 <?php namespace WPMailSMTP\Admin; use WPMailSMTP\EmailSendingDebug; use WPMailSMTP\Options; /** * WP Mail SMTP admin bar menu. * * @since 2.3.0 */ class AdminBarMenu { /** * Initialize class. * * @since 2.3.0 */ public function init() { $this->hooks(); } /** * Register hooks. * * @since 2.3.0 */ public function hooks() { add_action( 'wp_enqueue_scripts', [ $this, 'enqueues' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueues' ] ); add_action( 'admin_bar_menu', [ $this, 'register' ], 999 ); } /** * Check if current user has access to see admin bar menu. * * @since 2.3.0 * * @return bool */ public function has_access() { $access = false; if ( is_user_logged_in() && current_user_can( wp_mail_smtp()->get_capability_manage_options() ) ) { $access = true; } return apply_filters( 'wp_mail_smtp_admin_adminbarmenu_has_access', $access ); } /** * Check if new notifications are available. * * @since 2.3.0 * * @return bool */ public function has_notifications() { return wp_mail_smtp()->get_notifications()->get_count(); } /** * Enqueue styles. * * @since 2.3.0 */ public function enqueues() { if ( ! is_admin_bar_showing() ) { return; } if ( ! $this->has_access() ) { return; } wp_enqueue_style( 'wp-mail-smtp-admin-bar', wp_mail_smtp()->assets_url . '/css/admin-bar.min.css', [], WPMS_PLUGIN_VER ); } /** * Register and render admin menu bar. * * @since 2.3.0 * * @param \WP_Admin_Bar $wp_admin_bar WordPress Admin Bar object. */ public function register( \WP_Admin_Bar $wp_admin_bar ) { if ( ! $this->has_access() || ( ( ! $this->has_email_sending_errors() || (bool) Options::init()->get( 'general', 'email_delivery_errors_hidden' ) ) && empty( $this->has_notifications() ) ) ) { return; } $items = apply_filters( 'wp_mail_smtp_admin_adminbarmenu_register', [ 'main_menu', ], $wp_admin_bar ); foreach ( $items as $item ) { $this->{ $item }( $wp_admin_bar ); do_action( "wp_mail_smtp_admin_adminbarmenu_register_{$item}_after", $wp_admin_bar ); } } /** * Render primary top-level admin menu bar item. * * @since 2.3.0 * * @param \WP_Admin_Bar $wp_admin_bar WordPress Admin Bar object. */ public function main_menu( \WP_Admin_Bar $wp_admin_bar ) { if ( $this->has_email_sending_errors() && ! (bool) Options::init()->get( 'general', 'email_delivery_errors_hidden' ) ) { $indicator = ' <span class="wp-mail-smtp-admin-bar-menu-error">!</span>'; } elseif ( ! empty( $this->has_notifications() ) ) { $count = $this->has_notifications() < 10 ? $this->has_notifications() : '!'; $indicator = ' <div class="wp-mail-smtp-admin-bar-menu-notification-counter"><span>' . $count . '</span></div>'; } if ( ! isset( $indicator ) ) { return; } $wp_admin_bar->add_menu( [ 'id' => 'wp-mail-smtp-menu', 'title' => 'WP Mail SMTP' . $indicator, 'href' => apply_filters( 'wp_mail_smtp_admin_adminbarmenu_main_menu_href', wp_mail_smtp()->get_admin()->get_admin_page_url() ), ] ); } /** * Whether any active connection has a live (failed, regular-context) email * sending error recorded. * * Matches the filter used by EmailSendingErrors so the admin bar indicator * fires for the same set of failures shown by the inline error banner. * * @since 4.9.0 * * @return bool */ private function has_email_sending_errors() { $all = EmailSendingDebug::get(); if ( empty( $all ) ) { return false; } $live = array_filter( $all, static function ( $record ) { return isset( $record['status'], $record['context'] ) && $record['status'] === 'failed' && $record['context'] === 'regular'; } ); return ! empty( $live ); } } Revisions.php 0000644 00000031236 15252506741 0007252 0 ustar 00 <?php namespace WPForms\Admin; use WP_Post; /** * Form Revisions. * * @since 1.7.3 */ class Revisions { /** * Current Form Builder panel view. * * @since 1.7.3 * * @var string */ private $view = 'revisions'; /** * Current Form ID. * * @since 1.7.3 * * @var int|false */ private $form_id = false; /** * Current Form. * * @since 1.7.3 * * @var WP_Post|null */ private $form; /** * Current Form Revision ID. * * @since 1.7.3 * * @var int|false */ private $revision_id = false; /** * Current Form Revision. * * @since 1.7.3 * * @var WP_Post|null */ private $revision; /** * Whether revisions panel was already viewed by the user at least once. * * @since 1.7.3 * * @var bool */ private $viewed; /** * Initialize the class if preconditions are met. * * @since 1.7.3 * * @return void */ public function init() { if ( ! $this->allow_load() ) { return; } // phpcs:disable WordPress.Security.NonceVerification.Recommended if ( isset( $_REQUEST['view'] ) ) { $this->view = sanitize_key( $_REQUEST['view'] ); } if ( isset( $_REQUEST['revision_id'] ) ) { $this->revision_id = absint( $_REQUEST['revision_id'] ); } // phpcs:enable WordPress.Security.NonceVerification.Recommended if ( ! $this->can_access_form() ) { return; } if ( $this->revision_id && wp_revisions_enabled( $this->form ) ) { $this->revision = wp_get_post_revision( $this->revision_id ); // Bail if we don't have a valid revision. if ( ! $this->revision instanceof WP_Post || $this->revision->post_parent !== $this->form_id || $this->revision->ID !== $this->revision_id ) { return; } } $this->hooks(); } /** * Whether it is allowed to load under certain conditions. * * - numeric, non-zero form ID provided, * - the form with this ID exists and was successfully fetched, * - we're in the Form Builder or processing an ajax request. * * @since 1.7.3 * * @return bool */ private function allow_load() { if ( ! ( wpforms_is_admin_page( 'builder' ) || wp_doing_ajax() ) ) { return false; } // phpcs:disable WordPress.Security.NonceVerification.Recommended $id = wp_doing_ajax() && isset( $_REQUEST['id'] ) ? absint( $_REQUEST['id'] ) : false; $id = isset( $_REQUEST['form_id'] ) && ! is_array( $_REQUEST['form_id'] ) ? absint( $_REQUEST['form_id'] ) : $id; // phpcs:enable WordPress.Security.NonceVerification.Recommended $this->form_id = $id; $form_handler = wpforms()->obj( 'form' ); if ( ! $form_handler ) { return false; } $this->form = $form_handler->get( $this->form_id ); return $this->form_id && $this->form instanceof WP_Post; } /** * Hook into WordPress lifecycle. * * @since 1.7.3 */ private function hooks() { // Restore a revision. The `admin_init` action has already fired, `current_screen` fires before headers are sent. add_action( 'current_screen', [ $this, 'process_restore' ] ); // Refresh a rendered list of revisions on the frontend. add_action( 'wp_ajax_wpforms_get_form_revisions', [ $this, 'fetch_revisions_list' ] ); // Mark Revisions panel as viewed when viewed for the first time. Hides the error badge. add_action( 'wp_ajax_wpforms_mark_panel_viewed', [ $this, 'mark_panel_viewed' ] ); // Back-compat for forms created with revisions disabled. add_action( 'wpforms_builder_init', [ $this, 'maybe_create_initial_revision' ] ); // Pass localized strings to frontend. add_filter( 'wpforms_builder_strings', [ $this, 'get_localized_strings' ], 10, 2 ); } /** * Get current revision, if available. * * @since 1.7.3 * * @return WP_Post|null */ public function get_revision() { return $this->revision; } /** * Get formatted date or time. * * @since 1.7.3 * * @param string $datetime UTC datetime from the post object. * @param string $part What to return - date or time, defaults to date. * * @return string */ public function get_formatted_datetime( $datetime, $part = 'date' ) { if ( $part === 'time' ) { return wpforms_time_format( $datetime, '', true ); } // M j format needs to keep one-line date. return wpforms_date_format( $datetime, 'M j', true ); } /** * Get admin (Form Builder) base URL with additional query args. * * @since 1.7.3 * * @param array $query_args Additional query args to append to the base URL. * * @return string */ public function get_url( $query_args = [] ) { $defaults = [ 'page' => 'wpforms-builder', 'view' => $this->view, 'form_id' => $this->form_id, ]; return add_query_arg( wp_parse_args( $query_args, $defaults ), admin_url( 'admin.php' ) ); } /** * Determine if Revisions panel was previously viewed by current user. * * @since 1.7.3 * * @return bool */ public function panel_viewed() { if ( $this->viewed === null ) { $this->viewed = (bool) get_user_meta( get_current_user_id(), 'wpforms_revisions_disabled_notice_dismissed', true ); } return $this->viewed; } /** * Mark Revisions panel as viewed by current user. * * @since 1.7.3 */ public function mark_panel_viewed() { // Run a security check. check_ajax_referer( 'wpforms-builder', 'nonce' ); if ( ! $this->panel_viewed() ) { $this->viewed = update_user_meta( get_current_user_id(), 'wpforms_revisions_disabled_notice_dismissed', true ); } wp_send_json_success( [ 'updated' => $this->viewed ] ); } /** * Get a rendered list of all revisions. * * @since 1.7.3 * * @return string */ public function render_revisions_list() { return wpforms_render( 'builder/revisions/list', $this->prepare_template_render_arguments(), true ); } /** * Prepare all arguments for the template to be rendered. * * Note: All data is escaped in the template. * * @since 1.7.3 * * @return array */ private function prepare_template_render_arguments() { $args = [ 'active_class' => $this->revision ? '' : ' active', 'current_version_url' => $this->get_url(), 'author_id' => $this->form->post_author, 'revisions' => [], 'show_avatars' => get_option( 'show_avatars' ), ]; $revisions = wp_get_post_revisions( $this->form_id ); if ( empty( $revisions ) ) { return $args; } // WordPress always orders entries by `post_date` column, which contains a date and time in site's timezone configured in settings. // This setting is per site, not per user, and it's not expected to be changed. However, if it was changed for whatever reason, // the order of revisions will be incorrect. This is definitely an edge case, but we can prevent this from ever happening // by sorting the results using `post_date_gmt` or `post_modified_gmt`, which contains UTC date and never changes. uasort( $revisions, static function ( $a, $b ) { return strtotime( $a->post_modified_gmt ) > strtotime( $b->post_modified_gmt ) ? -1 : 1; } ); // The first revision is always identical to the current version and should not be displayed in the list. $current_revision = array_shift( $revisions ); // Display the author of current version instead of a form author. $args['author_id'] = $current_revision->post_author; // Single read of the form-level map of AI-tagged revision IDs → source keys — // written by `wpforms_ai_revision_tracking_finalize()` after each AI-driven save. $ai_revision_map = (array) get_post_meta( $this->form_id, '_wpforms_ai_revisions', true ); // Compat: legacy flat list (unkeyed array of revision IDs) → treat as `smart_edit`. if ( ! empty( $ai_revision_map ) && is_int( reset( $ai_revision_map ) ) ) { $ai_revision_map = array_fill_keys( array_map( 'absint', $ai_revision_map ), 'smart_edit' ); } $ai_labels = [ 'smart_edit' => esc_html__( 'Auto-saved before Smart Edit', 'wpforms-lite' ), 'choices' => esc_html__( 'Auto-saved before AI Choices', 'wpforms-lite' ), ]; foreach ( $revisions as $revision ) { $time_diff = sprintf( /* translators: %s - relative time difference, e.g. "5 minutes", "12 days". */ __( '%s ago', 'wpforms-lite' ), human_time_diff( strtotime( $revision->post_modified_gmt . ' +0000' ) ) ); $date_time = sprintf( /* translators: %1$s - date, %2$s - time when item was created, e.g. "Oct 22 at 11:11am". */ __( '%1$s at %2$s', 'wpforms-lite' ), $this->get_formatted_datetime( $revision->post_modified_gmt ), $this->get_formatted_datetime( $revision->post_modified_gmt, 'time' ) ); $revision_source = $ai_revision_map[ $revision->ID ] ?? ''; $args['revisions'][] = [ 'active_class' => $this->revision && $this->revision->ID === $revision->ID ? ' active' : '', 'url' => $this->get_url( [ 'revision_id' => $revision->ID, ] ), 'author_id' => $revision->post_author, 'time_diff' => $time_diff, 'date_time' => $date_time, 'ai_label' => $ai_labels[ $revision_source ] ?? '', ]; } return $args; } /** * Fetch a list of revisions via ajax. * * @since 1.7.3 */ public function fetch_revisions_list() { // Run a security check. check_ajax_referer( 'wpforms-builder', 'nonce' ); wp_send_json_success( [ 'html' => $this->render_revisions_list(), ] ); } /** * Restore the revision (if needed) and reload the Form Builder. * * @since 1.7.3 * * @return void */ public function process_restore() { $is_restore_request = isset( $_GET['action'] ) && $_GET['action'] === 'restore_revision'; // Bail early. if ( ! $is_restore_request || ! $this->form_id || ! $this->form || ! $this->revision_id || ! $this->revision || ! check_admin_referer( 'restore_revision', 'wpforms_nonce' ) ) { return; } if ( ! $this->can_access_form() ) { wp_die( esc_html__( 'You do not have permission to restore revisions for this form.', 'wpforms-lite' ) ); } if ( ! $this->revision instanceof WP_Post || $this->revision->post_parent !== $this->form_id || $this->revision->ID !== $this->revision_id ) { wp_die( esc_html__( 'Invalid revision. The revision does not belong to this form.', 'wpforms-lite' ) ); } $restored_id = wp_restore_post_revision( $this->revision ); if ( $restored_id ) { wp_safe_redirect( wpforms()->obj( 'revisions' )->get_url( [ 'form_id' => $restored_id, ] ) ); exit; } } /** * Create initial revision for existing form. * * When a new form is created with revisions enabled, WordPress immediately creates first revision which is identical to the form. But when * a form was created with revisions disabled, this initial revision does not exist. Revisions are saved after post update, so modifying * a form that have no initial revision will update the post first, then a revision of this updated post will be saved. The version of * the form that existed before this update is now gone. To avoid losing this pre-revisions state, we create this initial revision * when the Form Builder loads, if needed. * * @since 1.7.3 * * @return void */ public function maybe_create_initial_revision() { // On new form creation there's no revisions yet, bail. Also, when revisions are disabled. // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( isset( $_GET['newform'] ) || ! wp_revisions_enabled( $this->form ) ) { return; } $revisions = wp_get_post_revisions( $this->form_id, [ 'fields' => 'ids', 'numberposts' => 1, ] ); if ( $revisions ) { return; } $initial_revision_id = wp_save_post_revision( $this->form_id ); $initial_revision = wp_get_post_revision( $initial_revision_id ); // Initial revision should belong to the author of the original form. if ( $initial_revision->post_author !== $this->form->post_author ) { wp_update_post( [ 'ID' => $initial_revision_id, 'post_author' => $this->form->post_author, ] ); } } /** * Pass localized strings to frontend. * * @since 1.7.3 * * @param array $strings All strings that will be passed to frontend. * @param WP_Post $form Current form object. * * @return array */ public function get_localized_strings( $strings, $form ) { $strings['revision_update_confirm'] = esc_html__( 'You’re about to save a form revision. Continuing will make this the current version.', 'wpforms-lite' ); return $strings; } /** * Check if the current user has permission to access the form and its revisions. * * @since 1.9.5 * * @return bool */ private function can_access_form(): bool { if ( ! wpforms_current_user_can( 'view_form_single', $this->form_id ) ) { return false; } if ( ! wpforms_current_user_can( 'edit_form_single', $this->form_id ) ) { return false; } return true; } } MediaLibrary.php 0000644 00000006562 15252506741 0007641 0 ustar 00 <?php namespace WPForms\Admin; /** * Sanitize SVG files uploaded through the WordPress media library within WPForms admin. * * Image Choices, the Content field and other WPForms admin areas upload images via the * standard WordPress media library, which bypasses the File Upload field's SVG sanitization. * This class closes that gap by sanitizing SVGs at upload time in WPForms-originated requests. * * @since 1.10.2 */ class MediaLibrary { /** * Initialize. * * @since 1.10.2 */ public function init(): void { $this->hooks(); } /** * Register hooks. * * @since 1.10.2 */ private function hooks(): void { add_filter( 'wp_handle_upload_prefilter', [ $this, 'sanitize_svg_upload' ] ); } /** * Sanitize an SVG being uploaded to the media library from a WPForms admin context. * * Reuses wpforms_sanitize_svg_file(): non-SVG files are untouched, and the upload is * rejected when an SVG cannot be sanitized (e.g. gzipped .svgz or invalid XML), mirroring * the File Upload field behavior. * * @since 1.10.2 * * @param array|mixed $file Array of a single uploaded file ( name, type, tmp_name, error, size ). * * @return array Modified file array. */ public function sanitize_svg_upload( $file ): array { $file = (array) $file; // Preserve any pre-existing upload error ( e.g. file too large, failed PHP upload ). if ( ! empty( $file['error'] ) ) { return $file; } // Only act on SVG uploads; everything else passes through untouched. if ( empty( $file['name'] ) || ! $this->is_svg( (string) $file['name'] ) ) { return $file; } // Limit to uploads originating from WPForms admin to avoid touching unrelated SVGs. if ( ! $this->is_wpforms_upload() ) { return $file; } if ( empty( $file['tmp_name'] ) || ! wpforms_sanitize_svg_file( $file['tmp_name'], (string) $file['name'] ) ) { $file['error'] = esc_html__( 'Sorry, this SVG file could not be sanitized, so it was not uploaded.', 'wpforms-lite' ); } return $file; } /** * Whether the file name points to an SVG ( or gzipped SVG ). * * @since 1.10.2 * * @param string $file_name Uploaded file name. * * @return bool */ private function is_svg( string $file_name ): bool { $extension = strtolower( pathinfo( $file_name, PATHINFO_EXTENSION ) ); return in_array( $extension, [ 'svg', 'svgz' ], true ); } /** * Whether the current upload request originates from a WPForms admin context. * * Detected either by the `wpforms-` post_id marker ( Rich Text field convention ) or by a * referer pointing to a WPForms admin page. Nonce and capability are already verified upstream * by WordPress core ( wp_ajax_upload_attachment ), so this only reads request context for routing. * * @since 1.10.2 * * @return bool */ private function is_wpforms_upload(): bool { // phpcs:disable WordPress.Security.NonceVerification.Missing $post_id = isset( $_POST['post_id'] ) ? sanitize_text_field( wp_unslash( $_POST['post_id'] ) ) : ''; // phpcs:enable WordPress.Security.NonceVerification.Missing if ( strpos( $post_id, 'wpforms-' ) === 0 ) { return true; } $referer = wp_get_referer(); if ( ! $referer ) { return false; } $query = (string) wp_parse_url( $referer, PHP_URL_QUERY ); wp_parse_str( $query, $args ); $page = isset( $args['page'] ) ? (string) $args['page'] : ''; return strpos( $page, 'wpforms' ) === 0; } } Settings/Gdpr/WPConsentCallout.php 0000644 00000010203 15252506741 0013160 0 ustar 00 <?php namespace WPForms\Admin\Settings\Gdpr; use WPForms\Education\WPConsent\Helper as WPConsent; /** * "Privacy Compliance" WPConsent cross-promo row in Settings → General GDPR. * * @since 2.0.0 */ class WPConsentCallout { /** * Init. * * @since 2.0.0 */ public function init() { $this->hooks(); } /** * Register hooks. * * @since 2.0.0 */ private function hooks() { // Priority 15 — runs after Pro's priority-5 GDPR sub-settings insert. add_filter( 'wpforms_settings_defaults', [ $this, 'add_setting' ], 15 ); add_action( 'wpforms_settings_enqueue', [ $this, 'enqueue_assets' ] ); } /** * Insert the Privacy Compliance row after the last GDPR setting. * * @since 2.0.0 * * @param array $settings Registered settings. * * @return array */ public function add_setting( $settings ): array { // Guard against a third-party filter passing a non-array; the `: array` return type would otherwise fatal. $settings = (array) $settings; if ( empty( $settings['general'] ) || ! wpforms_can_install( 'plugin' ) ) { return $settings; } // Anchor after gdpr-disable-details (Pro) when present, else after gdpr. $anchor = array_key_exists( 'gdpr-disable-details', $settings['general'] ) ? 'gdpr-disable-details' : 'gdpr'; $settings['general'] = wpforms_array_insert( $settings['general'], [ 'gdpr-privacy-compliance' => [ 'id' => 'gdpr-privacy-compliance', 'name' => esc_html__( 'Privacy Compliance', 'wpforms-lite' ), 'type' => 'content', 'content' => $this->get_button_html() . $this->get_description(), 'is_hidden' => ! wpforms_setting( 'gdpr' ), ], ], $anchor ); return $settings; } /** * Description paragraph shown below the button. * * @since 2.0.0 * * @return string */ private function get_description(): string { return '<p class="wpforms-setting-desc">' . sprintf( wp_kses( /* translators: %s - WPConsent learn-more URL. */ __( 'Improve GDPR, CCPA, and privacy compliance with cookie banner and consent records. <a href="%s" target="_blank" rel="noopener noreferrer" class="wpforms-learn-more">Learn More</a>', 'wpforms-lite' ), [ 'a' => [ 'href' => [], 'target' => [], 'rel' => [], 'class' => [], ], ] ), esc_url( wpforms_utm_link( 'https://wpconsent.com/', 'settings-gdpr', 'WPConsent Privacy Compliance' ) ) ) . '</p>'; } /** * Button markup: "Set Up" link when WPConsent is active, else blue install button. * * @since 2.0.0 * * @return string */ private function get_button_html(): string { if ( WPConsent::is_activated() ) { return sprintf( '<a href="%s" class="wpforms-btn wpforms-btn-md wpforms-btn-light-grey">%s</a>', esc_url( WPConsent::get_setup_url() ), esc_html__( 'Set Up WPConsent', 'wpforms-lite' ) ); } $button_data = WPConsent::get_install_button_data(); $attrs = ''; foreach ( $button_data['attrs'] as $attr_name => $attr_value ) { $attrs .= sprintf( ' %s="%s"', esc_attr( $attr_name ), esc_attr( $attr_value ) ); } // Pre-built attribute string is already escaped via esc_attr above. // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped return sprintf( '<button type="button" class="wpforms-btn wpforms-btn-md wpforms-btn-blue wpforms-wpconsent-install" data-setup-url="%s" data-setup-text="%s"%s>%s</button>', esc_url( WPConsent::get_setup_url() ), esc_attr( __( 'Set Up WPConsent', 'wpforms-lite' ) ), $attrs, esc_html__( 'Install WPConsent', 'wpforms-lite' ) ); } /** * Enqueue the inline install handler script on the settings page. * * @since 2.0.0 */ public function enqueue_assets() { if ( WPConsent::is_activated() || ! wpforms_can_install( 'plugin' ) ) { return; } $min = wpforms_get_min_suffix(); wp_enqueue_script( 'wpforms-wpconsent-install', WPFORMS_PLUGIN_URL . "assets/js/admin/education/wpconsent-install{$min}.js", [ 'jquery' ], WPFORMS_VERSION, true ); wp_localize_script( 'wpforms-wpconsent-install', 'wpforms_wpconsent_install', [ 'ajax_url' => admin_url( 'admin-ajax.php' ) ] ); } } Settings/ModernMarkup.php 0000644 00000011175 15252506741 0011475 0 ustar 00 <?php namespace WPForms\Admin\Settings; use WPForms\Helpers\Transient; /** * Modern Markup setting element. * * @since 1.8.1 */ class ModernMarkup { /** * Initialize class. * * @since 1.8.1 */ public function init() { $this->hooks(); } /** * Hooks. * * @since 1.8.1 */ public function hooks() { add_action( 'wpforms_create_form', [ $this, 'clear_transient' ] ); add_action( 'wpforms_save_form', [ $this, 'clear_transient' ] ); add_action( 'wpforms_delete_form', [ $this, 'clear_transient' ] ); add_action( 'wpforms_form_handler_update_status', [ $this, 'clear_transient' ] ); // Only continue if we are actually on the settings page. if ( ! wpforms_is_admin_page( 'settings' ) ) { return; } add_filter( 'wpforms_settings_defaults', [ $this, 'register_field' ] ); } /** * Register setting field. * * @since 1.8.1 * * @param array|mixed $settings Settings data. * * @return array * @noinspection HtmlUnknownTarget * @noinspection NullPointerExceptionInspection */ public function register_field( $settings ): array { /** * Allows to show/hide the Modern Markup setting field on the Settings page. * * @since 1.8.1 * * @param mixed $is_hidden Whether the setting must be hidden. */ $is_hidden = apply_filters( 'wpforms_admin_settings_modern_markup_register_field_is_hidden', wpforms_setting( 'modern-markup-hide-setting' ) ); if ( ! empty( $is_hidden ) ) { return $settings; } $settings = (array) $settings; $modern_markup = [ 'id' => 'modern-markup', 'name' => esc_html__( 'Use Modern Markup', 'wpforms-lite' ), 'desc' => sprintf( wp_kses( /* translators: %s - WPForms.com form markup setting URL. */ __( 'Check this option to use modern markup, which has increased accessibility and allows you to easily customize your forms in the block editor. <a href="%s" target="_blank" rel="noopener noreferrer">Read our form markup documentation</a> to learn more.', 'wpforms-lite' ), [ 'a' => [ 'href' => [], 'target' => [], 'rel' => [], 'class' => [], ], ] ), wpforms_utm_link( 'https://wpforms.com/docs/styling-your-forms/', 'settings-license', 'Form Markup Documentation' ) ), 'type' => 'toggle', 'status' => true, ]; $is_disabled_transient = Transient::get( 'modern_markup_setting_disabled' ); // Transient doesn't set or expired. if ( $is_disabled_transient === false ) { $forms = wpforms()->obj( 'form' )->get( '', [ 'post_status' => 'publish' ] ); $is_disabled_transient = ( ! empty( $forms ) && wpforms_has_field_type( 'credit-card', $forms, true ) ) ? '1' : '0'; // Re-check all the forms for the CC field once per day. Transient::set( 'modern_markup_setting_disabled', $is_disabled_transient, DAY_IN_SECONDS ); } /** * Allows enabling/disabling the Modern Markup setting field on the Settings page. * * @since 1.8.1 * * @param mixed $is_disabled Whether the Modern Markup setting must be disabled. */ $is_disabled = (bool) apply_filters( 'wpforms_admin_settings_modern_markup_register_field_is_disabled', ! empty( $is_disabled_transient ) ); $current_value = wpforms_setting( 'modern-markup' ); // In the case, when it is disabled because of the legacy CC field, add the corresponding description. if ( $is_disabled && ! empty( $is_disabled_transient ) && empty( $current_value ) ) { $modern_markup['disabled'] = true; $modern_markup['disabled_desc'] = sprintf( wp_kses( /* translators: %s - WPForms Stripe addon URL. */ __( '<strong>You cannot use modern markup because you’re using the deprecated Credit Card field.</strong> If you’d like to use modern markup, replace your credit card field with a payment gateway like <a href="%s" target="_blank" rel="noopener noreferrer">Stripe</a>.', 'wpforms-lite' ), [ 'a' => [ 'href' => [], 'target' => [], 'rel' => [], ], 'strong' => [], ] ), 'https://wpforms.com/docs/how-to-install-and-use-the-stripe-addon-with-wpforms' ); } $modern_markup = [ 'modern-markup' => $modern_markup, ]; $settings['general'] = wpforms_list_insert_after( $settings['general'], 'disable-css', $modern_markup ); return $settings; } /** * Clear transient in the case when the form is created/saved/deleted. * So, next time when the user opens the Settings page, * the Modern Markup setting will check for the legacy Credit Card field in all the forms again. * * @since 1.8.1 */ public function clear_transient() { Transient::delete( 'modern_markup_setting_disabled' ); } } Settings/Email.php 0000644 00000051130 15252506741 0010113 0 ustar 00 <?php namespace WPForms\Admin\Settings; use WPForms\Emails\Helpers; use WPForms\Emails\Notifications; use WPForms\Admin\Education\Helpers as EducationHelpers; /** * Email setting page. * Settings will be accessible via “WPForms” → “Settings” → “Email”. * * @since 1.8.5 */ class Email { /** * Content is plain text type. * * @since 1.8.5 * * @var bool */ private $plain_text; /** * Temporary storage for the style overrides preferences. * * @since 1.8.6 * * @var array */ private $style_overrides; /** * Determines if the user has the education modal. * If true, the value will be used to add the Education modal class to the setting controls. * This is only available in the free version. * * @since 1.8.6 * * @var string */ private $has_education; /** * Determines if the user has the legacy template. * If true, the value will be used to add the Legacy template class to the setting controls. * * @since 1.8.6 * * @var string */ private $has_legacy_template; /** * Initialize class. * * @since 1.8.5 */ public function init() { $this->hooks(); } /** * Hooks. * * @since 1.8.5 */ private function hooks() { add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] ); add_filter( 'wpforms_update_settings', [ $this, 'maybe_update_settings' ] ); add_filter( 'wpforms_settings_tabs', [ $this, 'register_settings_tabs' ], 5 ); add_filter( 'wpforms_settings_defaults', [ $this, 'register_settings_fields' ], 5 ); } /** * Enqueue scripts and styles. * Static resources are enqueued only on the "Email" settings page. * * @since 1.8.5 */ public function enqueue_assets() { // Leave if the current page is not the "Email" settings page. if ( ! $this->is_settings_page() ) { return; } $min = wpforms_get_min_suffix(); wp_enqueue_script( 'wpforms-contrast-checker', WPFORMS_PLUGIN_URL . "assets/js/admin/share/contrast-checker{$min}.js", [], WPFORMS_VERSION, true ); wp_enqueue_script( 'wpforms-xor', WPFORMS_PLUGIN_URL . "assets/js/admin/share/xor{$min}.js", [], WPFORMS_VERSION, true ); wp_enqueue_script( 'wpforms-admin-email-settings', WPFORMS_PLUGIN_URL . "assets/js/admin/email/settings{$min}.js", [ 'jquery', 'wpforms-admin', 'wp-escape-html', 'wp-url', 'choicesjs', 'wpforms-contrast-checker', 'wpforms-xor' ], WPFORMS_VERSION, true ); wp_localize_script( 'wpforms-admin-email-settings', 'wpforms_admin_email_settings', [ 'contrast_fail' => esc_html__( 'This color combination may be hard to read. Try increasing the contrast between the body and text colors.', 'wpforms-lite' ), ] ); } /** * Maybe update settings. * * @since 1.8.5 * * @param array $settings Admin area settings list. * * @return array */ public function maybe_update_settings( $settings ) { // Leave if the current page is not the "Email" settings page. if ( ! $this->is_settings_page() ) { return $settings; } // Remove the appearance mode switcher from the settings array. unset( $settings['email-appearance'] ); // Backup the Pro version background color setting to the free version. // This is needed to keep the background color when the Pro version is deactivated. if ( wpforms()->is_pro() && ! Helpers::is_legacy_html_template() ) { $settings['email-background-color'] = sanitize_hex_color( $settings['email-color-scheme']['email_background_color'] ); $settings['email-background-color-dark'] = sanitize_hex_color( $settings['email-color-scheme-dark']['email_background_color_dark'] ); return $settings; } // Backup the free version background color setting to the Pro version. // This is needed to keep the background color when the Pro version is activated. $settings['email-color-scheme']['email_background_color'] = sanitize_hex_color( $settings['email-background-color'] ); $settings['email-color-scheme-dark']['email_background_color_dark'] = sanitize_hex_color( $settings['email-background-color-dark'] ); return $settings; } /** * Register "Email" settings tab. * * @since 1.8.5 * * @param array $tabs Admin area tabs list. * * @return array */ public function register_settings_tabs( $tabs ) { $payments = [ 'email' => [ 'form' => true, 'name' => esc_html__( 'Email', 'wpforms-lite' ), 'submit' => esc_html__( 'Save Settings', 'wpforms-lite' ), ], ]; return wpforms_array_insert( $tabs, $payments, 'general' ); } /** * Register "Email" settings fields. * * @since 1.8.5 * * @param array $settings Admin area settings list. * * @return array */ public function register_settings_fields( $settings ) { $this->plain_text = Helpers::is_plain_text_template(); $this->has_education = ! wpforms()->is_pro() ? 'education-modal' : ''; $this->style_overrides = Helpers::get_current_template_style_overrides(); $this->has_legacy_template = Helpers::is_legacy_html_template() ? 'legacy-template' : ''; $preview_link = $this->get_current_template_preview_link(); // Add Email settings. $settings['email'] = [ 'email-heading' => [ 'id' => 'email-heading', 'content' => $this->get_heading_content(), 'type' => 'content', 'no_label' => true, 'class' => [ 'section-heading', 'no-desc' ], ], 'email-template' => [ 'id' => 'email-template', 'name' => esc_html__( 'Template', 'wpforms-lite' ), 'class' => [ 'wpforms-email-template', 'wpforms-card-image-group' ], 'type' => 'email_template', 'default' => Notifications::DEFAULT_TEMPLATE, 'options' => Helpers::get_email_template_choices(), 'value' => Helpers::get_current_template_name(), ], // The reason that we're using the 'content' type is to avoid saving the value in the option storage. // The value is used only for the appearance mode switcher, and merely acts as a switch between dark and light mode controls. 'email-appearance' => [ 'id' => 'email-appearance', 'name' => esc_html__( 'Appearance', 'wpforms-lite' ), 'desc' => esc_html__( 'Modern email clients support viewing emails in light and dark modes. You can upload a header image and customize the style for each appearance mode independently to ensure an optimal reading experience.', 'wpforms-lite' ), 'type' => 'radio', 'class' => [ 'wpforms-setting-row-radio', 'hide-for-template-none', 'email-appearance-mode-toggle', $this->has_legacy_template ], 'default' => 'light', 'is_hidden' => $this->plain_text, 'options' => [ 'light' => esc_html__( 'Light', 'wpforms-lite' ), 'dark' => esc_html__( 'Dark', 'wpforms-lite' ), ], ], 'email-preview' => [ 'id' => 'email-preview', 'type' => 'content', 'is_hidden' => empty( $preview_link ), 'content' => $preview_link, ], 'sending-heading' => [ 'id' => 'sending-heading', 'content' => '<h4>' . esc_html__( 'Sending', 'wpforms-lite' ) . '</h4>', 'type' => 'content', 'no_label' => true, 'class' => [ 'section-heading', 'no-desc' ], ], 'email-async' => [ 'id' => 'email-async', 'name' => esc_html__( 'Optimize Email Sending', 'wpforms-lite' ), 'desc' => sprintf( wp_kses( /* translators: %1$s - WPForms.com Email settings documentation URL. */ __( 'Send emails asynchronously, which can make processing faster but may delay email delivery by a minute or two. <a href="%1$s" target="_blank" rel="noopener noreferrer" class="wpforms-learn-more">Learn More</a>', 'wpforms-lite' ), [ 'a' => [ 'href' => [], 'target' => [], 'rel' => [], 'class' => [], ], ] ), esc_url( wpforms_utm_link( 'https://wpforms.com/docs/a-complete-guide-to-wpforms-settings/#email', 'Settings - Email', 'Optimize Email Sending Documentation' ) ) ), 'type' => 'toggle', 'status' => true, ], 'email-carbon-copy' => [ 'id' => 'email-carbon-copy', 'name' => esc_html__( 'Carbon Copy', 'wpforms-lite' ), 'desc' => esc_html__( 'Enable the ability to CC: email addresses in the form notification settings.', 'wpforms-lite' ), 'type' => 'toggle', 'status' => true, ], ]; // Add the style controls. $settings['email'] = $this->add_appearance_controls( $settings['email'] ); // Maybe add the Legacy template notice. $settings['email'] = $this->maybe_add_legacy_notice( $settings['email'] ); return $settings; } /** * Maybe add the legacy template notice. * * @since 1.8.5 * * @param array $settings Email settings. * * @return array */ private function maybe_add_legacy_notice( $settings ) { if ( ! $this->is_settings_page() || ! Helpers::is_legacy_html_template() ) { return $settings; } $content = '<div class="notice-info"><p>'; $content .= sprintf( wp_kses( /* translators: %1$s - WPForms.com Email settings legacy template documentation URL. */ __( 'Some style settings are not available when using the Legacy template. <a href="%1$s" target="_blank" rel="noopener noreferrer">Learn More</a>', 'wpforms-lite' ), [ 'a' => [ 'href' => [], 'target' => [], 'rel' => [], ], ] ), esc_url( wpforms_utm_link( 'https://wpforms.com/docs/customizing-form-notification-emails/#legacy-template', 'Settings - Email', 'Legacy Template' ) ) ); $content .= '</p></div>'; // Add the background color control after the header image. return wpforms_array_insert( $settings, [ 'email-legacy-notice' => [ 'id' => 'email-legacy-notice', 'content' => $content, 'type' => 'content', 'class' => 'wpforms-email-legacy-notice', ], ], 'email-template' ); } /** * Add appearance controls. * This will include controls for both Light and Dark modes. * * @since 1.8.6 * * @param array $settings Email settings. * * @return array */ private function add_appearance_controls( $settings ) { // Education modal arguments. $education_args = [ 'action' => 'upgrade' ]; // New settings for the Light mode. $new_setting = [ 'email-header-image' => [ 'id' => 'email-header-image', 'name' => esc_html__( 'Header Image', 'wpforms-lite' ), 'desc' => esc_html__( 'Upload or choose a logo to be displayed at the top of email notifications.', 'wpforms-lite' ), 'class' => [ 'wpforms-email-header-image', 'hide-for-template-none', 'has-preview-changes', 'email-light-mode', $this->get_external_header_image_class() ], 'type' => 'image', 'is_hidden' => $this->plain_text, 'show_remove' => true, ], 'email-header-image-size' => [ 'id' => 'email-header-image-size', 'no_label' => true, 'type' => 'select', 'class' => [ 'wpforms-email-header-image-size', 'has-preview-changes', 'email-light-mode' ], 'is_hidden' => true, 'choicesjs' => false, 'default' => 'medium', 'options' => [ 'small' => esc_html__( 'Small', 'wpforms-lite' ), 'medium' => esc_html__( 'Medium', 'wpforms-lite' ), 'large' => esc_html__( 'Large', 'wpforms-lite' ), ], ], 'email-color-scheme' => [ 'id' => 'email-color-scheme', 'name' => esc_html__( 'Color Scheme', 'wpforms-lite' ), 'class' => [ 'email-color-scheme', 'hide-for-template-none', 'has-preview-changes', 'email-light-mode', $this->has_education, $this->has_legacy_template ], 'type' => 'color_scheme', 'is_hidden' => $this->plain_text, 'education_badge' => $this->get_pro_education_badge(), 'data_attributes' => $this->has_education ? array_merge( [ 'name' => esc_html__( 'Color Scheme', 'wpforms-lite' ) ], $education_args ) : [], 'colors' => $this->get_color_scheme_controls(), ], 'email-typography' => [ 'id' => 'email-typography', 'name' => esc_html__( 'Typography', 'wpforms-lite' ), 'desc' => esc_html__( 'Choose the style that’s applied to all text in email notifications.', 'wpforms-lite' ), 'class' => [ 'hide-for-template-none', 'has-preview-changes', 'email-typography', 'email-light-mode', $this->has_education, $this->has_legacy_template ], 'education_badge' => $this->get_pro_education_badge(), 'data_attributes' => $this->has_education ? array_merge( [ 'name' => esc_html__( 'Typography', 'wpforms-lite' ) ], $education_args ) : [], 'type' => 'select', 'is_hidden' => $this->plain_text, 'choicesjs' => true, 'default' => 'sans-serif', 'options' => [ 'sans-serif' => esc_html__( 'Sans Serif', 'wpforms-lite' ), 'serif' => esc_html__( 'Serif', 'wpforms-lite' ), ], ], ]; // Add background color control if the Pro version is not active or Legacy template is selected. $new_setting = $this->maybe_add_background_color_control( $new_setting ); return wpforms_array_insert( $settings, $this->add_appearance_dark_mode_controls( $new_setting ), 'email-appearance' ); } /** * Add appearance dark mode controls. * * This function will duplicate the default "Light" color * controls to create corresponding controls for dark mode. * * @since 1.8.6 * * @param array $settings Email settings. * * @return array */ private function add_appearance_dark_mode_controls( $settings ) { // Duplicate and modify each item for dark mode. foreach ( $settings as $key => $item ) { // Duplicate the item with '-dark' added to the key. $dark_key = "{$key}-dark"; $settings[ $dark_key ] = $item; // Modify the 'name' within the duplicated item. if ( isset( $settings[ $dark_key ]['id'] ) ) { $settings[ $dark_key ]['id'] .= '-dark'; $classes = &$settings[ $dark_key ]['class']; $classes[] = 'email-dark-mode'; $classes[] = 'wpforms-hide'; // Remove classes related to light mode. $classes = array_filter( $classes, static function ( $class_name ) { return $class_name !== 'email-light-mode' && $class_name !== 'has-external-image-url'; } ); } // Override the description for the header image control. if ( $key === 'email-header-image' ) { $settings[ $dark_key ]['desc'] = esc_html__( 'Upload or choose a logo to be displayed at the top of email notifications. Light mode image will be used if not set.', 'wpforms-lite' ); } // Override the background color control attributes. if ( $key === 'email-background-color' ) { $settings[ $dark_key ]['default'] = sanitize_hex_color( $this->style_overrides['email_background_color_dark'] ); $settings[ $dark_key ]['data']['fallback-color'] = sanitize_hex_color( $this->style_overrides['email_background_color_dark'] ); } // Override the color scheme control attributes. if ( $key === 'email-color-scheme' ) { $settings[ $dark_key ]['colors'] = $this->get_color_scheme_controls( true ); } } return $settings; } /** * Get Email settings heading content. * * @since 1.8.5 * * @return string */ private function get_heading_content() { return wpforms_render( 'admin/settings/email-heading' ); } /** * Get Email settings education badge. * This is only available in the free version. * * @since 1.8.6 * * @return string */ private function get_pro_education_badge() { // Leave early if the user has the Lite version. if ( empty( $this->has_education ) ) { return ''; } // Output the education badge. return EducationHelpers::get_badge( 'Pro' ); } /** * Generate color scheme controls for the color picker. * * @since 1.8.6 * * @param bool $is_dark_mode Whether the color scheme is for dark mode. * * @return array */ private function get_color_scheme_controls( $is_dark_mode = false ) { // Append '_dark' to keys if it's for dark mode. $is_dark_mode_suffix = $is_dark_mode ? '_dark' : ''; // Data attributes to disable extensions from appearing in the input field. $color_scheme_data = [ '1p-ignore' => 'true', // 1Password ignore. 'lp-ignore' => 'true', // LastPass ignore. ]; $colors = []; $controls = [ "email_background_color{$is_dark_mode_suffix}" => esc_html__( 'Background', 'wpforms-lite' ), "email_body_color{$is_dark_mode_suffix}" => esc_html__( 'Body', 'wpforms-lite' ), "email_text_color{$is_dark_mode_suffix}" => esc_html__( 'Text', 'wpforms-lite' ), "email_links_color{$is_dark_mode_suffix}" => esc_html__( 'Links', 'wpforms-lite' ), ]; foreach ( $controls as $key => $label ) { // Construct the color controls array. $colors[ $key ] = [ 'name' => $label, 'data' => array_merge( [ 'fallback-color' => $this->style_overrides[ $key ], ], $color_scheme_data ), ]; } return $colors; } /** * Get current email template hyperlink. * * @since 1.8.5 * * @return string */ private function get_current_template_preview_link() { // Leave if the user has the legacy template is set or the user doesn't have the capability. if ( ! wpforms_current_user_can() || Helpers::is_legacy_html_template() ) { return ''; } $template_name = Helpers::get_current_template_name(); $current_template = Notifications::get_available_templates( $template_name ); // Return empty string if the current template is not found. // Leave early if the preview link is empty. if ( ! isset( $current_template['path'] ) || ! class_exists( $current_template['path'] ) || empty( $current_template['preview'] ) ) { return ''; } return sprintf( wp_kses( /* translators: %1$s - Email template preview URL. */ __( '<a href="%1$s" class="wpforms-btn-preview" target="_blank" rel="noopener">Preview Email Template</a>', 'wpforms-lite' ), [ 'a' => [ 'class' => true, 'href' => true, 'target' => true, 'rel' => true, ], ] ), esc_url( $current_template['preview'] ) ); } /** * Maybe add the background color control to the email settings. * This is only available in the free version. * * @since 1.8.5 * * @param array $settings Email settings. * * @return array */ private function maybe_add_background_color_control( $settings ) { // Leave as is if the Pro version is active and no legacy template available. if ( ! Helpers::is_legacy_html_template() && wpforms()->is_pro() ) { return $settings; } // Add the background color control after the header image. return wpforms_array_insert( $settings, [ 'email-background-color' => [ 'id' => 'email-background-color', 'name' => esc_html__( 'Background Color', 'wpforms-lite' ), 'desc' => esc_html__( 'Customize the background color of the email template.', 'wpforms-lite' ), 'class' => [ 'email-background-color', 'has-preview-changes', 'email-light-mode' ], 'type' => 'color', 'is_hidden' => $this->plain_text, 'default' => '#e9eaec', 'data' => [ 'fallback-color' => $this->style_overrides['email_background_color'], '1p-ignore' => 'true', // 1Password ignore. 'lp-ignore' => 'true', // LastPass ignore. ], ], ], 'email-color-scheme', 'before' ); } /** * Gets the class for the header image control. * * This is used to determine if the header image is external. * Legacy header image control was allowing external URLs. * * Note that this evaluation is only available for the "Light" mode, * as the "Dark" mode is a new feature and doesn't have the legacy header image control. * * @since 1.8.5 * * @return string */ private function get_external_header_image_class() { $header_image_url = wpforms_setting( 'email-header-image', '' ); // If the header image URL is empty, return an empty string. if ( empty( $header_image_url ) ) { return ''; } $site_url = home_url(); // Get the current site's URL. // Get the hosts of the site URL and the header image URL. $site_url_host = wp_parse_url( $site_url, PHP_URL_HOST ); $header_image_url_host = wp_parse_url( $header_image_url, PHP_URL_HOST ); // Check if the header image URL host is different from the site URL host. if ( $header_image_url_host && $site_url_host && $header_image_url_host !== $site_url_host ) { return 'has-external-image-url'; } return ''; // If none of the conditions match, return an empty string. } /** * Determine if the current page is the "Email" settings page. * * @since 1.8.5 * * @return bool */ private function is_settings_page() { return wpforms_is_admin_page( 'settings', 'email' ); } } Settings/Payments.php 0000644 00000003775 15252506741 0010700 0 ustar 00 <?php namespace WPForms\Admin\Settings; /** * Payments setting page. * Settings will be accessible via “WPForms” → “Settings” → “Payments”. * * @since 1.8.2 */ class Payments { /** * Initialize class. * * @since 1.8.2 */ public function init() { $this->hooks(); } /** * Hooks. * * @since 1.8.2 */ private function hooks() { add_filter( 'wpforms_settings_tabs', [ $this, 'register_settings_tabs' ], 5 ); add_filter( 'wpforms_settings_defaults', [ $this, 'register_settings_fields' ], 5 ); } /** * Register "Payments" settings tab. * * @since 1.8.2 * * @param array $tabs Admin area tabs list. * * @return array */ public function register_settings_tabs( $tabs ) { $payments = [ 'payments' => [ 'name' => esc_html__( 'Payments', 'wpforms-lite' ), 'form' => true, 'submit' => esc_html__( 'Save Settings', 'wpforms-lite' ), ], ]; return wpforms_array_insert( $tabs, $payments, 'validation' ); } /** * Register "Payments" settings fields. * * @since 1.8.2 * * @param array $settings Admin area settings list. * * @return array */ public function register_settings_fields( $settings ) { $currency_option = []; $currencies = wpforms_get_currencies(); // Format currencies for select element. foreach ( $currencies as $code => $currency ) { $currency_option[ $code ] = sprintf( '%s (%s %s)', $currency['name'], $code, $currency['symbol'] ); } $settings['payments'] = [ 'heading' => [ 'id' => 'payments-heading', 'content' => '<h4>' . esc_html__( 'Payments', 'wpforms-lite' ) . '</h4>', 'type' => 'content', 'no_label' => true, 'class' => [ 'section-heading', 'no-desc' ], ], 'currency' => [ 'id' => 'currency', 'name' => esc_html__( 'Currency', 'wpforms-lite' ), 'type' => 'select', 'choicesjs' => true, 'search' => true, 'default' => 'USD', 'options' => $currency_option, ], ]; return $settings; } } Settings/Captcha/ActiveLayerCallout.php 0000644 00000007012 15252506741 0014163 0 ustar 00 <?php namespace WPForms\Admin\Settings\Captcha; use WPForms\Education\ActiveLayer\Helper as ActiveLayer; /** * Horizontal "Better Way to Stop Spam" notice on Settings → CAPTCHA. * * @since 1.10.0.5 */ class ActiveLayerCallout { const VIEW = 'captcha'; /** * Register hooks. * * @since 1.10.0.5 */ public function hooks() { add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] ); add_action( 'wpforms_admin_settings_after', [ $this, 'render' ], 5, 1 ); } /** * Are we on the CAPTCHA settings view? * * @since 1.10.0.5 * * @return bool */ private function is_captcha_settings_screen(): bool { if ( ! is_admin() || ! function_exists( 'get_current_screen' ) ) { return false; } $screen = get_current_screen(); if ( ! $screen || $screen->id !== 'wpforms_page_wpforms-settings' ) { return false; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended $view = isset( $_GET['view'] ) ? sanitize_key( wp_unslash( $_GET['view'] ) ) : 'general'; return $view === self::VIEW; } /** * Enqueue stylesheet only on the CAPTCHA settings view. * * @since 1.10.0.5 */ public function enqueue_assets() { if ( ! $this->is_captcha_settings_screen() ) { return; } $min = wpforms_get_min_suffix(); wp_enqueue_style( 'wpforms-activelayer-callout', WPFORMS_PLUGIN_URL . "assets/css/admin/activelayer-callout{$min}.css", [], WPFORMS_VERSION ); } /** * Render a horizontal notice above the CAPTCHA form. * * @since 1.10.0.5 * * @param string $view Current settings view slug. */ public function render( $view ) { if ( $view !== self::VIEW ) { return; } $modal = ActiveLayer::get_modal_data(); $attrs_html = ''; $cta_classes = 'wpforms-btn wpforms-btn-md wpforms-btn-orange wpforms-activelayer-callout__cta'; $action = $modal['attrs']['data-action'] ?? ''; if ( $action === 'install' ) { $modal['link_text'] = __( 'Install ActiveLayer', 'wpforms-lite' ); } elseif ( $action === 'activate' ) { $modal['link_text'] = __( 'Activate ActiveLayer', 'wpforms-lite' ); } if ( ! empty( $modal['class'] ) ) { $cta_classes .= ' ' . $modal['class']; } if ( ! empty( $modal['attrs'] ) ) { foreach ( $modal['attrs'] as $key => $value ) { $attrs_html .= sprintf( ' %s="%s"', esc_attr( $key ), esc_attr( $value ) ); } } ?> <div class="wpforms-activelayer-callout" role="region" aria-labelledby="wpforms-activelayer-callout-heading"> <div class="wpforms-activelayer-callout__icon"> <img src="<?php echo esc_url( WPFORMS_PLUGIN_URL . 'assets/images/anti-spam/activelayer.svg' ); ?>" alt=""> </div> <div class="wpforms-activelayer-callout__body"> <p class="wpforms-activelayer-callout__eyebrow"><?php esc_html_e( 'Better Way to Stop Spam', 'wpforms-lite' ); ?></p> <h3 id="wpforms-activelayer-callout-heading" class="wpforms-activelayer-callout__heading"> <?php esc_html_e( 'Prefer no CAPTCHAs?', 'wpforms-lite' ); ?> </h3> <p class="wpforms-activelayer-callout__text"> <?php esc_html_e( 'CAPTCHAs can reduce form completions by up to 40%. ActiveLayer catches bots invisibly, without asking your visitors to prove they\'re human. Free tier available.', 'wpforms-lite' ); ?> </p> <a href="<?php echo esc_url( $modal['link'] ); ?>" class="<?php echo esc_attr( $cta_classes ); ?>" <?php echo $attrs_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>> <?php echo esc_html( $modal['link_text'] ); ?> </a> </div> </div> <?php } } Settings/Captcha/HCaptcha.php 0000644 00000003105 15252506741 0012101 0 ustar 00 <?php namespace WPForms\Admin\Settings\Captcha; /** * HCaptcha settings class. * * @since 1.8.0 */ class HCaptcha extends Captcha { /** * Captcha variable used for JS invoking. * * @since 1.8.0 * * @var string */ protected static $api_var = 'hcaptcha'; /** * Get captcha key name. * * @since 1.8.0 * * @var string */ protected static $slug = 'hcaptcha'; /** * The hCaptcha Javascript URL-resource. * * @since 1.8.0 * * @var string */ protected static $url = 'https://hcaptcha.com/1/api.js'; /** * Array of captcha settings fields. * * @since 1.8.0 * * @return array[] */ public function get_settings_fields() { return [ 'hcaptcha-heading' => [ 'id' => 'hcaptcha-heading', 'content' => $this->get_field_desc(), 'type' => 'content', 'no_label' => true, 'class' => [ 'section-heading', 'specific-note' ], ], 'hcaptcha-site-key' => [ 'id' => 'hcaptcha-site-key', 'name' => esc_html__( 'Site Key', 'wpforms-lite' ), 'type' => 'text', ], 'hcaptcha-secret-key' => [ 'id' => 'hcaptcha-secret-key', 'name' => esc_html__( 'Secret Key', 'wpforms-lite' ), 'type' => 'masked_text', ], 'hcaptcha-fail-msg' => [ 'id' => 'hcaptcha-fail-msg', 'name' => esc_html__( 'Fail Message', 'wpforms-lite' ), 'desc' => esc_html__( 'Displays to users who fail the verification process.', 'wpforms-lite' ), 'type' => 'text', 'default' => esc_html__( 'hCaptcha verification failed, please try again later.', 'wpforms-lite' ), ], ]; } } Settings/Captcha/Captcha.php 0000644 00000010051 15252506741 0011767 0 ustar 00 <?php namespace WPForms\Admin\Settings\Captcha; /** * Base captcha settings class. * * @since 1.8.0 */ abstract class Captcha { /** * Saved CAPTCHA settings. * * @since 1.8.0 * * @var array */ protected $settings; /** * List of required static properties. * * @since 1.8.0 * * @var array */ private $required_static_properties = [ 'api_var', 'slug', 'url', ]; /** * Initialize class. * * @since 1.8.0 */ public function init() { $this->settings = wp_parse_args( wpforms_get_captcha_settings(), [ 'provider' => 'none' ] ); foreach ( $this->required_static_properties as $property ) { if ( empty( static::${$property} ) ) { // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error trigger_error( sprintf( 'The $%s static property is required for a %s class', esc_html( $property ), __CLASS__ ), E_USER_ERROR ); } } } /** * Array of captcha settings fields. * * @since 1.8.0 * * @return array[] */ abstract public function get_settings_fields(); /** * Get API request url for the captcha preview. * * @since 1.8.0 * * @return string */ public function get_api_url() { $url = static::$url; if ( ! empty( $url ) ) { $url = add_query_arg( $this->get_api_url_query_arg(), $url ); } /** * Filter API URL. * * @since 1.6.4 * * @param string $url API URL. * @param array $settings Captcha settings array. */ return apply_filters( 'wpforms_admin_settings_captcha_get_api_url', $url, $this->settings ); } /** * Enqueue assets for the CAPTCHA settings page. * * @since 1.8.0 */ public function enqueues() { /** * Allow/disallow to enquire captcha settings. * * @since 1.6.4 * * @param boolean $allow True/false. Default: false. */ $disable_enqueues = apply_filters( 'wpforms_admin_settings_captcha_enqueues_disable', false ); if ( $disable_enqueues || ! $this->is_captcha_preview_ready() ) { return; } $api_url = $this->get_api_url(); $provider_name = $this->settings['provider']; $handle = "wpforms-settings-{$provider_name}"; // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion wp_enqueue_script( $handle, $api_url, [ 'jquery' ], null, true ); wp_add_inline_script( $handle, $this->get_inline_script() ); } /** * Inline script for initialize captcha JS code. * * @since 1.8.0 * * @return string */ protected function get_inline_script() { return /** @lang JavaScript */ 'var wpformsSettingsCaptchaLoad = function() { jQuery( ".wpforms-captcha" ).each( function( index, el ) { var widgetID = ' . static::$api_var . '.render( el ); jQuery( el ).attr( "data-captcha-id", widgetID ); } ); jQuery( document ).trigger( "wpformsSettingsCaptchaLoaded" ); };'; } /** * Check if CAPTCHA config is ready to display a preview. * * @since 1.8.0 * * @return bool */ public function is_captcha_preview_ready() { return ( ( $this->settings['provider'] === static::$slug || ( $this->settings['provider'] === 'recaptcha' && $this->settings['recaptcha_type'] === 'v2' ) ) && ! empty( $this->settings['site_key'] ) && ! empty( $this->settings['secret_key'] ) ); } /** * Retrieve query arguments for the CAPTCHA API URL. * * @since 1.8.0 * * @return array */ protected function get_api_url_query_arg() { /** * Modify captcha api url parameters. * * @since 1.8.0 * * @param array $params Array of parameters. * @param array $params Saved CAPTCHA settings. */ return (array) apply_filters( 'wpforms_admin_settings_captcha_get_api_url_query_arg', [ 'onload' => 'wpformsSettingsCaptchaLoad', 'render' => 'explicit', ], $this->settings ); } /** * Heading description. * * @since 1.8.0 * * @return string */ public function get_field_desc() { $content = wpforms_render( 'admin/settings/' . static::$slug . '-description' ); return wpforms_render( 'admin/settings/specific-note', [ 'content' => $content ], true ); } } Settings/Captcha/ReCaptcha.php 0000644 00000005571 15252506741 0012271 0 ustar 00 <?php namespace WPForms\Admin\Settings\Captcha; /** * ReCaptcha settings class. * * @since 1.8.0 */ class ReCaptcha extends Captcha { /** * Captcha variable used for JS invoking. * * @since 1.8.0 * * @var string */ protected static $api_var = 'grecaptcha'; /** * Get captcha key name. * * @since 1.8.0 * * @var string */ protected static $slug = 'recaptcha'; /** * The ReCAPTCHA Javascript URL-resource. * * @since 1.8.0 * * @var string */ protected static $url = 'https://www.google.com/recaptcha/api.js'; /** * Array of captcha settings fields. * * @since 1.8.0 * * @return array[] */ public function get_settings_fields() { return [ 'recaptcha-heading' => [ 'id' => 'recaptcha-heading', 'content' => $this->get_field_desc(), 'type' => 'content', 'no_label' => true, 'class' => [ 'wpforms-setting-recaptcha', 'section-heading', 'specific-note' ], ], 'recaptcha-type' => [ 'id' => 'recaptcha-type', 'name' => esc_html__( 'Type', 'wpforms-lite' ), 'type' => 'radio', 'default' => 'v2', 'options' => [ 'v2' => esc_html__( 'Checkbox reCAPTCHA v2', 'wpforms-lite' ), 'invisible' => esc_html__( 'Invisible reCAPTCHA v2', 'wpforms-lite' ), 'v3' => esc_html__( 'reCAPTCHA v3', 'wpforms-lite' ), ], 'class' => [ 'wpforms-setting-recaptcha' ], ], 'recaptcha-site-key' => [ 'id' => 'recaptcha-site-key', 'name' => esc_html__( 'Site Key', 'wpforms-lite' ), 'type' => 'text', ], 'recaptcha-secret-key' => [ 'id' => 'recaptcha-secret-key', 'name' => esc_html__( 'Secret Key', 'wpforms-lite' ), 'type' => 'masked_text', ], 'recaptcha-fail-msg' => [ 'id' => 'recaptcha-fail-msg', 'name' => esc_html__( 'Fail Message', 'wpforms-lite' ), 'desc' => esc_html__( 'Displays to users who fail the verification process.', 'wpforms-lite' ), 'type' => 'text', 'default' => esc_html__( 'Google reCAPTCHA verification failed, please try again later.', 'wpforms-lite' ), ], 'recaptcha-v3-threshold' => [ 'id' => 'recaptcha-v3-threshold', 'name' => esc_html__( 'Score Threshold', 'wpforms-lite' ), 'desc' => esc_html__( 'reCAPTCHA v3 returns a score (1.0 is very likely a good interaction, 0.0 is very likely a bot). If the score is less than or equal to this threshold, the form submission will be blocked and the message above will be displayed.', 'wpforms-lite' ), 'type' => 'number', 'attr' => [ 'step' => '0.1', 'min' => '0.0', 'max' => '1.0', ], 'default' => esc_html__( '0.4', 'wpforms-lite' ), 'class' => $this->settings['provider'] === 'recaptcha' && $this->settings['recaptcha_type'] === 'v3' ? [ 'wpforms-setting-recaptcha' ] : [ 'wpforms-setting-recaptcha', 'wpforms-hidden' ], ], ]; } } Settings/Captcha/Page.php 0000644 00000023466 15252506741 0011316 0 ustar 00 <?php namespace WPForms\Admin\Settings\Captcha; use WPForms\Admin\Notice; /** * CAPTCHA setting page. * * @since 1.8.0 */ class Page { /** * Slug identifier for admin page view. * * @since 1.8.0 * * @var string */ const VIEW = 'captcha'; /** * Saved CAPTCHA settings. * * @since 1.8.0 * * @var array */ private $settings; /** * All available captcha types. * * @since 1.8.0 * * @var array */ private $captchas; /** * Initialize class. * * @since 1.8.0 */ public function init() { // Only load if we are actually on the settings page. if ( ! wpforms_is_admin_page( 'settings' ) ) { return; } // Listen the previous reCAPTCHA page and safely redirect from it. if ( wpforms_is_admin_page( 'settings', 'recaptcha' ) ) { wp_safe_redirect( add_query_arg( 'view', self::VIEW, admin_url( 'admin.php?page=wpforms-settings' ) ) ); exit; } $this->init_settings(); $this->hooks(); } /** * Init CAPTCHA settings. * * @since 1.8.0 */ public function init_settings() { $this->settings = wp_parse_args( wpforms_get_captcha_settings(), [ 'provider' => 'none' ] ); /** * Filter available captcha for the settings page. * * @since 1.8.0 * * @param array $captcha Array where key is captcha name and value is captcha class instance. * @param array $settings Array of settings. */ $this->captchas = apply_filters( 'wpforms_admin_settings_captcha_page_init_settings_available_captcha', [ 'hcaptcha' => new HCaptcha(), 'recaptcha' => new ReCaptcha(), 'turnstile' => new Turnstile(), ], $this->settings ); foreach ( $this->captchas as $captcha ) { $captcha->init(); } } /** * Hooks. * * @since 1.8.0 */ public function hooks() { add_filter( 'wpforms_settings_tabs', [ $this, 'register_settings_tabs' ], 5, 1 ); add_filter( 'wpforms_settings_defaults', [ $this, 'register_settings_fields' ], 5, 1 ); add_action( 'wpforms_settings_updated', [ $this, 'updated' ] ); add_action( 'wpforms_settings_enqueue', [ $this, 'enqueues' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'apply_noconflict' ], 9999 ); ( new ActiveLayerCallout() )->hooks(); } /** * Register CAPTCHA settings tab. * * @since 1.8.0 * * @param array $tabs Admin area tabs list. * * @return array */ public function register_settings_tabs( $tabs ) { $captcha = [ self::VIEW => [ 'name' => esc_html__( 'CAPTCHA', 'wpforms-lite' ), 'form' => true, 'submit' => esc_html__( 'Save Settings', 'wpforms-lite' ), ], ]; return wpforms_array_insert( $tabs, $captcha, 'email' ); } /** * Register CAPTCHA settings fields. * * @since 1.8.0 * * @param array $settings Admin area settings list. * * @return array */ public function register_settings_fields( $settings ) { $settings[ self::VIEW ] = [ self::VIEW . '-heading' => [ 'id' => self::VIEW . '-heading', 'content' => '<h4>' . esc_html__( 'CAPTCHA', 'wpforms-lite' ) . '</h4><p>' . esc_html__( 'A CAPTCHA is an anti-spam technique which helps to protect your website from spam and abuse while letting real people pass through with ease.', 'wpforms-lite' ) . '</p>', 'type' => 'content', 'no_label' => true, 'class' => [ 'wpforms-setting-captcha-heading', 'section-heading' ], ], self::VIEW . '-provider' => [ 'id' => self::VIEW . '-provider', 'type' => 'radio', 'default' => 'none', 'options' => [ 'hcaptcha' => 'hCaptcha', 'recaptcha' => 'reCAPTCHA', 'turnstile' => 'Turnstile', 'none' => esc_html__( 'None', 'wpforms-lite' ), ], 'desc' => sprintf( wp_kses( /* translators: %s - WPForms.com CAPTCHA comparison page URL. */ __( 'Not sure which service is right for you? <a href="%s" target="_blank" rel="noopener noreferrer">Check out our comparison</a> for more details.', 'wpforms-lite' ), [ 'a' => [ 'href' => [], 'target' => [], 'rel' => [], ], ] ), esc_url( wpforms_utm_link( 'https://wpforms.com/docs/setup-captcha-wpforms/', 'Settings - Captcha', 'Captcha Comparison Documentation' ) ) ), ], ]; // Add settings fields for each of available captcha types. foreach ( $this->captchas as $captcha ) { $settings[ self::VIEW ] = array_merge( $settings[ self::VIEW ], $captcha->get_settings_fields() ); } $settings[ self::VIEW ] = array_merge( $settings[ self::VIEW ], [ 'recaptcha-noconflict' => [ 'id' => 'recaptcha-noconflict', 'name' => esc_html__( 'No-Conflict Mode', 'wpforms-lite' ), 'desc' => esc_html__( 'Forcefully remove other CAPTCHA occurrences in order to prevent conflicts. Only enable this option if your site is having compatibility issues or instructed by support.', 'wpforms-lite' ), 'type' => 'toggle', 'status' => true, ], self::VIEW . '-preview' => [ 'id' => self::VIEW . '-preview', 'name' => esc_html__( 'Preview', 'wpforms-lite' ), 'content' => '<p class="desc wpforms-captcha-preview-desc">' . esc_html__( 'Please save settings to generate a preview of your CAPTCHA here.', 'wpforms-lite' ) . '</p>', 'type' => 'content', 'class' => [ 'wpforms-hidden' ], ], ] ); if ( $this->settings['provider'] === 'hcaptcha' || $this->settings['provider'] === 'turnstile' || ( $this->settings['provider'] === 'recaptcha' && $this->settings['recaptcha_type'] === 'v2' ) ) { // phpcs:disable WPForms.PHP.ValidateHooks.InvalidHookName /** * Modify captcha settings data. * * @since 1.6.4 * * @param array $data Array of settings. */ $data = apply_filters( 'wpforms_admin_pages_settings_captcha_data', [ 'sitekey' => $this->settings['site_key'], 'theme' => $this->settings['theme'], ] ); // phpcs:enable WPForms.PHP.ValidateHooks.InvalidHookName // Prepare HTML for CAPTCHA preview. $placeholder_description = $settings[ self::VIEW ][ self::VIEW . '-preview' ]['content']; $captcha_description = esc_html__( 'This CAPTCHA is generated using your site and secret keys. If an error is displayed, please double-check your keys.', 'wpforms-lite' ); $captcha_preview = sprintf( '<div class="wpforms-captcha-container" style="pointer-events:none!important;cursor:default!important;"> <div %s></div> <input type="text" name="wpforms-captcha-hidden" class="wpforms-recaptcha-hidden" style="position:absolute!important;clip:rect(0,0,0,0)!important;height:1px!important;width:1px!important;border:0!important;overflow:hidden!important;padding:0!important;margin:0!important;"> </div>', wpforms_html_attributes( '', [ 'wpforms-captcha', 'wpforms-captcha-' . $this->settings['provider'] ], $data ) ); $settings[ self::VIEW ][ self::VIEW . '-preview' ]['content'] = sprintf( '<div class="wpforms-captcha-preview"> %1$s <p class="desc">%2$s</p> </div> <div class="wpforms-captcha-placeholder wpforms-hidden">%3$s</div>', $captcha_preview, $captcha_description, $placeholder_description ); $settings[ self::VIEW ][ self::VIEW . '-preview' ]['class'] = []; } return $settings; } /** * Re-init CAPTCHA settings when plugin settings were updated. * * @since 1.8.0 */ public function updated() { $this->init_settings(); $this->notice(); } /** * Display notice about the CAPTCHA preview. * * @since 1.8.0 */ private function notice() { if ( ! wpforms_is_admin_page( 'settings', self::VIEW ) || ! $this->is_captcha_preview_ready() ) { return; } Notice::info( esc_html__( 'A preview of your CAPTCHA is displayed below. Please view to verify the CAPTCHA settings are correct.', 'wpforms-lite' ) ); } /** * Check if CAPTCHA config is ready to display a preview. * * @since 1.8.0 * * @return bool */ private function is_captcha_preview_ready() { $current_captcha = $this->get_current_captcha(); if ( ! $current_captcha ) { return false; } return $current_captcha->is_captcha_preview_ready(); } /** * Enqueue assets for the CAPTCHA settings page. * * @since 1.8.0 */ public function enqueues() { $current_captcha = $this->get_current_captcha(); if ( ! $current_captcha ) { return; } $current_captcha->enqueues(); } /** * Get current active captcha object. * * @since 1.8.0 * * @return object|string */ private function get_current_captcha() { return ! empty( $this->captchas[ $this->settings['provider'] ] ) ? $this->captchas[ $this->settings['provider'] ] : ''; } /** * Use the CAPTCHA no-conflict mode. * * When enabled in the WPForms settings, forcefully remove all other * CAPTCHA enqueues to prevent conflicts. Filter can be used to target * specific pages, etc. * * @since 1.6.4 */ public function apply_noconflict() { if ( ! wpforms_is_admin_page( 'settings', self::VIEW ) || empty( wpforms_setting( 'recaptcha-noconflict' ) ) || /** * Allow/disallow applying non-conflict mode for captcha scripts. * * @since 1.6.4 * * @param boolean $allow True/false. Default: true. */ ! apply_filters( 'wpforms_admin_settings_captcha_apply_noconflict', true ) // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName ) { return; } $scripts = wp_scripts(); $urls = [ 'google.com/recaptcha', 'gstatic.com/recaptcha', 'hcaptcha.com/1', 'challenges.cloudflare.com/turnstile' ]; foreach ( $scripts->queue as $handle ) { // Skip the WPForms JavaScript assets. if ( ! isset( $scripts->registered[ $handle ] ) || false !== strpos( $scripts->registered[ $handle ]->handle, 'wpforms' ) ) { return; } foreach ( $urls as $url ) { if ( false !== strpos( $scripts->registered[ $handle ]->src, $url ) ) { wp_dequeue_script( $handle ); wp_deregister_script( $handle ); break; } } } } } Settings/Captcha/Turnstile.php 0000644 00000004773 15252506741 0012433 0 ustar 00 <?php namespace WPForms\Admin\Settings\Captcha; /** * Cloudflare Turnstile settings class. * * @since 1.8.0 */ class Turnstile extends Captcha { /** * Captcha variable used for JS invoking. * * @since 1.8.0 * * @var string */ protected static $api_var = 'turnstile'; /** * Captcha key name. * * @since 1.8.0 * * @var string */ protected static $slug = 'turnstile'; /** * The Turnstile Javascript URL-resource. * * @since 1.8.0 * * @var string */ protected static $url = 'https://challenges.cloudflare.com/turnstile/v0/api.js'; /** * Inline script for captcha initialization JS code. * * @since 1.8.0 * * @return string */ protected function get_inline_script() { return /** @lang JavaScript */ 'const wpformsCaptcha = jQuery( ".wpforms-captcha" ); if ( wpformsCaptcha.length > 0 ) { var widgetID = ' . static::$api_var . '.render( ".wpforms-captcha", { "refresh-expired": "auto" } ); wpformsCaptcha.attr( "data-captcha-id", widgetID); jQuery( document ).trigger( "wpformsSettingsCaptchaLoaded" ); }'; } /** * Array of captcha settings fields. * * @since 1.8.0 * * @return array[] */ public function get_settings_fields() { return [ 'turnstile-heading' => [ 'id' => 'turnstile-heading', 'content' => $this->get_field_desc(), 'type' => 'content', 'no_label' => true, 'class' => [ 'section-heading', 'specific-note' ], ], 'turnstile-site-key' => [ 'id' => 'turnstile-site-key', 'name' => esc_html__( 'Site Key', 'wpforms-lite' ), 'type' => 'text', ], 'turnstile-secret-key' => [ 'id' => 'turnstile-secret-key', 'name' => esc_html__( 'Secret Key', 'wpforms-lite' ), 'type' => 'masked_text', ], 'turnstile-fail-msg' => [ 'id' => 'turnstile-fail-msg', 'name' => esc_html__( 'Fail Message', 'wpforms-lite' ), 'desc' => esc_html__( 'Displays to users who fail the verification process.', 'wpforms-lite' ), 'type' => 'text', 'default' => esc_html__( 'Cloudflare Turnstile verification failed, please try again later.', 'wpforms-lite' ), ], 'turnstile-theme' => [ 'id' => 'turnstile-theme', 'name' => esc_html__( 'Type', 'wpforms-lite' ), 'type' => 'select', 'default' => 'auto', 'options' => [ 'auto' => esc_html__( 'Auto', 'wpforms-lite' ), 'light' => esc_html__( 'Light', 'wpforms-lite' ), 'dark' => esc_html__( 'Dark', 'wpforms-lite' ), ], ], ]; } } Splash/SplashCache.php 0000644 00000006573 15252506741 0010707 0 ustar 00 <?php namespace WPForms\Admin\Splash; use WPForms\Helpers\CacheBase; /** * Splash cache handler. * * @since 1.8.7 */ class SplashCache extends CacheBase { use SplashTrait; /** * Remote source URL. * * @since 1.8.7 * * @var string */ public const REMOTE_SOURCE = 'https://wpformsapi.com/feeds/v1/splash/'; /** * Determine if the class is allowed to load. * * @since 1.8.7 * * @return bool */ protected function allow_load(): bool { return is_admin() || wp_doing_cron() || wpforms_doing_wp_cli(); } /** * Provide settings. * * @since 1.8.7 * * @return array Settings array. */ protected function setup(): array { return [ // Remote source URL. 'remote_source' => $this->get_remote_source(), // Splash cache file name. 'cache_file' => 'splash.json', /** * Time-to-live of the splash cache file in seconds. * * This applies to `uploads/wpforms/cache/splash.json` file. * * @since 1.8.7 * * @param integer $cache_ttl Cache time-to-live, in seconds. * Default value: WEEK_IN_SECONDS. */ 'cache_ttl' => (int) apply_filters( 'wpforms_admin_splash_cache_ttl', WEEK_IN_SECONDS ), ]; } /** * Get remote source URL. * * @since 1.8.7 * * @return string */ protected function get_remote_source(): string { return defined( 'WPFORMS_SPLASH_REMOTE_SOURCE' ) ? WPFORMS_SPLASH_REMOTE_SOURCE : self::REMOTE_SOURCE; } /** * Prepare splash modal data. * * @since 1.8.7 * * @param array $data Splash modal data. */ protected function prepare_cache_data( $data ): array { if ( empty( $data ) || ! is_array( $data ) ) { return []; } $blocks = $this->prepare_blocks( $data ); if ( empty( $blocks ) ) { return []; } $prepared_data['blocks'] = $blocks; return $prepared_data; } /** * Prepare blocks. * * @since 1.8.7 * * @param array $data Splash modal data. * * @return array Prepared blocks. */ private function prepare_blocks( array $data ): array { $user_license = $this->get_user_license(); $user_version = $this->get_user_version(); // Filter data by plugin version. $blocks = array_filter( $data, static function ( $block ) use ( $user_license ) { // Return only blocks that match the user license. return in_array( $user_license, $block['type'] ?? [], true ); } ); // Get the latest 10 blocks. $blocks = array_slice( $blocks, 0, 10 ); // Reset indexes. $blocks = array_values( $blocks ); return array_map( function ( $block ) use ( $user_version ) { $block_version = $block['version'] ?? ''; // Prepare buttons URLs. $block['buttons'] = $this->prepare_buttons( $block['btns'] ?? [] ); // Change main button URL if the block version is greater than the user version. if ( version_compare( $block_version, $user_version, '>' ) ) { $block['buttons']['main'] = [ 'url' => $this->get_update_url(), 'text' => __( 'Update Now', 'wpforms-lite' ), ]; } // If the block version is less than the user version, set 'new' to false. if ( version_compare( $block_version, $user_version, '<' ) ) { $block['new'] = false; } // Set layout based on an image type. $block['layout'] = $this->get_block_layout( $block['img'] ); unset( $block['btns'] ); return $block; }, $blocks, array_keys( $blocks ) ) ?? []; } } Splash/SplashScreen.php 0000644 00000022363 15252506741 0011116 0 ustar 00 <?php namespace WPForms\Admin\Splash; /** * What's New class. * * @since 1.8.7 */ class SplashScreen { use SplashTrait; /** * Splash data. * * @since 1.8.7 * * @var array */ private $splash_data = []; /** * Whether it is a new WPForms installation. * * @since 1.8.8 * * @var bool */ private $is_new_install; /** * Initialize class. * * @since 1.8.7 */ public function init() { $this->hooks(); } /** * Hooks. * * @since 1.8.7 */ private function hooks() { add_action( 'admin_init', [ $this, 'initialize_splash_data' ], 15 ); add_action( 'admin_enqueue_scripts', [ $this, 'admin_enqueue_scripts' ] ); add_action( 'admin_footer', [ $this, 'admin_footer' ] ); add_filter( 'removable_query_args', [ $this, 'removable_query_args' ] ); add_action( 'update_option_wpforms_license', [ $this, 'reset_splash_data' ] ); } /** * Initialize splash data. * * @since 1.8.7 */ public function initialize_splash_data() { if ( ! $this->is_allow_splash() ) { return; } if ( empty( $this->splash_data ) ) { $cached_data_obj = wpforms()->obj( 'splash_cache' ); $cached_data = $cached_data_obj ? $cached_data_obj->get() : null; if ( empty( $cached_data ) ) { return; } $default_data = $this->get_default_data(); $this->splash_data = wp_parse_args( $cached_data, $default_data ); } } /** * Enqueue assets. * * @since 1.8.7 */ public function admin_enqueue_scripts() { $min = wpforms_get_min_suffix(); // jQuery confirm. wp_register_script( 'jquery-confirm', WPFORMS_PLUGIN_URL . 'assets/lib/jquery.confirm/jquery-confirm.min.js', [ 'jquery' ], '1.0.0', true ); wp_register_style( 'jquery-confirm', WPFORMS_PLUGIN_URL . 'assets/lib/jquery.confirm/jquery-confirm.min.css', [], '1.0.0' ); wp_register_script( 'wpforms-splash-modal', WPFORMS_PLUGIN_URL . "assets/js/admin/splash/modal{$min}.js", [ 'jquery', 'wp-util' ], WPFORMS_VERSION, true ); wp_register_style( 'wpforms-splash-modal', WPFORMS_PLUGIN_URL . "assets/css/admin/admin-splash-modal{$min}.css", [], WPFORMS_VERSION ); wp_localize_script( 'wpforms-splash-modal', 'wpforms_splash_data', [ 'nonce' => wp_create_nonce( 'wpforms_dash_widget_nonce' ), 'triggerForceOpen' => $this->should_open_splash(), ] ); } /** * Output splash modal. * * @since 1.8.7 */ public function admin_footer(): void { if ( $this->is_splash_empty() || ! $this->is_allow_splash() ) { return; } $this->render_modal(); } /** * Check if splash data is empty. * * @since 1.8.7 * @since 1.8.8 Changed method visibility from private to public. * * @return bool True if empty, false otherwise. */ public function is_splash_empty(): bool { if ( empty( $this->splash_data ) ) { return true; } return empty( $this->splash_data['blocks'] ); } /** * Render splash modal. * * @since 1.8.7 * * @param array $data Splash modal data. */ public function render_modal( array $data = [] ) { wp_enqueue_script( 'jquery-confirm' ); wp_enqueue_style( 'jquery-confirm' ); wp_enqueue_script( 'wpforms-splash-modal' ); wp_enqueue_style( 'wpforms-splash-modal' ); if ( $this->should_open_splash() ) { $this->update_splash_version(); } $data = ! empty( $data ) ? $data : $this->splash_data; if ( ! $this->is_plugin_version_up_to_date() ) { $data['display_notice'] = true; } // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wpforms_render( 'admin/splash/modal', $data, true ); } /** * Check if the plugin version is up to date. * * @since 1.9.7 * * @return bool True if up to date, false otherwise. */ private function is_plugin_version_up_to_date(): bool { if ( ! empty( $this->splash_data['blocks'] ) && is_array( $this->splash_data['blocks'] ) ) { // Get the first block and check its version. $first_block = reset( $this->splash_data['blocks'] ); // If the first block has a version, and it's greater than the current user version, that means the splash contains features that are not available to the user. if ( ! empty( $first_block['version'] ) && version_compare( $first_block['version'], $this->get_user_version(), '>' ) ) { return false; } } return true; } /** * Add a splash link to footer. * * @since 1.8.7 * @deprecated 1.9.7 * * @param string|mixed $content Footer content. * * @return string Footer content. */ public function add_splash_link( $content ): string { return $content; } /** * Check if splash modal can be displayed manually, via a link. * Used in footer and in form builder context menu. * * @since 1.8.8 * @deprecated 1.9.7 * * @return bool */ public function is_available_for_display(): bool { return true; } /** * Check if splash modal is allowed. * Only allow in Form Builder, WPForms pages, and the Dashboard. * * @since 1.8.7 * * @return bool True if allowed, false otherwise. */ public function is_allow_splash(): bool { return wpforms_is_admin_page( 'builder' ) || wpforms_is_admin_page() || $this->is_dashboard(); } /** * Check if the current page is the dashboard. * * @since 1.8.8 * * @return bool True if it is the dashboard, false otherwise. */ private function is_dashboard(): bool { global $pagenow; return $pagenow === 'index.php'; } /** * Check if splash modal should be forced open. * * @since 1.8.8 * * @return bool True if it should be forced open, false otherwise. */ private function is_force_open(): bool { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return sanitize_key( $_GET['wpforms_action'] ?? '' ) === 'preview-splash-screen'; } /** * Check if splash modal should be opened. * * @since 1.8.7 * * @return bool True if splash should open, false otherwise. */ private function should_open_splash(): bool { // Skip if announcements are hidden, or it is the dashboard page. if ( $this->is_dashboard() || $this->hide_splash_modal() ) { return false; } // Allow if a splash version different from the current plugin major version, and it's not a new installation. $should_open_splash = $this->get_latest_splash_version() !== $this->get_major_version( WPFORMS_VERSION ) && ( ! $this->is_new_install() || $this->is_force_open() ); if ( ! $should_open_splash ) { return false; } // Skip if user on the builder page and the Challenge can be started. if ( wpforms_is_admin_page( 'builder' ) ) { return $this->is_allow_builder_splash(); } return true; } /** * Check if splash modal should be allowed on the builder page. * If the Challenge can be started, the splash modal should not be displayed. * * @since 1.9.0 * * @return bool True if allowed, false otherwise. */ private function is_allow_builder_splash(): bool { $challenge = wpforms()->obj( 'challenge' ); return ! ( $challenge->challenge_force_start() || $challenge->challenge_can_start() ); } /** * Check if the plugin is newly installed. * * Get all migrations that have run. * If the only migration with a timestamp is the current version, it's a new installation. * * @since 1.8.8 * * @return bool True if new install, false otherwise. */ private function is_new_install(): bool { if ( isset( $this->is_new_install ) ) { return $this->is_new_install; } $option_name = wpforms()->is_pro() ? 'wpforms_versions' : 'wpforms_versions_lite'; $migrations_run = get_option( $option_name, [] ); if ( empty( $migrations_run ) ) { return true; } unset( $migrations_run[ WPFORMS_VERSION ] ); $this->is_new_install = empty( end( $migrations_run ) ); return $this->is_new_install; } /** * Determine if the current update is a minor update. * * This method checks the version history of migrations run and compares * the last recorded version with the current version to determine if * the update is minor or major. * * @since 1.9.0 * * @return bool True if it's a minor update, false otherwise. */ private function is_minor_update(): bool { return $this->get_major_version( $this->get_previous_plugin_version() ) === $this->get_major_version( WPFORMS_VERSION ); } /** * Check if splash modal should be hidden. * * @since 1.8.8 * * @return bool True if hidden, false otherwise. */ private function hide_splash_modal(): bool { /** * Force to hide splash modal. * * @since 1.8.8 * * @param bool $hide_splash_modal True to hide, false otherwise. */ return (bool) apply_filters( 'wpforms_admin_splash_screen_hide_splash_modal', wpforms_setting( 'hide-announcements' ) ); } /** * Remove certain arguments from a query string that WordPress should always hide for users. * * @since 1.8.8 * * @param array $removable_query_args An array of parameters to remove from the URL. * * @return array Extended/filtered array of parameters to remove from the URL. */ public function removable_query_args( $removable_query_args ) { $removable_query_args[] = 'wpforms_action'; return $removable_query_args; } /** * Reset splash data after license update. * * @since 1.9.7 */ public function reset_splash_data(): void { // Force update splash data cache. wpforms()->obj( 'splash_cache' )->update( true ); } } Splash/SplashTrait.php 0000644 00000014337 15252506741 0010764 0 ustar 00 <?php namespace WPForms\Admin\Splash; use WPForms\Migrations\Base as MigrationsBase; trait SplashTrait { /** * Default plugin version. * * @since 1.9.3 * * @var string */ private $default_plugin_version = '1.8.6'; // The last version before the "What's New?" feature. /** * Previous plugin version. * * @since 1.9.3 * * @var string */ private $previous_plugin_version; /** * Latest splash version. * * @since 1.9.3 * * @var string */ private $latest_splash_version; /** * Get the latest splash version. * * @since 1.8.7 * * @return string Splash version. */ private function get_latest_splash_version(): string { if ( $this->latest_splash_version ) { return $this->latest_splash_version; } $this->latest_splash_version = get_option( 'wpforms_splash_version', '' ); // Create option if it doesn't exist. if ( empty( $this->latest_splash_version ) ) { $this->latest_splash_version = $this->default_plugin_version; update_option( 'wpforms_splash_version', $this->latest_splash_version ); } return $this->latest_splash_version; } /** * Update option with the latest splash version. * * @since 1.8.7 */ private function update_splash_version(): void { update_option( 'wpforms_splash_version', $this->get_major_version( WPFORMS_VERSION ) ); } /** * Get user license type. * * @since 1.8.8 * * @return string */ private function get_user_license(): string { $license = wpforms_get_license_type(); if ( empty( $license ) ) { $license = 'lite'; } /** * License type used for splash screen. * * @since 1.8.8 * * @param string $license License type. */ return (string) apply_filters( 'wpforms_admin_splash_splashtrait_get_user_license', $license ); } /** * Get user version. * * @since 1.9.7 * * @return string User version. */ private function get_user_version(): string { /** * User version used for splash screen. * * @since 1.9.7 * * @param string $version User version. */ return (string) apply_filters( 'wpforms_admin_splash_splashtrait_get_user_version', $this->get_major_version( WPFORMS_VERSION ) ); } /** * Get default splash modal data. * * @since 1.8.7 * * @return array Splash modal data. */ private function get_default_data(): array { return [ 'license' => $this->get_user_license(), 'display_notice' => false, 'update_url' => $this->get_update_url(), 'buttons' => [ 'get_started' => __( 'Get Started', 'wpforms-lite' ), 'learn_more' => __( 'Learn More', 'wpforms-lite' ), ], 'header' => [ 'image' => WPFORMS_PLUGIN_URL . 'assets/images/splash/sullie.svg', 'title' => __( 'What’s New in WPForms', 'wpforms-lite' ), 'description' => __( 'We\'ve added some great new features to help grow your business and generate more leads. Here are the highlights...', 'wpforms-lite' ), ], 'footer' => [ 'title' => __( 'Start Building Smarter WordPress Forms', 'wpforms-lite' ), 'description' => __( 'Add advanced form fields and conditional logic, plus offer more payment options, manage entries, and connect to your favorite marketing tools – all when you purchase a premium plan.', 'wpforms-lite' ), 'upgrade' => [ 'text' => __( 'Upgrade to Pro Today', 'wpforms-lite' ), 'url' => wpforms_admin_upgrade_link( 'splash-modal', 'Upgrade to Pro Today' ), ], ], ]; } /** * Get plugin update URL. * * @since 1.9.7 * * @return string Update URL. */ private function get_update_url(): string { $plugin_slug = defined( 'WPFORMS_PLUGIN_DIR' ) ? plugin_basename( WPFORMS_PLUGIN_DIR ) : 'wpforms'; $plugin_path = $plugin_slug . '/wpforms.php'; return wp_nonce_url( self_admin_url( 'update.php?action=upgrade-plugin&plugin=' . $plugin_path ), 'upgrade-plugin_' . $plugin_path ); } /** * Prepare buttons. * * @since 1.8.7 * * @param array $buttons Buttons. * * @return array Prepared buttons. */ private function prepare_buttons( array $buttons ): array { return array_map( function ( $button ) { return [ 'url' => $this->prepare_url( $button['url'] ), 'text' => $button['text'], ]; }, $buttons ); } /** * Prepare URL. * * @since 1.8.7 * * @param string $url URL. * * @return string Prepared URL. */ private function prepare_url( string $url ): string { $replace_tags = [ '{admin_url}' => admin_url(), '{license_key}' => wpforms_get_license_key(), ]; return str_replace( array_keys( $replace_tags ), array_values( $replace_tags ), $url ); } /** * Get block layout. * * @since 1.8.7 * * @param array $image Image data. * * @return string Block layout. */ private function get_block_layout( array $image ): string { $image_type = $image['type'] ?? 'icon'; switch ( $image_type ) { case 'icon': $layout = 'one-third-two-thirds'; break; case 'illustration': $layout = 'fifty-fifty'; break; default: $layout = 'full-width'; break; } return $layout; } /** * Get a major version. * * @since 1.8.7.2 * * @param string $version Version. * * @return string Major version. */ private function get_major_version( $version ): string { // Allow only digits and dots. $clean_version = preg_replace( '/[^0-9.]/', '.', $version ); // Get version parts. $version_parts = explode( '.', $clean_version ); // If a version has more than 3 parts - use only first 3. Get block data only for major versions. if ( count( $version_parts ) > 3 ) { $version = implode( '.', array_slice( $version_parts, 0, 3 ) ); } return $version; } /** * Get the WPForms plugin previous version. * * @since 1.8.8 * * @return string Previous WPForms version. */ private function get_previous_plugin_version(): string { if ( $this->previous_plugin_version ) { return $this->previous_plugin_version; } $this->previous_plugin_version = get_option( MigrationsBase::PREVIOUS_CORE_VERSION_OPTION_NAME, '' ); if ( empty( $this->previous_plugin_version ) ) { $this->previous_plugin_version = $this->default_plugin_version; // The last version before the "What's New?" feature. } return $this->previous_plugin_version; } } Splash/SplashUpgrader.php 0000644 00000002205 15252506741 0011441 0 ustar 00 <?php namespace WPForms\Admin\Splash; use WPForms\Migrations\Base as MigrationsBase; /** * Splash upgrader. * * @since 1.8.7 */ class SplashUpgrader { use SplashTrait; /** * Initialize class. * * @since 1.8.7 */ public function init(): void { $this->hooks(); } /** * Hooks. * * @since 1.8.7 */ private function hooks(): void { // Update splash data after plugin update. add_action( 'wpforms_migrations_base_core_upgraded', [ $this, 'update_splash_data_on_migration' ], 10, 2 ); } /** * Update splash modal data on migration. * * @since 1.8.8 * * @param string|mixed $previous_version Previous plugin version. * @param MigrationsBase $migrations_obj Migrations object. * * @noinspection PhpUnusedParameterInspection */ public function update_splash_data_on_migration( $previous_version, MigrationsBase $migrations_obj ): void { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed // Force update splash data cache. $splash_cache_obj = wpforms()->obj( 'splash_cache' ); if ( ! $splash_cache_obj ) { return; } $splash_cache_obj->update( true ); } } Forms/Analytics.php 0000644 00000034050 15252506741 0010303 0 ustar 00 <?php namespace WPForms\Admin\Forms; use WP_Post; use WPForms\Admin\Forms\Table\Facades\Columns; use WPForms\Analytics\Analytics as AnalyticsFeature; use WPForms\Pro\Analytics\Analytics as ProAnalytics; /** * Analytics columns on the Forms Overview page. * * Registers three new columns (Views, Interactions, Conversion), pre-fetches * their data via a bulk query, and renders cells. * * @since 2.0.0 */ class Analytics { /** * Pre-fetched stats keyed by form ID. * * @since 2.0.0 * * @var array */ private $stats = []; /** * Start the engine. * * @since 2.0.0 */ public function init(): void { $this->hooks(); } /** * Register WordPress hooks. * * @since 2.0.0 */ private function hooks(): void { add_filter( 'wpforms_admin_forms_table_facades_columns_data', [ $this, 'register_columns' ] ); add_filter( 'wpforms_overview_table_column_value', [ $this, 'render_column' ], 10, 3 ); add_action( 'wpforms_admin_forms_list_table_prepare_items_after', [ $this, 'prefetch_stats' ] ); add_filter( 'default_hidden_columns', [ $this, 'set_column_defaults' ], 11, 2 ); add_action( 'wpforms_admin_header_after', [ $this, 'render_feature_tooltip' ] ); // Priority 10 runs after the core row actions (priority 9), so "Entries" is // already present to insert "Analytics" after. add_filter( 'wpforms_overview_row_actions', [ $this, 'add_analytics_row_action' ], 10, 2 ); } /** * Add an "Analytics" row action after "Entries" in the Forms Overview table. * * Pro/Elite only — Lite users see the upgrade badge in the analytics columns * instead. Gated by the same per-form capability as the analytics page. * * @since 2.0.0 * * @param array|mixed $row_actions Row action links keyed by slug. * @param WP_Post $form Form object. * * @return array */ public function add_analytics_row_action( $row_actions, $form ): array { $row_actions = (array) $row_actions; if ( ! wpforms()->is_pro() || ! ProAnalytics::is_allowed() || ! wpforms_current_user_can( 'view_entries_form_single', $form->ID ) ) { return $row_actions; } $analytics = [ 'analytics' => sprintf( '<a href="%s" title="%s">%s</a>', esc_url( add_query_arg( [ 'page' => 'wpforms-analytics', 'form_id' => $form->ID, ], admin_url( 'admin.php' ) ) ), esc_attr__( 'View analytics', 'wpforms-lite' ), esc_html__( 'Analytics', 'wpforms-lite' ) ), ]; // Insert after Entries (or append when absent), mirroring register_columns(). $position = array_search( 'entries', array_keys( $row_actions ), true ); $position = $position !== false ? $position + 1 : count( $row_actions ); return array_slice( $row_actions, 0, $position, true ) + $analytics + array_slice( $row_actions, $position, null, true ); } /** * Register analytics columns (Views, Interactions, Conversion). * * Inserts three analytics columns immediately after the Entries column when * it exists, or appends them at the end of the columns list otherwise. * * Hooks: wpforms_admin_forms_table_facades_columns_data (filter). * * @since 2.0.0 * * @param array|mixed $columns Existing columns data. * * @return array */ public function register_columns( $columns ): array { $columns = (array) $columns; $analytics_columns = [ 'analytics_views' => [ 'label' => esc_html__( 'Views', 'wpforms-lite' ), ], 'analytics_interactions' => [ 'label' => esc_html__( 'Interactions', 'wpforms-lite' ), ], 'analytics_conversion' => [ 'label' => esc_html__( 'Conversion', 'wpforms-lite' ), ], ]; $position = array_search( 'entries', array_keys( $columns ), true ); $position = $position !== false ? $position + 1 : count( $columns ); return array_slice( $columns, 0, $position, true ) + $analytics_columns + array_slice( $columns, $position, null, true ); } /** * Render an analytics column cell. * * Dispatches to the appropriate private renderer based on $column_name. * Returns the original $value unchanged when $form is not a WP_Post instance * or when $column_name does not match an analytics column. * * Hooks: wpforms_overview_table_column_value (filter). * Matches the signature used by Forms\Locator::column_value — no strict type * hints on filter callback params. * * @since 2.0.0 * * @param mixed $value Current cell value (pass-through for unknown columns). * @param mixed $form Form post object; must be WP_Post to render. * @param string $column_name Column identifier. * * @return mixed */ public function render_column( $value, $form, string $column_name ) { if ( ! $form instanceof WP_Post ) { return $value; } $form_id = (int) $form->ID; switch ( $column_name ) { case 'analytics_views': $value = $this->render_views( $form_id ); break; case 'analytics_interactions': $value = $this->render_interactions( $form_id ); break; case 'analytics_conversion': $value = $this->render_conversion( $form_id ); break; } return $value; } /** * Bulk-fetch stats for all forms on the current overview page. * * Retrieves views and submissions for every form in a single DB call and * stores the result in $stats keyed by form ID. No-ops when $form_ids is * empty or non-array, or when the analytics DB object is unavailable. * * Hooks: wpforms_admin_forms_list_table_prepare_items_after (action). The * parameter is untyped because the action's payload depends on the dispatcher * (`ListTable::prepare_items()`), which may pass a non-array if the items * lookup degraded - keeping this method tolerant prevents a fatal in that path. * * @since 2.0.0 * * @param mixed $form_ids Form IDs (expected: int[]). */ public function prefetch_stats( $form_ids ): void { if ( ! is_array( $form_ids ) || empty( $form_ids ) ) { $this->stats = []; return; } $db = wpforms()->obj( 'analytics_db' ); if ( ! $db ) { $this->stats = []; return; } $this->stats = $db->get_overview_stats( $form_ids ); } /** * Hide the Shortcode column by default on the Forms Overview screen. * * Hooks: default_hidden_columns (filter, priority 11). Runs after * ListTable::default_hidden_columns() (priority 10), receives its output, * gates on the same screen ID + Columns::has_selected_columns() conditions, * and appends 'shortcode' to keep the analytics columns visually grouped * after Entries on first visit. * * @since 2.0.0 * * @param mixed $hidden Hidden columns array (filter input may be non-array). * @param mixed $screen WP_Screen-like object for the current admin screen. * * @return array */ public function set_column_defaults( $hidden, $screen ): array { $hidden = (array) $hidden; if ( ! is_object( $screen ) || ! isset( $screen->id ) || $screen->id !== 'toplevel_page_wpforms-overview' ) { return $hidden; } if ( Columns::has_selected_columns() ) { return $hidden; } $hidden[] = 'shortcode'; return $hidden; } /** * Render the Views column cell. * * @since 2.0.0 * * @param int $form_id Form ID. * * @return string */ private function render_views( int $form_id ): string { $views = $this->stats[ $form_id ]['views'] ?? null; if ( $views === null ) { return '—'; } $value = number_format_i18n( (int) $views ); if ( ! wpforms()->is_pro() ) { return $value; } if ( ! ProAnalytics::is_allowed() ) { return $value; } return $this->build_analytics_link( $form_id, 'views', $value ); } /** * Render the Interactions column cell. * * On Lite, returns the Pro upgrade badge. On Pro with stats present, * returns a link to the Analytics page for the given form. * Returns a dash on Pro when the form has no stats. * * @since 2.0.0 * * @param int $form_id Form ID. * * @return string */ private function render_interactions( int $form_id ): string { if ( ! wpforms()->is_pro() || ! ProAnalytics::is_allowed() ) { return $this->render_pro_badge( __( 'Upgrade to Pro to unlock Interactions', 'wpforms-lite' ) ); } $interactions = $this->stats[ $form_id ]['interactions'] ?? null; if ( $interactions === null ) { return '—'; } return $this->build_analytics_link( $form_id, 'interactions', number_format_i18n( (int) $interactions ) ); } /** * Render the Pro upgrade teaser for locked columns (Lite only). * * Outputs a blurred-value image wrapped in the education-modal link, so the * locked metric reads as "there is data here, upgrade to see it". Both gated * columns (Interactions, Conversion) share a single `analytics-upgrade` * utm_content value. The `data-utm-medium` attribute is read by the Lite * education modal JS to override the upgrade URL's utm_medium with * `forms-overview`. * * @since 2.0.0 * * @param string $tooltip Hover title naming the locked metric (e.g. "Upgrade to Pro to unlock Interactions"). * * @return string */ private function render_pro_badge( string $tooltip ): string { return sprintf( '<a href="#" class="education-modal" title="%5$s" data-action="upgrade" data-name="%1$s" data-license="pro" data-banner-src="%2$s" data-utm-medium="forms-overview" data-utm-content="analytics-upgrade"><img src="%3$s" alt="%4$s" width="48" height="27"></a>', esc_attr__( 'Form Analytics', 'wpforms-lite' ), esc_url( WPFORMS_PLUGIN_URL . 'assets/images/education/analytics-preview.png' ), esc_url( WPFORMS_PLUGIN_URL . 'assets/images/education/blurred-value.svg' ), esc_attr__( 'Upgrade to Pro', 'wpforms-lite' ), esc_attr( $tooltip ) ); } /** * Render the Conversion column cell. * * @since 2.0.0 * * @param int $form_id Form ID. * * @return string */ private function render_conversion( int $form_id ): string { if ( ! wpforms()->is_pro() || ! ProAnalytics::is_allowed() ) { return $this->render_pro_badge( __( 'Upgrade to Pro to unlock Conversion Rate', 'wpforms-lite' ) ); } $value = $this->calculate_conversion_value( $this->stats[ $form_id ] ?? null ); if ( $value === null ) { return '—'; } return $this->build_analytics_link( $form_id, 'conversion', $value ); } /** * Compute the formatted conversion-rate string for a form's stats. * * @since 2.0.0 * * @param array|null $stats Pre-fetched stats for the form, or null when absent. * * @return string|null Formatted "NN.N%" value, or null when stats are missing or views are zero. */ private function calculate_conversion_value( ?array $stats ): ?string { if ( ! $stats ) { return null; } $views = (int) ( $stats['views'] ?? 0 ); $submissions = (int) ( $stats['submissions'] ?? 0 ); if ( $views <= 0 ) { return null; } $rate = round( ( $submissions / $views ) * 100, 1 ); return number_format_i18n( $rate, 1 ) . '%'; } /** * Build the Analytics-page link markup for a populated column cell (Pro). * * @since 2.0.0 * * @param int $form_id Form ID the link targets. * @param string $css_modifier Column suffix for the link CSS class. * @param string $value Display value (escaped on output). * * @return string Anchor markup pointing at the form's Analytics page. */ private function build_analytics_link( int $form_id, string $css_modifier, string $value ): string { return sprintf( '<a href="%1$s" class="wpforms-analytics-%2$s-link">%3$s</a>', esc_url( admin_url( 'admin.php?page=wpforms-analytics&form_id=' . $form_id ) ), esc_attr( $css_modifier ), esc_html( $value ) ); } /** * Render the Form Analytics feature-discovery tooltip on Forms Overview. * * @since 2.0.0 */ public function render_feature_tooltip(): void { if ( ! $this->should_render_feature_tooltip() ) { return; } $tooltip = wpforms()->obj( 'education_feature_tooltip' ); if ( ! $tooltip ) { return; } /** * Filter the Learn More URL for the Form Analytics feature-discovery tooltip. * * @since 2.0.0 * * @param string $url Default UTM-wrapped wpforms.com URL. */ $learn_more_url = apply_filters( 'wpforms_admin_forms_analytics_render_feature_tooltip_learn_more_url', wpforms_utm_link( AnalyticsFeature::DOC_URL, 'forms-overview', 'Analytics Feature Tooltip' ) ); $button = wpforms()->is_pro() && ProAnalytics::is_allowed() ? [ 'text' => esc_html__( 'View Analytics', 'wpforms-lite' ), 'url' => add_query_arg( 'page', 'wpforms-analytics', admin_url( 'admin.php' ) ), 'classes' => 'wpforms-btn wpforms-btn-sm wpforms-btn-orange wpforms-education-feature-tooltip-view', ] : [ 'text' => esc_html__( 'Upgrade to Pro', 'wpforms-lite' ), 'url' => '#', 'classes' => 'wpforms-btn wpforms-btn-sm wpforms-btn-orange education-modal', 'data' => [ 'name' => esc_attr__( 'Form Analytics', 'wpforms-lite' ), 'license' => 'pro', 'action' => 'upgrade', 'banner-src' => WPFORMS_PLUGIN_URL . 'assets/images/education/analytics-preview.png', 'utm-medium' => 'forms-overview', 'utm-content' => 'analytics-feature-tooltip', ], ]; $tooltip->render( [ 'title' => esc_html__( 'Form Analytics', 'wpforms-lite' ), 'badge' => esc_html__( 'NEW', 'wpforms-lite' ), 'text' => esc_html__( 'Monitor views, conversions, and field-level activity to optimize performance.', 'wpforms-lite' ), 'section' => 'analytics-feature-discovery', 'learn_more_url' => $learn_more_url, 'button' => $button, ] ); } /** * Whether the Form Analytics feature-discovery tooltip should render. * * Gates on the current admin screen and the per-user dismissal flag stored * in the `wpforms_dismissed` user meta map. * * @since 2.0.0 * * @return bool True when the tooltip should render on this request. */ private function should_render_feature_tooltip(): bool { $screen = get_current_screen(); $screen_id = $screen->id ?? ''; if ( ! $screen || $screen_id !== 'toplevel_page_wpforms-overview' ) { return false; } $dismissed = (array) get_user_meta( get_current_user_id(), 'wpforms_dismissed', true ); return empty( $dismissed['edu-analytics-feature-discovery'] ); } } Forms/Ajax/Tags.php 0000644 00000015254 15252506741 0010142 0 ustar 00 <?php namespace WPForms\Admin\Forms\Ajax; use WPForms_Form_Handler; /** * Tags AJAX actions on All Forms page. * * @since 1.7.5 */ class Tags { /** * Determine if the new tag was added during processing submitted tags. * * @since 1.7.5 * * @var bool */ private $is_new_tag_added; /** * Determine if the class is allowed to load. * * @since 1.7.5 * * @return bool */ private function allow_load() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $action = isset( $_REQUEST['action'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['action'] ) ) : ''; // Load only in the case of AJAX calls on Forms Overview page. return wp_doing_ajax() && strpos( $action, 'wpforms_admin_forms_overview_' ) === 0; } /** * Initialize class. * * @since 1.7.5 */ public function init() { if ( ! $this->allow_load() ) { return; } $this->hooks(); } /** * Hooks. * * @since 1.7.5 */ private function hooks() { add_action( 'wp_ajax_wpforms_admin_forms_overview_save_tags', [ $this, 'save_tags' ] ); add_action( 'wp_ajax_wpforms_admin_forms_overview_delete_tags', [ $this, 'delete_tags' ] ); } /** * Save tags. * * @since 1.7.5 */ public function save_tags() { $data = $this->get_prepared_data( 'save' ); $tags_ids = $this->get_processed_tags( $data['tags'] ); $tags_labels = wp_list_pluck( $data['tags'], 'label' ); // Set tags to each form. $this->set_tags_to_forms( $data['forms'], $tags_ids, $tags_labels ); $tags_obj = wpforms()->obj( 'forms_tags' ); $terms = get_the_terms( array_pop( $data['forms'] ), WPForms_Form_Handler::TAGS_TAXONOMY ); $tags_data = $tags_obj->get_tags_data( $terms ); if ( ! empty( $this->is_new_tag_added ) ) { $tags_data['all_tags_choices'] = $tags_obj->get_all_tags_choices(); } wp_send_json_success( $tags_data ); } /** * Delete tags. * * @since 1.7.5 */ public function delete_tags(): void { $form_obj = wpforms()->obj( 'form' ); $data = $this->get_prepared_data( 'delete' ); $deleted = 0; $labels = []; foreach ( $data['tags'] as $tag_id ) { $term = get_term_by( 'term_id', $tag_id, WPForms_Form_Handler::TAGS_TAXONOMY, ARRAY_A ); $labels[] = $term['name']; // Delete tag (term). if ( wp_delete_term( $tag_id, WPForms_Form_Handler::TAGS_TAXONOMY ) === true ) { ++$deleted; } } // Get forms marked by the tags. $args = [ 'fields' => 'ids', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query 'tax_query' => [ [ 'taxonomy' => WPForms_Form_Handler::TAGS_TAXONOMY, 'field' => 'term_id', 'terms' => array_map( 'absint', $data['tags'] ), ], ], ]; $forms = $form_obj ? (array) $form_obj->get( 0, $args ) : []; // Remove tags from the settings of the forms. foreach ( $forms as $form_id ) { $form_data = $form_obj->get( $form_id, [ 'content_only' => true ] ); if ( empty( $form_data['settings']['form_tags'] ) || ! is_array( $form_data['settings']['form_tags'] ) ) { continue; } $form_data['settings']['form_tags'] = array_diff( $form_data['settings']['form_tags'], $labels ); $form_obj->update( $form_id, $form_data ); } wp_send_json_success( [ 'deleted' => $deleted ] ); } /** * Get processed tags. * * @since 1.7.5 * * @param array $tags_data Submitted tags data. * * @return array Tags IDs list. */ public function get_processed_tags( $tags_data ) { if ( ! is_array( $tags_data ) ) { return []; } $tags_ids = []; // Process the tags' data. foreach ( $tags_data as $tag ) { $term = get_term( $tag['value'], WPForms_Form_Handler::TAGS_TAXONOMY ); // In the case when the term is not found, we should create the new term. if ( empty( $term ) || is_wp_error( $term ) ) { $new_term = wp_insert_term( sanitize_text_field( $tag['label'] ), WPForms_Form_Handler::TAGS_TAXONOMY ); $tag['value'] = ! is_wp_error( $new_term ) && isset( $new_term['term_id'] ) ? $new_term['term_id'] : 0; $this->is_new_tag_added = $this->is_new_tag_added || $tag['value'] > 0; } if ( ! empty( $tag['value'] ) ) { $tags_ids[] = absint( $tag['value'] ); } } return $tags_ids; } /** * Get prepared data before perform ajax action. * * @since 1.7.5 * * @param string $action Action: `save` OR `delete`. * * @return array */ private function get_prepared_data( string $action ): array { // Run a security check. if ( ! check_ajax_referer( 'wpforms-admin-forms-overview-nonce', 'nonce', false ) ) { wp_send_json_error( esc_html__( 'Most likely, your session expired. Please reload the page.', 'wpforms-lite' ) ); } // Check for permissions. if ( ! wpforms_current_user_can( 'edit_others_forms' ) ) { wp_send_json_error( esc_html__( 'You are not allowed to perform this action.', 'wpforms-lite' ) ); } $data = [ // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized 'tags' => ! empty( $_POST['tags'] ) ? map_deep( (array) wp_unslash( $_POST['tags'] ), 'sanitize_text_field' ) : [], ]; if ( $action === 'save' ) { $data['forms'] = $this->get_allowed_forms(); } return $data; } /** * Get allowed forms. * * @since 1.7.5 * * @return array Allowed form IDs. */ private function get_allowed_forms() { // phpcs:disable WordPress.Security.NonceVerification.Missing if ( empty( $_POST['forms'] ) ) { wp_send_json_error( esc_html__( 'No forms selected when trying to add a tag to them.', 'wpforms-lite' ) ); } $forms_all = array_filter( array_map( 'absint', (array) $_POST['forms'] ) ); $forms_allowed = []; // phpcs:enable WordPress.Security.NonceVerification.Missing foreach ( $forms_all as $form_id ) { if ( wpforms_current_user_can( 'edit_form_single', $form_id ) ) { $forms_allowed[] = $form_id; } } if ( empty( $forms_allowed ) ) { wp_send_json_error( esc_html__( 'You are not allowed to perform this action.', 'wpforms-lite' ) ); } return $forms_allowed; } /** * Set tags to each form in the list. * * @since 1.7.5 * * @param array $forms_ids Forms IDs list. * @param array $tags_ids Tags IDs list. * @param array $tags_labels Tags labels list. */ private function set_tags_to_forms( $forms_ids, $tags_ids, $tags_labels ) { $form_obj = wpforms()->obj( 'form' ); foreach ( $forms_ids as $form_id ) { wp_set_post_terms( $form_id, $tags_ids, WPForms_Form_Handler::TAGS_TAXONOMY ); // Store tags labels in the form settings. $form_data = $form_obj->get( $form_id, [ 'content_only' => true ] ); $form_data['settings']['form_tags'] = $tags_labels; $form_obj->update( $form_id, $form_data ); } } } Forms/Ajax/Columns.php 0000644 00000004261 15252506741 0010660 0 ustar 00 <?php namespace WPForms\Admin\Forms\Ajax; use WPForms\Admin\Forms\Table\Facades; /** * Columns AJAX actions on Forms Overview list page. * * @since 1.8.6 */ class Columns { /** * Determine if the class is allowed to load. * * @since 1.8.6 * * @return bool */ private function allow_load(): bool { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $action = isset( $_REQUEST['action'] ) ? sanitize_key( wp_unslash( $_REQUEST['action'] ) ) : ''; // Load only in the case of AJAX calls on Forms Overview page. return wpforms_is_admin_ajax() && strpos( $action, 'wpforms_admin_forms_overview_' ) === 0; } /** * Initialize class. * * @since 1.8.6 */ public function init(): void { if ( ! $this->allow_load() ) { return; } $this->hooks(); } /** * Hooks. * * @since 1.8.6 */ private function hooks(): void { add_action( 'wp_ajax_wpforms_admin_forms_overview_save_columns_order', [ $this, 'save_order' ] ); } /** * Save columns' order. * * @since 1.8.6 */ public function save_order(): void { check_ajax_referer( 'wpforms-admin', 'nonce' ); if ( ! wpforms_current_user_can( 'view_forms' ) ) { wp_send_json_error( esc_html__( 'You do not have permission to perform this action.', 'wpforms-lite' ) ); } $data = $this->get_prepared_data(); // Prepare the new columns' order. $columns = []; foreach ( $data['columns'] as $column ) { $columns[] = str_replace( '-foot', '', $column ); } $result = Facades\Columns::sanitize_and_save_columns( $columns ); if ( $result === false ) { wp_send_json_error( esc_html__( 'Cannot save columns order.', 'wpforms-lite' ) ); } wp_send_json_success(); } /** * Get prepared data before perform ajax action. * * @since 1.8.6 * * @return array */ private function get_prepared_data(): array { // Run a security check. if ( ! check_ajax_referer( 'wpforms-admin', 'nonce', false ) ) { wp_send_json_error( esc_html__( 'Most likely, your session expired. Please reload the page.', 'wpforms-lite' ) ); } return [ 'columns' => ! empty( $_POST['columns'] ) ? map_deep( (array) wp_unslash( $_POST['columns'] ), 'sanitize_key' ) : [], ]; } } Forms/Tags.php 0000644 00000036145 15252506741 0007261 0 ustar 00 <?php namespace WPForms\Admin\Forms; use WP_Post; use WPForms_Form_Handler; /** * Tags on All Forms page. * * @since 1.7.5 */ class Tags { /** * Current tags filter. * * @since 1.7.5 * * @var array */ private $tags_filter; /** * Current view slug. * * @since 1.7.5 * * @var string */ private $current_view; /** * Base URL. * * @since 1.7.5 * * @var string */ private $base_url; /** * Determine if the class is allowed to load. * * @since 1.7.5 * * @return bool */ private function allow_load() { // Load only on the `All Forms` admin page. return wpforms_is_admin_page( 'overview' ); } /** * Initialize class. * * @since 1.7.5 */ public function init() { // In case of AJAX call we need to initialize base URL only. if ( wp_doing_ajax() ) { $this->base_url = admin_url( 'admin.php?page=wpforms-overview' ); } if ( ! $this->allow_load() ) { return; } $this->update_view_vars(); $this->hooks(); } /** * Hooks. * * @since 1.7.5 */ private function hooks() { add_action( 'init', [ $this, 'update_tags_filter' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueues' ] ); add_action( 'wpforms_admin_overview_before_rows', [ $this, 'bulk_edit_tags' ] ); add_filter( 'wpforms_get_multiple_forms_args', [ $this, 'get_forms_args' ] ); add_filter( 'wpforms_admin_forms_bulk_actions_get_dropdown_items', [ $this, 'add_bulk_action' ], 10, 2 ); add_filter( 'wpforms_overview_table_columns', [ $this, 'filter_columns' ] ); } /** * Init view-related variables. * * @since 1.7.5 */ private function update_view_vars() { $views_object = wpforms()->obj( 'forms_views' ); $this->current_view = $views_object->get_current_view(); $view_config = $views_object->get_view_by_slug( $this->current_view ); $this->base_url = remove_query_arg( [ 'tags', 'search', 'action', 'action2', '_wpnonce', 'form_id', 'paged', '_wp_http_referer' ], $views_object->get_base_url() ); // Base URL should contain variable according to the current view. if ( isset( $view_config['get_var'], $view_config['get_var_value'] ) && $this->current_view !== 'all' ) { $this->base_url = add_query_arg( $view_config['get_var'], $view_config['get_var_value'], $this->base_url ); } // Base URL fallback. $this->base_url = empty( $this->base_url ) ? admin_url( 'admin.php?page=wpforms-overview' ) : $this->base_url; } /** * Update tags filter value. * * @since 1.7.5 */ public function update_tags_filter() { // Do not need to update this property while doing AJAX. if ( wp_doing_ajax() ) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash $tags = isset( $_GET['tags'] ) ? sanitize_text_field( wp_unslash( rawurldecode( $_GET['tags'] ) ) ) : ''; $tags_slugs = explode( ',', $tags ); $tags_filter = array_filter( self::get_all_tags_choices(), static function( $tag ) use ( $tags_slugs ) { return in_array( trim( rawurldecode( $tag['slug'] ) ), $tags_slugs, true ); } ); $this->tags_filter = array_map( 'absint', wp_list_pluck( $tags_filter, 'value' ) ); } /** * Enqueue assets. * * @since 1.7.5 */ public function enqueues() { wp_enqueue_script( 'wpforms-admin-forms-overview-choicesjs', WPFORMS_PLUGIN_URL . 'assets/lib/choices.min.js', [], '10.2.0', true ); wp_localize_script( 'wpforms-admin-forms-overview-choicesjs', 'wpforms_admin_forms_overview', [ 'choicesjs_config' => self::get_choicesjs_config(), 'edit_tags_form' => $this->get_column_tags_form(), 'all_tags_choices' => self::get_all_tags_choices(), 'strings' => $this->get_localize_strings(), ] ); } /** * Get Choices.js configuration. * * @since 1.7.5 */ public static function get_choicesjs_config() { return [ 'removeItemButton' => true, 'shouldSort' => false, 'loadingText' => esc_html__( 'Loading...', 'wpforms-lite' ), 'noResultsText' => esc_html__( 'No results found', 'wpforms-lite' ), 'noChoicesText' => esc_html__( 'No tags to choose from', 'wpforms-lite' ), 'searchEnabled' => true, 'searchChoices' => true, 'searchFloor' => 1, 'searchResultLimit' => 100, 'searchFields' => [ 'label' ], 'allowHTML' => true, // These `fuseOptions` options enable the search of chars not only from the beginning of the tags. 'fuseOptions' => [ 'threshold' => 0.1, 'distance' => 1000, 'location' => 2, ], ]; } /** * Get all tags (terms) as items for Choices.js. * * @since 1.7.5 * * @return array */ public static function get_all_tags_choices() { static $choices = null; if ( is_array( $choices ) ) { return $choices; } $choices = []; $tags = get_terms( [ 'taxonomy' => WPForms_Form_Handler::TAGS_TAXONOMY, 'hide_empty' => false, ] ); foreach ( $tags as $tag ) { $choices[] = [ 'value' => (string) $tag->term_id, 'slug' => $tag->slug, 'label' => sanitize_term_field( 'name', $tag->name, $tag->term_id, WPForms_Form_Handler::TAGS_TAXONOMY, 'display' ), 'count' => (int) $tag->count, ]; } return $choices; } /** * Determine if the Tags column is hidden. * * @since 1.7.5 * * @return bool */ private function is_tags_column_hidden() { $overview_table = ListTable::get_instance(); $columns = $overview_table->__call( 'get_column_info', [] ); return isset( $columns[1] ) && in_array( 'tags', $columns[1], true ); } /** * Get localize strings. * * @since 1.7.5 */ private function get_localize_strings() { return [ 'nonce' => wp_create_nonce( 'wpforms-admin-forms-overview-nonce' ), 'is_tags_column_hidden' => $this->is_tags_column_hidden(), 'base_url' => admin_url( 'admin.php?' . wp_parse_url( $this->base_url, PHP_URL_QUERY ) ), 'add_new_tag' => esc_html__( 'Press Enter or "," key to add new tag', 'wpforms-lite' ), 'error' => esc_html__( 'Something wrong. Please try again later.', 'wpforms-lite' ), 'all_tags' => esc_html__( 'All Tags', 'wpforms-lite' ), 'bulk_edit_one_form' => wp_kses( __( '<strong>1 form</strong> selected for Bulk Edit.', 'wpforms-lite' ), [ 'strong' => [] ] ), 'bulk_edit_n_forms' => wp_kses( /* translators: %d - number of forms selected for Bulk Edit. */ __( '<strong>%d forms</strong> selected for Bulk Edit.', 'wpforms-lite' ), [ 'strong' => [] ] ), 'manage_tags_title' => esc_html__( 'Manage Tags', 'wpforms-lite' ), 'manage_tags_desc' => esc_html__( 'Delete tags that you\'re no longer using. Deleting a tag will remove it from a form, but will not delete the form itself.', 'wpforms-lite' ), 'manage_tags_save' => esc_html__( 'Delete Tags', 'wpforms-lite' ), 'manage_tags_one_tag' => wp_kses( __( 'You have <strong>1 tag</strong> selected for deletion.', 'wpforms-lite' ), [ 'strong' => [] ] ), 'manage_tags_n_tags' => wp_kses( /* translators: %d - number of forms selected for Bulk Edit. */ __( 'You have <strong>%d tags</strong> selected for deletion.', 'wpforms-lite' ), [ 'strong' => [] ] ), 'manage_tags_no_tags' => wp_kses( __( 'There are no tags to delete.<br>Please create at least one by adding it to any form.', 'wpforms-lite' ), [ 'br' => [] ] ), 'manage_tags_one_deleted' => esc_html__( '1 tag was successfully deleted.', 'wpforms-lite' ), /* translators: %d - number of deleted tags. */ 'manage_tags_n_deleted' => esc_html__( '%d tags were successfully deleted.', 'wpforms-lite' ), 'manage_tags_result_title' => esc_html__( 'Almost done!', 'wpforms-lite' ), 'manage_tags_result_text' => esc_html__( 'In order to update the tags in the forms list, please refresh the page.', 'wpforms-lite' ), 'manage_tags_btn_refresh' => esc_html__( 'Refresh', 'wpforms-lite' ), ]; } /** * Determine if tags are editable. * * @since 1.7.5 * * @param int|null $form_id Form ID. * * @return bool */ private function is_editable( $form_id = null ) { if ( $this->current_view === 'trash' ) { return false; } if ( ! empty( $form_id ) && ! wpforms_current_user_can( 'edit_form_single', $form_id ) ) { return false; } if ( empty( $form_id ) && ! wpforms_current_user_can( 'edit_forms' ) ) { return false; } return true; } /** * Generate Tags column markup. * * @since 1.7.5 * * @param WP_Post $form Form. * * @return string */ public function column_tags( $form ) { $terms = get_the_terms( $form->ID, WPForms_Form_Handler::TAGS_TAXONOMY ); $data = $this->get_tags_data( $terms ); return $this->get_column_tags_links( $data['tags_links'], $data['tags_ids'], $form->ID ) . $this->get_column_tags_form( $data['tags_options'] ); } /** * Generate tags data. * * @since 1.7.5 * * @param array $terms Tags terms. * * @return array */ public function get_tags_data( $terms ) { if ( ! is_array( $terms ) ) { $taxonomy_object = get_taxonomy( WPForms_Form_Handler::TAGS_TAXONOMY ); return [ 'tags_links' => sprintf( '<span aria-hidden="true">—</span><span class="screen-reader-text">%s</span>', esc_html( isset( $taxonomy_object->labels->no_terms ) ? $taxonomy_object->labels->no_terms : '—' ) ), 'tags_ids' => '', 'tags_options' => '', ]; } $tags_links = []; $tags_ids = []; $tags_options = []; $terms = empty( $terms ) ? [] : (array) $terms; foreach ( $terms as $tag ) { $filter_url = add_query_arg( 'tags', rawurlencode( $tag->slug ), $this->base_url ); $tags_links[] = sprintf( '<a href="%1$s">%2$s</a>', esc_url( $filter_url ), esc_html( $tag->name ) ); $tags_ids[] = $tag->term_id; $tags_options[] = sprintf( '<option value="%1$s" selected>%2$s</option>', esc_attr( $tag->term_id ), esc_html( $tag->name ) ); } return [ /* translators: used between list items, there is a space after the comma. */ 'tags_links' => implode( __( ', ', 'wpforms-lite' ), $tags_links ), 'tags_ids' => implode( ',', array_filter( $tags_ids ) ), 'tags_options' => implode( '', $tags_options ), ]; } /** * Get form tags links list markup. * * @since 1.7.5 * * @param string $tags_links Tags links. * @param string $tags_ids Tags IDs. * @param int $form_id Form ID. * * @return string */ private function get_column_tags_links( $tags_links = '', $tags_ids = '', $form_id = 0 ) { $edit_link = ''; if ( $this->is_editable( $form_id ) ) { $edit_link = sprintf( '<a href="#" class="wpforms-column-tags-edit">%s</a>', esc_html__( 'Edit', 'wpforms-lite' ) ); } return sprintf( '<div class="wpforms-column-tags-links" data-form-id="%1$d" data-is-editable="%2$s" data-tags="%3$s"> <div class="wpforms-column-tags-links-list">%4$s</div> %5$s </div>', absint( $form_id ), $this->is_editable( $form_id ) ? '1' : '0', esc_attr( $tags_ids ), $tags_links, $edit_link ); } /** * Get edit tags form markup in the Tags column. * * @since 1.7.5 * * @param string $tags_options Tags options. * * @return string */ private function get_column_tags_form( $tags_options = '' ) { return sprintf( '<div class="wpforms-column-tags-form wpforms-hidden"> <select multiple>%1$s</select> <i class="dashicons dashicons-dismiss wpforms-column-tags-edit-cancel" title="%2$s"></i> <i class="dashicons dashicons-yes-alt wpforms-column-tags-edit-save" title="%3$s"></i> <i class="wpforms-spinner spinner wpforms-hidden"></i> </div>', $tags_options, esc_attr__( 'Cancel', 'wpforms-lite' ), esc_attr__( 'Save changes', 'wpforms-lite' ) ); } /** * Extra controls to be displayed between bulk actions and pagination. * * @since 1.7.5 * * @param string $which The location of the table navigation: 'top' or 'bottom'. * @param ListTable $overview_table Instance of the ListTable class. */ public function extra_tablenav( $which, $overview_table ) { if ( $this->current_view === 'trash' ) { return; } $all_tags = self::get_all_tags_choices(); $is_column_hidden = $this->is_tags_column_hidden(); $is_hidden = $is_column_hidden || empty( $all_tags ); $tags_options = ''; if ( $this->is_filtered() ) { $tags = get_terms( [ 'taxonomy' => WPForms_Form_Handler::TAGS_TAXONOMY, 'hide_empty' => false, 'include' => $this->tags_filter, ] ); foreach ( $tags as $tag ) { $tags_options .= sprintf( '<option value="%1$s" selected>%2$s</option>', esc_attr( $tag->term_id ), esc_html( $tag->name ) ); } } printf( '<div class="wpforms-tags-filter %1$s"> <select multiple size="1" data-tags-filter="1"> <option placeholder>%2$s</option> %3$s </select> <button type="button" class="button">%4$s</button> </div> <button type="button" class="button wpforms-manage-tags %1$s">%5$s</button>', esc_attr( $is_hidden ? 'wpforms-hidden' : '' ), esc_html( empty( $tags_options ) ? __( 'All Tags', 'wpforms-lite' ) : '' ), wp_kses( $tags_options, [ 'option' => [ 'value' => [], 'selected' => [], ], ] ), esc_attr__( 'Filter', 'wpforms-lite' ), esc_attr__( 'Manage Tags', 'wpforms-lite' ) ); } /** * Render and display Bulk Edit Tags form. * * @since 1.7.5 * * @param ListTable $list_table Overview list table object. */ public function bulk_edit_tags( $list_table ) { $columns = $list_table->get_columns(); echo wpforms_render( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 'admin/forms/bulk-edit-tags', [ 'columns' => count( $columns ), ], true ); } /** * Determine whether a filtering is performing. * * @since 1.7.5 * * @return bool */ private function is_filtered() { return ! empty( $this->tags_filter ); } /** * Pass the tags to the arguments array. * * @since 1.7.5 * * @param array $args Get posts arguments. * * @return array */ public function get_forms_args( $args ) { if ( $args['post_status'] === 'trash' || ! $this->is_filtered() ) { return $args; } // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query $args['tax_query'] = [ [ 'taxonomy' => WPForms_Form_Handler::TAGS_TAXONOMY, 'field' => 'term_id', 'terms' => $this->tags_filter, ], ]; return $args; } /** * Add item to Bulk Actions dropdown. * * @since 1.7.5 * * @param array $items Dropdown items. * * @return array */ public function add_bulk_action( $items ) { if ( $this->is_editable() ) { $items['edit_tags'] = esc_html__( 'Edit Tags', 'wpforms-lite' ); } return $items; } /** * Filter list table columns. * * @since 1.7.5 * * @param string[] $columns Array of columns. * * @return array */ public function filter_columns( $columns ) { if ( $this->current_view === 'trash' ) { unset( $columns['tags'] ); } return $columns; } } Forms/Table/Facades/Columns.php 0000644 00000020306 15252506741 0012350 0 ustar 00 <?php namespace WPForms\Admin\Forms\Table\Facades; use WPForms\Admin\Base\Tables\Facades\ColumnsBase; use WPForms\Admin\Forms\Table\DataObjects\Column; use WPForms\Analytics\Analytics; use WPForms\Integrations\LiteConnect\LiteConnect; /** * Column facade class. * * Hides the complexity of columns' collection behind a simple interface. * * @since 1.8.6 */ class Columns extends ColumnsBase { /** * Saved columns order user meta name. * * @since 1.8.6 */ const COLUMNS_USER_META_NAME = 'wpforms_overview_table_columns'; /** * Legacy saved columns order user meta name. * * @since 1.8.6 */ const LEGACY_COLUMNS_USER_META_NAME = 'managetoplevel_page_wpforms-overviewcolumnshidden'; /** * Get columns. * * Returns all possible columns for the Forms table. * * @since 1.8.6 * * @return Column[] Array of columns as objects. */ protected static function get_all(): array { static $columns = null; if ( ! $columns ) { $columns = self::get_columns(); } return $columns; } /** * Get forms' list table columns. * * @since 1.8.6 * * @return Column[] Array of columns as objects. */ public static function get_columns(): array { $columns_data = [ 'id' => [ 'label' => esc_html__( 'ID', 'wpforms-lite' ), ], 'name' => [ 'label' => esc_html__( 'Name', 'wpforms-lite' ), 'readonly' => true, ], 'tags' => [ 'label' => esc_html__( 'Tags', 'wpforms-lite' ), ], 'author' => [ 'label' => esc_html__( 'Author', 'wpforms-lite' ), ], 'shortcode' => [ 'label' => esc_html__( 'Shortcode', 'wpforms-lite' ), ], 'created' => [ 'label' => esc_html__( 'Date', 'wpforms-lite' ), ], 'entries' => [ 'label' => esc_html__( 'Entries', 'wpforms-lite' ), ], ]; // In Lite, we should not show Entries column if Lite Connect is not enabled. if ( ! wpforms()->is_pro() && ! ( LiteConnect::is_allowed() && LiteConnect::is_enabled() ) ) { unset( $columns_data['entries'] ); } /** * Filters the forms overview table columns data. * * @since 1.8.6 * * @param array[] $columns Columns data. */ $columns_data = apply_filters( 'wpforms_admin_forms_table_facades_columns_data', $columns_data ); $columns_data = self::set_columns_data_defaults( $columns_data ); $columns = []; foreach ( $columns_data as $id => $column ) { $columns[ $id ] = new Column( $id, $column ); } return $columns; } /** * Get columns' keys for the columns which user selected to be displayed. * * It returns an array of keys in the order they should be displayed. * It returns draggable and non-draggable columns. * * @since 1.8.6 * * @return array */ public static function get_selected_columns_keys(): array { $user_id = get_current_user_id(); $user_meta_columns = get_user_meta( $user_id, self::COLUMNS_USER_META_NAME, true ); $user_meta_legacy_columns_hidden = get_user_meta( $user_id, self::LEGACY_COLUMNS_USER_META_NAME, true ); $user_meta_columns = is_array( $user_meta_columns ) ? $user_meta_columns : []; $user_meta_legacy_columns_hidden = is_array( $user_meta_legacy_columns_hidden ) ? $user_meta_legacy_columns_hidden : []; $user_meta_columns_is_empty = empty( $user_meta_columns ); // Make the ID column hidden by default for users who have no saved selection yet. if ( $user_meta_columns_is_empty ) { $user_meta_legacy_columns_hidden[] = 'id'; } // The Shortcode column is also default-hidden when Form Analytics is active so // the analytics columns sit closer to Entries without crowding the row. if ( $user_meta_columns_is_empty && Analytics::is_enabled() ) { $user_meta_legacy_columns_hidden[] = 'shortcode'; } // Always include readonly columns. $user_meta_columns = array_unique( array_merge( $user_meta_columns, self::get_readonly_columns_keys() ) ); if ( ! empty( $user_meta_columns ) && empty( $user_meta_legacy_columns_hidden ) ) { return $user_meta_columns; } // If custom order is not saved, let's check if there is a legacy user meta-option. // It is a kind of migration from legacy user meta-option to the new one. $user_meta_columns = array_diff( array_keys( self::get_all() ), $user_meta_legacy_columns_hidden ); // Update user meta option. if ( update_user_meta( $user_id, self::COLUMNS_USER_META_NAME, $user_meta_columns ) ) { // Remove legacy user meta-option. delete_user_meta( $user_id, self::LEGACY_COLUMNS_USER_META_NAME ); } return $user_meta_columns; } /** * Get draggable columns ordered keys. * * It will return custom order if user has already saved it, otherwise it will return default order. * * @since 1.8.6 * * @return array */ private static function get_draggable_ordered_keys(): array { // First, let's check if user has already saved custom order. $custom_order = self::get_selected_columns_keys(); $all_columns = self::get_all(); if ( $custom_order ) { // If a user has saved custom order, let's filter out columns which are not draggable. return array_filter( $custom_order, static function ( $column ) use ( $all_columns ) { return isset( $all_columns[ $column ] ) && $all_columns[ $column ]->is_draggable(); } ); } // If a user has not saved custom order, let's use the default order. $draggable = array_filter( $all_columns, static function ( $column ) { return $column->is_draggable(); } ); return array_keys( $draggable ); } /** * Save columns keys array into user meta. * * @since 1.8.6 * * @param array $columns_keys Array of columns keys in desired display order. * * @return bool */ public static function sanitize_and_save_columns( array $columns_keys ): bool { $columns_keys = array_map( [ __CLASS__, 'sanitize_column_key' ], $columns_keys ); $columns_keys = array_filter( $columns_keys, [ __CLASS__, 'validate_column_key' ] ); // Add readonly columns. $columns_keys = array_unique( array_merge( $columns_keys, self::get_readonly_columns_keys() ) ); $user_id = get_current_user_id(); $user_meta_columns = get_user_meta( $user_id, self::COLUMNS_USER_META_NAME, true ); // If user has already saved custom order, let's check if it has been changed. if ( $user_meta_columns === $columns_keys ) { return true; } // Update user meta option. return update_user_meta( $user_id, self::COLUMNS_USER_META_NAME, $columns_keys ); } /** * Sanitize column key. * * @since 1.8.6 * * @param string $key Column key. * * @return string */ public static function sanitize_column_key( string $key ): string { return sanitize_key( $key ); } /** * Get columns' data ready to use in the list table object. * * @since 1.8.6 * * @return array */ public static function get_list_table_columns(): array { $columns = [ 'cb' => '<input type="checkbox" />', ]; $order = self::get_draggable_ordered_keys(); $all_columns = self::get_all(); foreach ( $order as $column_id ) { $columns[ $column_id ] = $all_columns[ $column_id ]->get_label_html(); } /** * Filters the forms overview table columns. * * @since 1.0.0 * * @param array $columns Columns data. */ $columns = apply_filters( 'wpforms_overview_table_columns', $columns ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName // Add empty column for the cog icon. $columns['cog'] = ''; return $columns; } /** * Get readonly columns keys. * * @since 1.8.6 * * @return array */ private static function get_readonly_columns_keys(): array { $readonly = array_filter( self::get_all(), static function ( $column ) { return $column->is_readonly(); } ); return array_keys( $readonly ); } /** * Set columns data defaults. * * @since 1.8.6 * * @param array $columns_data Columns data. * * @return array */ private static function set_columns_data_defaults( array $columns_data ): array { return array_map( static function ( $column ) { $column['type'] = $column['type'] ?? ''; $column['draggable'] = $column['draggable'] ?? true; $column['label_html'] = $column['label_html'] ?? ''; $column['readonly'] = $column['readonly'] ?? false; return $column; }, $columns_data ); } } Forms/Table/DataObjects/Column.php 0000644 00000000305 15252506741 0013017 0 ustar 00 <?php namespace WPForms\Admin\Forms\Table\DataObjects; use WPForms\Admin\Base\Tables\DataObjects\ColumnBase; /** * Column data object. * * @since 1.8.6 */ class Column extends ColumnBase {} Forms/Search.php 0000644 00000015000 15252506741 0007553 0 ustar 00 <?php namespace WPForms\Admin\Forms; /** * Search Forms feature. * * @since 1.7.2 */ class Search { /** * Current search term. * * @since 1.7.2 * * @var string */ private $term; /** * Current search term escaped. * * @since 1.7.2 * * @var string */ private $term_escaped; /** * Determine if the class is allowed to load. * * @since 1.7.2 * * @return bool */ private function allow_load() { // Load only on the `All Forms` admin page and only if the search should be performed. return wpforms_is_admin_page( 'overview' ) && $this->is_search(); } /** * Initialize class. * * @since 1.7.2 */ public function init() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $this->term = isset( $_GET['search']['term'] ) ? sanitize_text_field( wp_unslash( $_GET['search']['term'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $this->term_escaped = isset( $_GET['search']['term'] ) ? esc_html( wp_unslash( $_GET['search']['term'] ) ) : ''; if ( ! $this->allow_load() ) { return; } $this->hooks(); } /** * Hooks. * * @since 1.7.2 */ private function hooks() { // Use filter to add the search term to the get forms arguments. add_filter( 'wpforms_get_multiple_forms_args', [ $this, 'get_forms_args' ] ); // Encapsulate search into posts_where. add_action( 'wpforms_form_handler_get_multiple_before_get_posts', [ $this, 'before_get_posts' ] ); add_action( 'wpforms_form_handler_get_multiple_after_get_posts', [ $this, 'after_get_posts' ], 10, 2 ); } /** * Determine whether a search is performing. * * @since 1.7.2 * * @return bool */ private function is_search() { return ! wpforms_is_empty_string( $this->term_escaped ); } /** * Pass the search term to the arguments array. * * @since 1.7.2 * * @param array $args Get posts arguments. * * @return array */ public function get_forms_args( $args ) { if ( is_numeric( $this->term ) ) { $args['post__in'] = [ absint( $this->term ) ]; } else { $args['search']['term'] = $this->term; $args['search']['term_escaped'] = $this->term_escaped; } return $args; } /** * Before get_posts() call routine. * * @since 1.7.2 * * @param array $args Arguments of the `get_posts()`. */ public function before_get_posts( $args ) { // The `posts_where` hook is very general and has broad usage across the WP core and tons of plugins. // Therefore, in order to do not break something, // we should add this hook right before the call of `get_posts()` inside \WPForms_Form_Handler::get_multiple(). add_filter( 'posts_where', [ $this, 'search_by_term_where' ], 10, 2 ); } /** * After get_posts() call routine. * * @since 1.7.2 * * @param array $args Arguments of the get_posts(). * @param array $forms Forms data. Result of getting multiple forms. */ public function after_get_posts( $args, $forms ) { // The `posts_where` hook is very general and has broad usage across the WP core and tons of plugins. // Therefore, in order to do not break something, // we should remove this hook right after the call of `get_posts()` inside \WPForms_Form_Handler::get_multiple(). remove_filter( 'posts_where', [ $this, 'search_by_term_where' ] ); } /** * Modify the WHERE clause of the SQL query in order to search forms by given term. * * @since 1.7.2 * * @param string $where WHERE clause. * @param \WP_Query $wp_query The WP_Query instance. * * @return string */ public function search_by_term_where( $where, $wp_query ) { if ( is_numeric( $this->term ) ) { return $where; } global $wpdb; // When user types only HTML tag (<section> for example), the sanitized term we will be empty. // In this case, it's better to return an empty result set than all the forms. It's not the same as the empty search term. if ( wpforms_is_empty_string( $this->term ) && ! wpforms_is_empty_string( $this->term_escaped ) ) { $where .= ' AND 1<>1'; } if ( wpforms_is_empty_string( $this->term ) ) { return $where; } // Prepare the WHERE clause to search form title and description. $where .= $wpdb->prepare( " AND ( $wpdb->posts.post_title LIKE %s OR $wpdb->posts.post_excerpt LIKE %s )", '%' . $wpdb->esc_like( esc_html( $this->term ) ) . '%', '%' . $wpdb->esc_like( $this->term ) . '%' ); return $where; } /** * Forms search markup. * * @since 1.7.2 * * @param string $text The 'submit' button label. * @param string $input_id ID attribute value for the search input field. */ public function search_box( $text, $input_id ) { $search_term = wpforms_is_empty_string( $this->term ) ? $this->term_escaped : $this->term; // Display search reset block. $this->search_reset_block( $search_term ); // Display search box. // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wpforms_render( 'admin/forms/search-box', [ 'term_input_id' => $input_id . '-term', 'text' => $text, 'search_term' => $search_term, ], true ); } /** * Forms search reset block. * * @since 1.7.2 * * @param string $search_term Current search term. */ private function search_reset_block( $search_term ) { if ( wpforms_is_empty_string( $search_term ) ) { return; } $views = wpforms()->obj( 'forms_views' ); $count = $views->get_count(); $view = $views->get_current_view(); $count['all'] = ! empty( $count['all'] ) ? $count['all'] : 0; $message = sprintf( wp_kses( /* translators: %1$d - number of forms found, %2$s - search term. */ _n( 'Found <strong>%1$d form</strong> containing <em>"%2$s"</em>', 'Found <strong>%1$d forms</strong> containing <em>"%2$s"</em>', (int) $count['all'], 'wpforms-lite' ), [ 'strong' => [], 'em' => [], ] ), (int) $count['all'], esc_html( $search_term ) ); /** * Filters the message in the search reset block. * * @since 1.7.3 * * @param string $message Message text. * @param string $search_term Search term. * @param array $count Count forms in different views. * @param string $view Current view. */ $message = apply_filters( 'wpforms_admin_forms_search_search_reset_block_message', $message, $search_term, $count, $view ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wpforms_render( 'admin/forms/search-reset', [ 'message' => $message, ], true ); } } Forms/Page.php 0000644 00000022057 15252506741 0007234 0 ustar 00 <?php namespace WPForms\Admin\Forms; use WPForms\Admin\Forms\Table\Facades\Columns; use WPForms\Admin\Traits\HasScreenOptions; /** * Primary overview page inside the admin which lists all forms. * * @since 1.8.6 */ class Page { use HasScreenOptions; /** * Overview Table instance. * * @since 1.8.6 * * @var ListTable */ private $overview_table; /** * Primary class constructor. * * @since 1.8.6 */ public function __construct() { $this->screen_options_id = 'wpforms_forms_overview_screen_options'; $this->screen_options = [ 'pagination' => [ 'heading' => esc_html__( 'Pagination', 'wpforms-lite' ), 'options' => [ [ 'label' => esc_html__( 'Number of forms per page:', 'wpforms-lite' ), 'option' => 'per_page', 'default' => wpforms()->obj( 'form' )->get_count_per_page(), 'type' => 'number', 'args' => [ 'min' => 1, 'max' => 999, 'step' => 1, 'maxlength' => 3, ], ], ], ], 'view' => [ 'heading' => esc_html__( 'View', 'wpforms-lite' ), 'options' => [ [ 'label' => esc_html__( 'Show form templates', 'wpforms-lite' ), 'option' => 'show_form_templates', 'default' => true, 'type' => 'checkbox', 'checked' => true, ], ], ], ]; $this->init_screen_options( wpforms_is_admin_page( 'overview' ) ); $this->hooks(); } /** * Hooks. * * @since 1.8.6 */ private function hooks() { // Reset columns settings. add_filter( 'manage_toplevel_page_wpforms-overview_columns', [ $this, 'screen_settings_columns' ] ); // Rewrite forms per page value from Form Overview page screen options. add_filter( 'wpforms_forms_per_page', [ $this, 'get_wpforms_forms_per_page' ] ); } /** * Check if the template visibility option is enabled. * * @since 1.8.8 * * @return bool */ public function overview_show_form_templates() { return get_user_option( $this->screen_options_id . '_view_show_form_templates' ); } /** * Get forms per page value from Form Overview page screen options. * * @since 1.8.8 * * @return int */ public function get_wpforms_forms_per_page() { return get_user_option( $this->screen_options_id . '_pagination_per_page' ); } /** * Determine if the user is viewing the overview page, if so, party on. * * @since 1.8.6 */ public function init() { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks // Only load if we are actually on the overview page. if ( ! wpforms_is_admin_page( 'overview' ) ) { return; } // Avoid recursively include _wp_http_referer in the REQUEST_URI. $this->remove_referer(); add_action( 'current_screen', [ $this, 'init_overview_table' ], 5 ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueues' ] ); add_action( 'wpforms_admin_page', [ $this, 'output' ] ); add_action( 'wpforms_admin_page', [ $this, 'field_column_setting' ] ); /** * Fires after the form overview page initialization. * * @since 1.0.0 */ do_action( 'wpforms_overview_init' ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName } /** * Init overview table class. * * @since 1.8.6 */ public function init_overview_table() { $this->overview_table = ListTable::get_instance(); } /** * Remove previous `_wp_http_referer` variable from the REQUEST_URI. * * @since 1.8.6 */ private function remove_referer() { if ( isset( $_SERVER['REQUEST_URI'] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $_SERVER['REQUEST_URI'] = remove_query_arg( '_wp_http_referer', wp_unslash( $_SERVER['REQUEST_URI'] ) ); } } /** * Add per-page screen option to the Forms table. * * @since 1.8.6 * * @depecated 1.8.8 Use HasScreenOptions trait instead. */ public function screen_options() { _deprecated_function( __METHOD__, '1.8.8 of the WPForms plugin' ); } /** * Filter screen settings columns data. * * @since 1.8.6 * * @param array $columns Columns. * * @return array * @noinspection PhpMissingParamTypeInspection */ public function screen_settings_columns( $columns ): array { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found return []; } /** * Enqueue assets for the overview page. * * @since 1.8.6 */ public function enqueues() { $min = wpforms_get_min_suffix(); wp_enqueue_script( 'wpforms-htmx', WPFORMS_PLUGIN_URL . 'assets/lib/htmx.min.js', [], WPFORMS_VERSION, true ); wp_enqueue_script( 'wpforms-admin-forms-overview', WPFORMS_PLUGIN_URL . "assets/js/admin/forms/overview{$min}.js", [ 'jquery', 'underscore', 'wpforms-htmx' ], WPFORMS_VERSION, true ); wp_enqueue_style( 'wpforms-admin-list-table-ext', WPFORMS_PLUGIN_URL . "assets/css/admin-list-table-ext{$min}.css", [], WPFORMS_VERSION ); wp_enqueue_script( 'wpforms-admin-list-table-ext', WPFORMS_PLUGIN_URL . "assets/js/admin/share/list-table-ext{$min}.js", [ 'jquery', 'jquery-ui-sortable', 'underscore', 'wpforms-admin', 'wpforms-multiselect-checkboxes' ], WPFORMS_VERSION, true ); /** * Fires after enqueue the forms overview page assets. * * @since 1.0.0 */ do_action( 'wpforms_overview_enqueue' ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName } /** * Determine if it is an empty state. * * @since 1.8.6 */ private function is_empty_state() { // phpcs:disable WordPress.Security.NonceVerification.Recommended return empty( $this->overview_table->items ) && ! isset( $_GET['search']['term'] ) && ! isset( $_GET['status'] ) && ! isset( $_GET['tags'] ) && array_sum( wpforms()->obj( 'forms_views' )->get_count() ) === 0; // phpcs:enable WordPress.Security.NonceVerification.Recommended } /** * Build the output for the overview page. * * @since 1.8.6 */ public function output() { ?> <div id="wpforms-overview" class="wrap wpforms-admin-wrap"> <h1 class="page-title"> <?php esc_html_e( 'Forms Overview', 'wpforms-lite' ); ?> <?php if ( wpforms_current_user_can( 'create_forms' ) ) : ?> <a href="<?php echo esc_url( admin_url( 'admin.php?page=wpforms-builder&view=setup' ) ); ?>" class="page-title-action wpforms-btn add-new-h2 wpforms-btn-orange" data-action="add"> <svg viewBox="0 0 14 14" class="page-title-action-icon"> <path d="M14 5.385v3.23H8.615V14h-3.23V8.615H0v-3.23h5.385V0h3.23v5.385H14Z"/> </svg> <span class="page-title-action-text"><?php esc_html_e( 'Add New', 'wpforms-lite' ); ?></span> </a> <?php endif; ?> </h1> <div class="wpforms-admin-content"> <?php $this->overview_table->prepare_items(); /** * Fires before forms overview list table output. * * @since 1.6.0.1 */ do_action( 'wpforms_admin_overview_before_table' ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName if ( $this->is_empty_state() ) { // Output no forms screen. echo wpforms_render( 'admin/empty-states/no-forms' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } else { ?> <form id="wpforms-overview-table" method="get" action="<?php echo esc_url( admin_url( 'admin.php?page=wpforms-overview' ) ); ?>"> <input type="hidden" name="post_type" value="wpforms" /> <input type="hidden" name="page" value="wpforms-overview" /> <?php $this->overview_table->search_box( esc_html__( 'Search Forms', 'wpforms-lite' ), 'wpforms-overview-search' ); $this->overview_table->views(); $this->overview_table->display(); ?> </form> <?php } ?> </div> </div> <?php } /** * Settings for field column personalization. * * @since 1.8.6 */ public function field_column_setting() { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo $this->get_columns_multiselect(); } /** * Get columns multiselect menu. * * @since 1.8.6 * * @return string HTML menu markup. */ private function get_columns_multiselect(): string { $columns = Columns::get_columns(); $selected_keys = Columns::get_selected_columns_keys(); $options = ''; $html = ' <div id="wpforms-list-table-ext-edit-columns-select-container" class="wpforms-hidden wpforms-forms-overview-page"> <form method="post" action=""> <input type="hidden" name="action" value="wpforms_admin_forms_overview_save_columns_order"/> <select name="fields[]" id="wpforms-forms-table-edit-columns-select" class="wpforms-forms-table-edit-columns-select wpforms-list-table-ext-edit-columns-select" multiple="multiple"> <optgroup label="' . esc_html__( 'Columns', 'wpforms-lite' ) . '"> %s </optgroup> </select> </form> </div> '; foreach ( $columns as $column ) { $selected = in_array( $column->get_id(), $selected_keys, true ) ? 'selected' : ''; $disabled = $column->is_readonly() ? 'disabled="true"' : ''; $options .= sprintf( '<option value="%s" %s %s>%s</option>', esc_attr( $column->get_id() ), $selected, $disabled, esc_html( $column->get_label() ) ); } return sprintf( $html, $options ); } } Forms/ListTable.php 0000644 00000041455 15252506741 0010246 0 ustar 00 <?php namespace WPForms\Admin\Forms; use WP_List_Table; use WP_Post; use WP_Screen; use WPForms\Admin\Forms\Table\Facades\Columns; use WPForms\Forms\Locator; use WPForms\Integrations\LiteConnect\LiteConnect; use WPForms\Integrations\LiteConnect\Integration as LiteConnectIntegration; // IMPORTANT NOTICE: // This line is needed to prevent fatal errors in the third-party plugins. // We know about Jetpack (probably others also) can load WP classes during cron jobs or something similar. require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php'; /** * Generate the table on the plugin overview page. * * @since 1.8.6 */ class ListTable extends WP_List_Table { /** * Number of forms to show per page. * * @since 1.8.6 * * @var int */ public $per_page; /** * Number of forms in different views. * * @since 1.8.6 * * @var array */ private $count; /** * Current view. * * @since 1.8.6 * * @var string */ private $view; /** * Primary class constructor. * * @since 1.8.6 */ public function __construct() { // Utilize the parent constructor to build the main class properties. parent::__construct( [ 'singular' => 'form', 'plural' => 'forms', 'ajax' => false, ] ); $this->hooks(); // Determine the current view. $this->view = wpforms()->obj( 'forms_views' )->get_current_view(); /** * Filters the default number of forms to show per page. * * @since 1.0.0 * * @param int $forms_per_page Number of forms to show per page. */ $this->per_page = (int) apply_filters( 'wpforms_overview_per_page', 20 ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName } /** * Register hooks. * * @since 1.8.6 */ private function hooks() { add_filter( 'default_hidden_columns', [ $this, 'default_hidden_columns' ], 10, 2 ); } /** * Get the instance of a class and store it in itself. * * @since 1.8.6 */ public static function get_instance() { static $instance; if ( ! $instance ) { $instance = new self(); } return $instance; } /** * Retrieve the table columns. * * @since 1.8.6 * * @return array $columns Array of all the list table columns. */ public function get_columns() { return Columns::get_list_table_columns(); } /** * Render the checkbox column. * * @since 1.8.6 * * @param WP_Post $form Form. * * @return string */ public function column_cb( $form ) { return '<input type="checkbox" name="form_id[]" value="' . absint( $form->ID ) . '" />'; } /** * Render the columns. * * @since 1.8.6 * * @param WP_Post $form CPT object as a form representation. * @param string $column_name Column Name. * * @return string */ public function column_default( $form, $column_name ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity switch ( $column_name ) { case 'id': $value = $form->ID; break; case 'shortcode': $value = '[wpforms id="' . $form->ID . '"]'; if ( wpforms_is_form_template( $form->ID ) ) { $value = __( 'N/A', 'wpforms-lite' ); } break; // This slug is not changed to 'date' for backward compatibility. case 'created': if ( gmdate( 'Ymd', strtotime( $form->post_date ) ) === gmdate( 'Ymd', strtotime( $form->post_modified ) ) ) { $value = wp_kses( sprintf( /* translators: %1$s - Post created date. */ __( 'Created<br/>%1$s', 'wpforms-lite' ), esc_html( wpforms_datetime_format( $form->post_date ) ) ), [ 'br' => [] ] ); } else { $value = wp_kses( sprintf( /* translators: %1$s - Post modified date. */ __( 'Last Modified<br/>%1$s', 'wpforms-lite' ), esc_html( wpforms_datetime_format( $form->post_modified ) ) ), [ 'br' => [] ] ); } break; case 'entries': $value = sprintf( '<span class="wpforms-lite-connect-entries-count"><a href="%s" data-title="%s">%s%d</a></span>', esc_url( admin_url( 'admin.php?page=wpforms-entries' ) ), esc_attr__( 'Entries are securely backed up in the cloud. Upgrade to restore.', 'wpforms-lite' ), '<svg viewBox="0 0 16 12"><path d="M10.8 2c1.475 0 2.675 1.175 2.775 2.625C15 5.125 16 6.475 16 8a3.6 3.6 0 0 1-3.6 3.6H4a3.98 3.98 0 0 1-4-4 4.001 4.001 0 0 1 2.475-3.7A4.424 4.424 0 0 1 6.8.4c1.4 0 2.625.675 3.425 1.675C10.4 2.025 10.6 2 10.8 2ZM4 10.4h8.4a2.4 2.4 0 0 0 0-4.8.632.632 0 0 0-.113.013.678.678 0 0 1-.112.012c.125-.25.225-.525.225-.825 0-.875-.725-1.6-1.6-1.6a1.566 1.566 0 0 0-1.05.4 3.192 3.192 0 0 0-2.95-2 3.206 3.206 0 0 0-3.2 3.2v.05A2.757 2.757 0 0 0 1.2 7.6 2.795 2.795 0 0 0 4 10.4Zm6.752-4.624a.64.64 0 1 0-.905-.905L6.857 7.86 5.38 6.352a.64.64 0 1 0-.914.896l1.93 1.97a.64.64 0 0 0 .91.004l3.446-3.446Z"/></svg>', LiteConnectIntegration::get_form_entries_count( $form->ID ) ); break; case 'modified': $value = get_post_modified_time( get_option( 'date_format' ), false, $form ); break; case 'author': $value = ''; $author = get_userdata( $form->post_author ); if ( ! $author ) { break; } $value = $author->display_name; $user_edit_url = get_edit_user_link( $author->ID ); if ( ! empty( $user_edit_url ) ) { $value = '<a href="' . esc_url( $user_edit_url ) . '">' . esc_html( $value ) . '</a>'; } break; case 'php': $value = '<code style="display:block;font-size:11px;">if( function_exists( \'wpforms_get\' ) ){ wpforms_get( ' . $form->ID . ' ); }</code>'; break; default: $value = ''; } /** * Filters the Forms Overview list table culumn value. * * @since 1.0.0 * * @param string $value Column value. * @param WP_Post $form CPT object as a form representation. * @param string $column_name Column Name. */ return apply_filters( 'wpforms_overview_table_column_value', $value, $form, $column_name ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName } /** * Filter the default list of hidden columns. * * @since 1.8.6 * * @param string[] $hidden Array of IDs of columns hidden by default. * @param WP_Screen $screen WP_Screen object of the current screen. * * @return string[] */ public function default_hidden_columns( $hidden, $screen ) { if ( $screen->id !== 'toplevel_page_wpforms-overview' ) { return $hidden; } if ( Columns::has_selected_columns() ) { return []; } return [ 'tags', 'author', Locator::COLUMN_NAME, ]; } /** * Render the form name column with action links. * * @since 1.8.6 * * @param WP_Post $form Form. * * @return string */ public function column_name( $form ) { $title = $this->get_column_name_title( $form ); $states = _post_states( $form, false ); $actions = $this->get_column_name_row_actions( $form ); // Build the row action links and return the value. return $title . $states . $actions; } /** * Render the form tags column. * * @since 1.8.6 * * @param WP_Post $form Form. * * @return string */ public function column_tags( $form ) { return wpforms()->obj( 'forms_tags' )->column_tags( $form ); } /** * Get the form name HTML for the form name column. * * @since 1.8.6 * * @param WP_Post $form Form object. * * @return string */ protected function get_column_name_title( $form ) { $title = ! empty( $form->post_title ) ? $form->post_title : $form->post_name; $name = sprintf( '<span><strong>%s</strong></span>', esc_html( $title ) ); if ( $this->view === 'trash' ) { return $name; } if ( wpforms_current_user_can( 'view_form_single', $form->ID ) ) { $name = sprintf( '<a href="%s" title="%s" class="row-title" target="_blank" rel="noopener noreferrer"><strong>%s</strong></a>', esc_url( wpforms_get_form_preview_url( $form->ID ) ), esc_attr__( 'View preview', 'wpforms-lite' ), esc_html( $title ) ); } if ( wpforms_current_user_can( 'view_entries_form_single', $form->ID ) ) { $name = sprintf( '<a href="%s" title="%s"><strong>%s</strong></a>', esc_url( add_query_arg( [ 'view' => 'list', 'form_id' => $form->ID, ], admin_url( 'admin.php?page=wpforms-entries' ) ) ), esc_attr__( 'View entries', 'wpforms-lite' ), esc_html( $title ) ); } if ( wpforms_current_user_can( 'edit_form_single', $form->ID ) ) { $name = sprintf( '<a href="%s" title="%s"><strong>%s</strong></a>', esc_url( add_query_arg( [ 'view' => 'fields', 'form_id' => $form->ID, ], admin_url( 'admin.php?page=wpforms-builder' ) ) ), esc_attr__( 'Edit This Form', 'wpforms-lite' ), esc_html( $title ) ); } return $name; } /** * Get the row actions HTML for the form name column. * * @since 1.8.6 * * @param WP_Post $form Form object. * * @return string */ protected function get_column_name_row_actions( $form ) { // phpcs:disable WPForms.Comments.PHPDocHooks.RequiredHookDocumentation, WPForms.PHP.ValidateHooks.InvalidHookName /** * Filters row action links on the 'All Forms' admin page. * * @since 1.0.0 * * @param array $row_actions An array of action links for a given form. * @param WP_Post $form Form object. */ return $this->row_actions( apply_filters( 'wpforms_overview_row_actions', [], $form ) ); // phpcs:enable WPForms.Comments.PHPDocHooks.RequiredHookDocumentation, WPForms.PHP.ValidateHooks.InvalidHookName } /** * Define bulk actions available for our table listing. * * @since 1.8.6 * * @return array */ public function get_bulk_actions() { return wpforms()->obj( 'forms_bulk_actions' )->get_dropdown_items(); } /** * Generate the table navigation above or below the table. * * @since 1.8.6 * * @param string $which The location of the table navigation: 'top' or 'bottom'. */ protected function display_tablenav( $which ) { // If there are some forms just call the parent method. if ( $this->has_items() ) { parent::display_tablenav( $which ); return; } // Otherwise, display bulk actions menu and "0 items" on the right (pagination). ?> <div class="tablenav <?php echo esc_attr( $which ); ?>"> <div class="alignleft actions bulkactions"> <?php $this->bulk_actions( $which ); ?> </div> <?php $this->extra_tablenav( $which ); if ( $which === 'top' ) { $this->pagination( $which ); } ?> <br class="clear" /> </div> <?php } /** * Extra controls to be displayed between bulk actions and pagination. * * @since 1.8.6 * * @param string $which The location of the table navigation: 'top' or 'bottom'. */ protected function extra_tablenav( $which ) { wpforms()->obj( 'forms_tags' )->extra_tablenav( $which, $this ); wpforms()->obj( 'forms_views' )->extra_tablenav( $which ); } /** * Message to be displayed when there are no forms. * * @since 1.8.6 */ public function no_items() { wpforms()->obj( 'forms_views' )->get_current_view() === 'templates' ? esc_html_e( 'No form templates found.', 'wpforms-lite' ) : esc_html_e( 'No forms found.', 'wpforms-lite' ); } /** * Fetch and set up the final data for the table. * * @since 1.8.6 */ public function prepare_items() { // Set up the columns. $columns = $this->get_columns(); // Hidden columns (none). $hidden = get_hidden_columns( $this->screen ); // Define which columns can be sorted - form name, author, date. $sortable = [ 'id' => [ 'ID', false ], 'name' => [ 'title', false ], 'author' => [ 'author', false ], 'created' => [ 'date', false ], ]; // Set column headers. $this->_column_headers = [ $columns, $hidden, $sortable ]; // phpcs:disable WordPress.Security.NonceVerification.Recommended $page = $this->get_pagenum(); $order = isset( $_GET['order'] ) && $_GET['order'] === 'asc' ? 'ASC' : 'DESC'; $orderby = isset( $_GET['orderby'] ) ? sanitize_key( $_GET['orderby'] ) : 'ID'; $per_page = $this->get_items_per_page( 'wpforms_forms_per_page', $this->per_page ); // phpcs:enable WordPress.Security.NonceVerification.Recommended if ( $orderby === 'date' ) { $orderby = [ 'modified' => $order, 'date' => $order, ]; } $args = [ 'orderby' => $orderby, 'order' => $order, 'nopaging' => false, 'posts_per_page' => $per_page, 'paged' => $page, 'no_found_rows' => false, 'post_status' => 'publish', ]; /** * Filters the `get_posts()` arguments while preparing items for the forms overview table. * * @since 1.7.3 * * @param array $args Arguments array. */ $args = (array) apply_filters( 'wpforms_overview_table_prepare_items_args', $args ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName // Giddy up. $this->items = wpforms()->obj( 'form' )->get( '', $args ); $per_page = $args['posts_per_page'] ?? $this->get_items_per_page( 'wpforms_forms_per_page', $this->per_page ); $this->update_count( $args ); $count_current_view = empty( $this->count[ $this->view ] ) ? 0 : $this->count[ $this->view ]; // Finalize pagination. $this->set_pagination_args( [ 'total_items' => $count_current_view, 'per_page' => $per_page, 'total_pages' => (int) ceil( $count_current_view / $per_page ), ] ); $items = is_array( $this->items ) ? $this->items : []; $form_ids = array_map( static function ( $form ) { return (int) $form->ID; }, $items ); /** * Fires after the Forms Overview list table has prepared its items. * * Provides the IDs of all forms shown on the current page, allowing * subscribers to bulk-fetch related per-form data in a single query * (avoiding per-row N+1 queries during column rendering). * * @since 2.0.0 * * @param int[] $form_ids IDs of forms on the current page. */ do_action( 'wpforms_admin_forms_list_table_prepare_items_after', $form_ids ); } /** * Calculate and update form counts. * * @since 1.8.6 * * @param array $args Get forms arguments. */ private function update_count( $args ) { /** * Allow counting forms filtered by a given search criteria. * * If result will not contain `all` key, count All Forms without filtering will be performed. * * @since 1.7.2 * * @param array $count Contains counts of forms in different views. * @param array $args Arguments of the `get_posts`. */ $this->count = (array) apply_filters( 'wpforms_overview_table_update_count', [], $args ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName // We do not need to perform all forms count if we have the result already. if ( isset( $this->count['all'] ) ) { return; } // Count all forms. $this->count['all'] = wpforms_current_user_can( 'wpforms_view_others_forms' ) ? (int) wp_count_posts( 'wpforms' )->publish : (int) count_user_posts( get_current_user_id(), 'wpforms', true ); /** * Filters forms count data after counting all forms. * * This filter executes only if the result of `wpforms_overview_table_update_count` filter * doesn't contain `all` key. * * @since 1.7.3 * * @param array $count Contains counts of forms in different views. * @param array $args Arguments of the `get_posts`. */ $this->count = (array) apply_filters( 'wpforms_overview_table_update_count_all', $this->count, $args ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName } /** * Display the pagination. * * @since 1.8.6 * * @param string $which The location of the table pagination: 'top' or 'bottom'. */ protected function pagination( $which ) { if ( $this->has_items() ) { parent::pagination( $which ); return; } printf( '<div class="tablenav-pages one-page"> <span class="displaying-num">%s</span> </div>', esc_html__( '0 items', 'wpforms-lite' ) ); } /** * Extending the `display_rows()` method in order to add hooks. * * @since 1.8.6 */ public function display_rows() { /** * Fires before displaying the table rows. * * @since 1.5.6.2 * * @param ListTable $list_table_obj ListTable instance. */ do_action( 'wpforms_admin_overview_before_rows', $this ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName parent::display_rows(); /** * Fires after displaying the table rows. * * @since 1.5.6.2 * * @param ListTable $list_table_obj ListTable instance. */ do_action( 'wpforms_admin_overview_after_rows', $this ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName } /** * Forms search markup. * * @since 1.8.6 * * @param string $text The 'submit' button label. * @param string $input_id ID attribute value for the search input field. */ public function search_box( $text, $input_id ) { wpforms()->obj( 'forms_search' )->search_box( $text, $input_id ); } /** * Get the list of views available on forms overview table. * * @since 1.8.6 */ protected function get_views() { return wpforms()->obj( 'forms_views' )->get_views(); } } Forms/UserTemplates.php 0000644 00000027737 15252506741 0011167 0 ustar 00 <?php namespace WPForms\Admin\Forms; use WP_Post; use WPForms\Admin\Notice; use WPForms\Pro\Tasks\Actions\PurgeTemplateEntryTask; /** * User Templates class. * * @since 1.8.8 */ class UserTemplates { /** * Initialize class. * * @since 1.8.8 */ public function init() { $this->hooks(); } /** * Hooks. * * @since 1.8.8 */ public function hooks() { add_action( 'init', [ $this, 'register_post_type' ] ); // Add template states. add_filter( 'display_post_states', [ $this, 'add_template_states' ], 10, 2 ); // Modify get form args on the overview page. add_filter( 'wpforms_get_form_args', [ $this, 'add_template_post_type' ] ); // Modify Show Templates user option. add_filter( 'get_user_option_wpforms_forms_overview_show_form_templates', [ $this, 'get_forms_overview_show_form_templates_option' ] ); // Disable payment processing for user templates. add_filter( 'wpforms_process_before_form_data', [ $this, 'process_before_form_data' ] ); // Add user templates to the form templates list. add_filter( 'wpforms_form_templates', [ $this, 'add_form_templates' ] ); // AJAX handler for deleting user templates. add_action( 'wp_ajax_wpforms_user_template_remove', [ $this, 'ajax_remove_user_template' ] ); // Disable Lite Connect integration for user templates on form submission. add_action( 'wpforms_process', [ $this, 'process_entry' ], 10, 3 ); if ( wpforms()->is_pro() ) { // Add notices about entry(ies) being purged. add_action( 'admin_notices', [ $this, 'get_template_entries_notice' ] ); add_action( 'admin_notices', [ $this, 'get_template_entry_notice' ] ); // Add purge entry task. add_filter( 'wpforms_tasks_get_tasks', [ $this, 'add_purge_entry_task' ] ); // Disable edit entry for templates. add_filter( 'wpforms_current_user_can', [ $this, 'disable_edit_entry' ], 10, 3 ); } } /** * Register the `wpforms-template` post type. * * @since 1.8.8 */ public function register_post_type() { /** * Filter the arguments for the `wpforms-template` post type. * * @since 1.8.8 * * @param array $args Post type arguments. */ $args = apply_filters( // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName 'wpforms_template_post_type_args', [ 'label' => 'WPForms Template', 'public' => false, 'exclude_from_search' => true, 'show_ui' => false, 'show_in_admin_bar' => false, 'rewrite' => false, 'query_var' => false, 'can_export' => false, 'supports' => [ 'title', 'author', 'revisions' ], 'capability_type' => 'wpforms_form', // Not using 'capability_type' anywhere. It just has to be custom for security reasons. 'map_meta_cap' => false, // Don't let WP to map meta caps to have a granular control over this process via 'map_meta_cap' filter. ] ); register_post_type( 'wpforms-template', $args ); } /** * Add template states. * * @since 1.8.8 * * @param array $post_states Array of post states. * @param WP_Post $post Post object. * * @return array */ public function add_template_states( $post_states, $post ) { if ( ! ( wpforms_is_admin_page( 'overview' ) || wpforms_is_admin_page( 'entries' ) ) ) { return $post_states; } // No need to show template states on the templates page. if ( wpforms_is_admin_page( 'overview' ) && wpforms()->obj( 'forms_views' )->get_current_view() === 'templates' ) { return $post_states; } if ( $post->post_type === 'wpforms-template' ) { $post_states['wpforms_template'] = __( 'Template', 'wpforms-lite' ); } return $post_states; } /** * Disable edit entry for templates. * * @since 1.8.8 * * @param bool $user_can Whether the current user can perform the given capability. * @param string $caps Capability name. * @param int $id The ID of the object to check against. * * @return bool Whether the current user can perform the given capability. */ public function disable_edit_entry( bool $user_can, $caps, $id ): bool { if ( $caps === 'edit_entries_form_single' && wpforms_is_form_template( $id ) ) { $user_can = false; } return $user_can; } /** * Display admin notice for the entries page. * * @since 1.8.8 */ public function get_template_entries_notice() { if ( ! wpforms_is_admin_page( 'entries', 'list' ) ) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended $form_id = ! empty( $_REQUEST['form_id'] ) ? absint( $_REQUEST['form_id'] ) : 0; // The notice should be displayed only for form templates. if ( ! wpforms_is_form_template( $form_id ) ) { return; } // If there are no entries, we don't need to display the notice on the empty state page. $entries = wpforms()->obj( 'entry' )->get_entries( [ 'form_id' => $form_id, 'limit' => 1, ] ); if ( empty( $entries ) ) { return; } /** This filter is documented in wpforms/src/Pro/Tasks/Actions/PurgeTemplateEntryTask.php */ $delay = (int) apply_filters( 'wpforms_pro_tasks_actions_purge_template_entry_task_delay', DAY_IN_SECONDS ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName Notice::warning( sprintf( /* translators: %s - delay in formatted time. */ esc_html__( 'Form template entries are for testing purposes and will be automatically deleted after %s.', 'wpforms-lite' ), // The `- 1` hack is to avoid the "1 day" message in favor of "24 hours". human_time_diff( time(), time() + $delay - 1 ) ) ); } /** * Display admin notice for the entry page. * * @since 1.8.8 */ public function get_template_entry_notice() { if ( ! wpforms_is_admin_page( 'entries', 'details' ) ) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended $entry_id = ! empty( $_REQUEST['entry_id'] ) ? absint( $_REQUEST['entry_id'] ) : 0; $entry = wpforms()->obj( 'entry' )->get( $entry_id ); // If entry does not exist, we don't need to display the notice on the empty state page. if ( empty( $entry ) ) { return; } // The notice should be displayed only for form template entry. if ( ! wpforms_is_form_template( $entry->form_id ) ) { return; } $meta = wpforms()->obj( 'entry_meta' )->get_meta( [ 'entry_id' => absint( $entry_id ), 'type' => 'purge_template_entry_task', 'number' => 1, ] ); if ( empty( $meta ) ) { return; } $task = wpforms_json_decode( $meta[0]->data,true ); if ( empty( $task['timestamp'] ) ) { return; } Notice::warning( sprintf( /* translators: %s - delay in formatted time. */ esc_html__( 'Form template entries are for testing purposes. This entry will be automatically deleted in %s.', 'wpforms-lite' ), human_time_diff( time(), $task['timestamp'] ) ) ); } /** * Get the Show Templates user option. * * If the user has not set the Show Templates screen option, it will default to showing templates. * In this case, we want to show templates by default. * * @since 1.8.8 * * @return bool Whether to show templates by default. */ public function get_forms_overview_show_form_templates_option(): bool { $screen_options = get_user_option( 'wpforms_forms_overview_options' ); $result = $screen_options['wpforms_forms_overview_show_form_templates'] ?? true; return $result !== '0'; // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Add `wpforms-template` post type to the form args. * * @since 1.8.8 * * @param array $args Form arguments. * * @return array */ public function add_template_post_type( array $args ): array { // Only add the post type to the form args on the overview page. if ( ! wpforms_is_admin_page( 'overview' ) ) { return $args; } // Only add the template post type if the Show Templates screen option is enabled // and `post_type` is not already set. if ( ! isset( $args['post_type'] ) && wpforms()->obj( 'forms_overview' )->overview_show_form_templates() ) { $args['post_type'] = wpforms()->obj( 'form' )::POST_TYPES; } return $args; } /** * Add user templates to the form templates list. * * @since 1.8.8 * * @param array $templates Form templates. * * @return array Form templates. */ public function add_form_templates( array $templates ): array { $user_templates = wpforms()->obj( 'form' )->get( '', [ 'post_type' => 'wpforms-template' ] ); if ( empty( $user_templates ) ) { return $templates; } foreach ( $user_templates as $template ) { $template_data = wpforms_decode( $template->post_content ); $edit_url = add_query_arg( [ 'page' => 'wpforms-builder', 'form_id' => $template->ID, ], admin_url( 'admin.php' ) ); $create_url = add_query_arg( [ 'page' => 'wpforms-builder', 'form_id' => $template->ID, 'action' => 'template_to_form', '_wpnonce' => wp_create_nonce( 'wpforms_template_to_form_form_nonce' ), ], admin_url( 'admin.php' ) ); $templates[] = [ 'name' => $template->post_title, 'slug' => 'wpforms-user-template-' . $template->ID, 'action_text' => wpforms_is_admin_page( 'builder' ) || wp_doing_ajax() ? esc_html__( 'Use Template', 'wpforms-lite' ) : esc_html__( 'Create Form', 'wpforms-lite' ), 'edit_action_text' => esc_html__( 'Edit Template', 'wpforms-lite' ), 'description' => ! empty( $template_data['settings']['template_description'] ) ? $template_data['settings']['template_description'] : '', 'source' => 'wpforms-user-template', 'create_url' => $create_url, 'edit_url' => $edit_url, 'categories' => [ 'user' ], 'has_access' => true, 'data' => $template_data, 'post_id' => $template->ID, ]; } return $templates; } /** * AJAX handler for removing user templates. * * @since 1.8.8 */ public function ajax_remove_user_template(): void { // Run a security check. check_ajax_referer( 'wpforms-form-templates', 'nonce' ); $template_id = isset( $_POST['template'] ) ? absint( $_POST['template'] ) : 0; if ( ! $template_id ) { wp_send_json_error(); } // Check for permissions for the specific template. if ( ! wpforms_current_user_can( 'delete_form_single', $template_id ) ) { wp_send_json_error( esc_html__( 'You do not have permission to delete this template.', 'wpforms-lite' ) ); } // Verify the post exists and is a template. $template = get_post( $template_id ); if ( ! $template || $template->post_type !== 'wpforms-template' ) { wp_send_json_error( esc_html__( 'Template not found.', 'wpforms-lite' ) ); } // Delete the template. $result = wp_delete_post( $template_id, true ); if ( ! $result ) { wp_send_json_error( esc_html__( 'Failed to delete the template.', 'wpforms-lite' ) ); } wp_send_json_success(); } /** * Add purge entry task. * * @since 1.8.8 * * @param array $tasks Task class list. */ public function add_purge_entry_task( $tasks ) { $tasks[] = PurgeTemplateEntryTask::class; return $tasks; } /** * Modify the form data before it is processed to disable payment processing. * * @since 1.8.8 * * @param array $form_data Form data. * * @return array */ public function process_before_form_data( $form_data ) { if ( ! isset( $form_data['id'] ) ) { return $form_data; } if ( wpforms_is_form_template( $form_data['id'] ) ) { $form_data['payments'] = []; } return $form_data; } /** * Disable Lite Connect integration for user templates while processing submission. * * @since 1.8.8 * * @param array $fields Form fields. * @param array $entry Form entry. * @param array $form_data Form data. */ public function process_entry( array $fields, array $entry, array $form_data ) { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks if ( ! wpforms_is_form_template( $form_data['id'] ) ) { return; } add_filter( 'wpforms_integrations_lite_connect_is_allowed', '__return_false' ); } } Forms/Views.php 0000644 00000043420 15252506741 0007452 0 ustar 00 <?php namespace WPForms\Admin\Forms; use WP_Post; /** * List table views. * * @since 1.7.3 */ class Views { /** * Current view slug. * * @since 1.7.3 * * @var string */ private $current_view; /** * Views settings. * * @since 1.7.3 * * @var array */ private $views; /** * Count forms in different views. * * @since 1.7.3 * * @var array */ private $count; /** * Base URL. * * @since 1.7.3 * * @var string */ private $base_url; /** * Show form templates. * * @since 1.8.8 * * @var bool */ private $show_form_templates; /** * Views configuration. * * @since 1.7.3 */ private function configuration() { if ( ! empty( $this->views ) ) { return; } // Define views. $views = [ 'all' => [ 'title' => __( 'All', 'wpforms-lite' ), 'get_var' => '', 'get_var_value' => '', ], 'trash' => [ 'title' => __( 'Trash', 'wpforms-lite' ), 'get_var' => 'status', 'get_var_value' => 'trash', 'args' => [ 'post_status' => 'trash', ], ], ]; $this->show_form_templates = wpforms()->obj( 'forms_overview' )->overview_show_form_templates(); // Add Forms and Templates views if Show Templates setting is enabled. if ( $this->show_form_templates ) { $views = wpforms_array_insert( $views, [ 'forms' => [ 'title' => __( 'Forms', 'wpforms-lite' ), 'get_var' => 'type', 'get_var_value' => 'form', 'args' => [ 'post_type' => 'wpforms', ], ], 'templates' => [ 'title' => __( 'Templates', 'wpforms-lite' ), 'get_var' => 'type', 'get_var_value' => 'template', 'args' => [ 'post_type' => 'wpforms-template', ], ], ], 'all' ); } // phpcs:disable WPForms.Comments.ParamTagHooks.InvalidParamTagsQuantity /** * Filters configuration of the Forms Overview table views. * * @since 1.7.3 * * @param array $views { * Views array. * * @param array $view { * Each view is the array with three elements: * * @param string $title View title. * @param string $get_var URL query variable name. * @param string $get_var_value URL query variable value. * @param array $args Additional arguments to be passed to `wpforms()->obj( 'form' )->get()` method. * } * ... * } */ $this->views = apply_filters( 'wpforms_admin_forms_views_configuration', $views ); // phpcs:enable WPForms.Comments.ParamTagHooks.InvalidParamTagsQuantity } /** * Determine if the class is allowed to load. * * @since 1.7.3 * * @return bool */ private function allow_load(): bool { // Load only on the `All Forms` admin page. return wpforms_is_admin_page( 'overview' ); } /** * Initialize class. * * @since 1.7.3 */ public function init() { if ( ! $this->allow_load() ) { return; } $this->configuration(); $this->update_current_view(); $this->update_base_url(); $this->hooks(); } /** * Hooks. * * @since 1.7.3 */ private function hooks() { add_filter( 'wpforms_overview_table_update_count', [ $this, 'update_count' ], 10, 2 ); add_filter( 'wpforms_overview_table_update_count_all', [ $this, 'update_count' ], 10, 2 ); add_filter( 'wpforms_overview_table_prepare_items_args', [ $this, 'prepare_items_args' ], 100 ); add_filter( 'wpforms_overview_row_actions', [ $this, 'row_actions_all' ], 9, 2 ); add_filter( 'wpforms_overview_row_actions', [ $this, 'row_actions_trash' ], PHP_INT_MAX, 2 ); add_filter( 'wpforms_admin_forms_search_search_reset_block_message', [ $this, 'search_reset_message' ], 10, 4 ); } /** * Determine and save current view slug. * * @since 1.7.3 */ private function update_current_view() { if ( ! is_array( $this->views ) ) { return; } $this->current_view = 'all'; foreach ( $this->views as $slug => $view ) { if ( // phpcs:disable WordPress.Security.NonceVerification.Recommended isset( $_GET[ $view['get_var'] ] ) && $view['get_var_value'] === sanitize_key( $_GET[ $view['get_var'] ] ) // phpcs:enable WordPress.Security.NonceVerification.Recommended ) { $this->current_view = $slug; return; } } } /** * Update Base URL. * * @since 1.7.3 */ private function update_base_url() { if ( ! is_array( $this->views ) ) { return; } $get_vars = wp_list_pluck( $this->views, 'get_var' ); $get_vars = array_merge( $get_vars, [ 'paged', 'trashed', 'restored', 'deleted', 'duplicated', ] ); $this->base_url = remove_query_arg( $get_vars ); } /** * Get current view slug. * * @since 1.7.3 * * @return string */ public function get_current_view(): string { return $this->current_view; } /** * Get base URL. * * @since 1.7.5 * * @return string */ public function get_base_url(): string { return $this->base_url; } /** * Get view configuration by slug. * * @since 1.7.5 * * @param string $slug View slug. * * @return array */ public function get_view_by_slug( string $slug ): array { return $this->views[ $slug ] ?? []; // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Update count. * * @since 1.7.3 * * @param array $count Number of forms in different views. * @param array $args Get forms arguments. * * @return array */ public function update_count( $count, $args ) { $defaults = [ 'nopaging' => true, 'no_found_rows' => true, 'update_post_meta_cache' => false, 'update_post_term_cache' => false, 'fields' => 'ids', 'post_status' => 'publish', 'post_type' => wpforms()->obj( 'form' )::POST_TYPES, ]; $args = array_merge( $args, $defaults ); $count['all'] = $this->get_all_items_count( $args ); $count['trash'] = $this->get_trashed_forms_count( $args ); // Count forms and templates separately only if Show Templates screen setting is enabled. if ( $this->show_form_templates ) { $count['forms'] = $this->get_forms_count( $args ); $count['templates'] = $this->get_form_templates_count( $args ); } // Store in class property for further use. $this->count = $count; return $count; } /** * Get count of all items. * * May include only forms or both forms and form templates, depending on the * Screen Options settings whether to show form templates or not. * * @since 1.8.8 * * @param array $args Get forms arguments. * * @return int Number of forms and templates. */ private function get_all_items_count( array $args ): int { if ( ! $this->show_form_templates ) { $args['post_type'] = 'wpforms'; } $all_items = wpforms()->obj( 'form' )->get( '', $args ); return is_array( $all_items ) ? count( $all_items ) : 0; } /** * Get count of forms. * * @since 1.8.8 * * @param array $args Get forms arguments. * * @return int Number of published forms. */ private function get_forms_count( array $args ): int { $args['post_type'] = 'wpforms'; $forms = wpforms()->obj( 'form' )->get( '', $args ); return is_array( $forms ) ? count( $forms ) : 0; } /** * Get count of form templates. * * @since 1.8.8 * * @param array $args Get forms arguments. * * @return int Number of published templates. */ private function get_form_templates_count( array $args ): int { $args['post_type'] = 'wpforms-template'; $templates = wpforms()->obj( 'form' )->get( '', $args ); return is_array( $templates ) ? count( $templates ) : 0; } /** * Get count of trashed items. * * May include only forms or both forms and form templates, depending on the * Screen Options settings whether to show form templates or not. * * @since 1.8.8 * * @param array $args Get forms arguments. * * @return int Number of trashed forms. */ private function get_trashed_forms_count( array $args ): int { if ( ! $this->show_form_templates ) { $args['post_type'] = 'wpforms'; } $args['post_status'] = 'trash'; $forms = wpforms()->obj( 'form' )->get( '', $args ); return is_array( $forms ) ? count( $forms ) : 0; } /** * Get counts of forms in different views. * * @since 1.7.3 * * @return array */ public function get_count(): array { return $this->count; } /** * Prepare items arguments for list table. * * @since 1.8.8 * * @param array $args Get multiple forms arguments. * * @return array */ public function prepare_items_args( $args ): array { $view_args = $this->views[ $this->current_view ]['args'] ?? []; if ( ! empty( $view_args ) ) { $args = array_merge( $args, $view_args ); } return $args; } /** * Get forms from Trash when preparing items for list table. * * @since 1.7.3 * * @depecated 1.8.8 The `prepare_items_args()` now handles all cases, uses `$this->views`. * * @param array $args Get multiple forms arguments. * * @return array */ public function prepare_items_trash( $args ) { _deprecated_function( __METHOD__, '1.8.8 of the WPForms plugin' ); return $args; } /** * Generate views items. * * @since 1.7.3 * * @return array */ public function get_views(): array { if ( ! is_array( $this->views ) ) { return []; } $views = []; foreach ( $this->views as $slug => $view ) { if ( $slug === 'trash' && $this->current_view !== 'trash' && empty( $this->count[ $slug ] ) ) { continue; } $views[ $slug ] = $this->get_view_markup( $slug ); } /** * Filters the Forms Overview table views links. * * @since 1.7.3 * * @param array $views Views links. * @param array $count Count forms in different views. */ return apply_filters( 'wpforms_admin_forms_views_get_views', $views, $this->count ); } /** * Generate single view item. * * @since 1.7.3 * * @param string $slug View slug. * * @return string */ private function get_view_markup( string $slug ): string { if ( empty( $this->views[ $slug ] ) ) { return ''; } $view = $this->views[ $slug ]; return sprintf( '<a href="%1$s"%2$s>%3$s <span class="count">(%4$d)</span></a>', $slug === 'all' ? esc_url( $this->base_url ) : esc_url( add_query_arg( $view['get_var'], $view['get_var_value'], $this->base_url ) ), $this->current_view === $slug ? ' class="current"' : '', esc_html( $view['title'] ), empty( $this->count[ $slug ] ) ? 0 : absint( $this->count[ $slug ] ) ); } /** * Row actions for views "All", "Forms", "Templates". * * @since 1.7.3 * * @param array $row_actions Row actions. * @param WP_Post $form Form object. * * @return array */ public function row_actions_all( $row_actions, $form ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh // Modify row actions only for these views. $allowed_views = [ 'all', 'forms', 'templates' ]; if ( ! in_array( $this->current_view, $allowed_views, true ) ) { return $row_actions; } $is_form_template = wpforms_is_form_template( $form ); $row_actions = []; // Edit. if ( wpforms_current_user_can( 'edit_form_single', $form->ID ) ) { $row_actions['edit'] = sprintf( '<a href="%s" title="%s">%s</a>', esc_url( add_query_arg( [ 'view' => 'fields', 'form_id' => $form->ID, ], admin_url( 'admin.php?page=wpforms-builder' ) ) ), $is_form_template ? esc_attr__( 'Edit this template', 'wpforms-lite' ) : esc_attr__( 'Edit this form', 'wpforms-lite' ), esc_html__( 'Edit', 'wpforms-lite' ) ); } // Entries. if ( wpforms_current_user_can( 'view_entries_form_single', $form->ID ) ) { $row_actions['entries'] = sprintf( '<a href="%s" title="%s">%s</a>', esc_url( add_query_arg( [ 'view' => 'list', 'form_id' => $form->ID, ], admin_url( 'admin.php?page=wpforms-entries' ) ) ), esc_attr__( 'View entries', 'wpforms-lite' ), esc_html__( 'Entries', 'wpforms-lite' ) ); } // Payments. if ( wpforms_current_user_can( wpforms_get_capability_manage_options(), $form->ID ) && wpforms()->obj( 'payment' )->get_by( 'form_id', $form->ID ) ) { $row_actions['payments'] = sprintf( '<a href="%s" title="%s">%s</a>', esc_url( add_query_arg( [ 'page' => 'wpforms-payments', 'form_id' => $form->ID, ], admin_url( 'admin.php' ) ) ), esc_attr__( 'View payments', 'wpforms-lite' ), esc_html__( 'Payments', 'wpforms-lite' ) ); } // Preview. if ( wpforms_current_user_can( 'view_form_single', $form->ID ) ) { $row_actions['preview_'] = sprintf( '<a href="%s" title="%s" target="_blank" rel="noopener noreferrer">%s</a>', esc_url( wpforms_get_form_preview_url( $form->ID ) ), esc_attr__( 'View preview', 'wpforms-lite' ), esc_html__( 'Preview', 'wpforms-lite' ) ); } // Duplicate. if ( wpforms_current_user_can( 'create_forms' ) && wpforms_current_user_can( 'view_form_single', $form->ID ) ) { $row_actions['duplicate'] = sprintf( '<a href="%1$s" title="%2$s" data-type="%3$s">%4$s</a>', esc_url( wp_nonce_url( add_query_arg( [ 'action' => 'duplicate', 'form_id' => $form->ID, ], $this->base_url ), 'wpforms_duplicate_form_nonce' ) ), $is_form_template ? esc_attr__( 'Duplicate this template', 'wpforms-lite' ) : esc_attr__( 'Duplicate this form', 'wpforms-lite' ), $is_form_template ? 'template' : 'form', esc_html__( 'Duplicate', 'wpforms-lite' ) ); } // Trash. if ( wpforms_current_user_can( 'delete_form_single', $form->ID ) ) { $query_arg = [ 'action' => 'trash', 'form_id' => $form->ID, ]; if ( $this->current_view !== 'all' ) { $query_arg['type'] = $this->current_view === 'templates' ? 'template' : 'form'; } $row_actions['trash'] = sprintf( '<a href="%s" title="%s">%s</a>', esc_url( wp_nonce_url( add_query_arg( $query_arg, $this->base_url ), 'wpforms_trash_form_nonce' ) ), $is_form_template ? esc_attr__( 'Move this form template to trash', 'wpforms-lite' ) : esc_attr__( 'Move this form to trash', 'wpforms-lite' ), esc_html__( 'Trash', 'wpforms-lite' ) ); } return $row_actions; } /** * Row actions for view "Trash". * * @since 1.7.3 * * @param array $row_actions Row actions. * @param WP_Post $form Form object. * * @return array */ public function row_actions_trash( $row_actions, $form ) { if ( $this->current_view !== 'trash' || ! wpforms_current_user_can( 'delete_form_single', $form->ID ) ) { return $row_actions; } $is_form_template = wpforms_is_form_template( $form ); $row_actions = []; // Restore form. $row_actions['restore'] = sprintf( '<a href="%s" title="%s">%s</a>', esc_url( wp_nonce_url( add_query_arg( [ 'action' => 'restore', 'form_id' => $form->ID, 'status' => 'trash', ], $this->base_url ), 'wpforms_restore_form_nonce' ) ), $is_form_template ? esc_attr__( 'Restore this template', 'wpforms-lite' ) : esc_attr__( 'Restore this form', 'wpforms-lite' ), esc_html__( 'Restore', 'wpforms-lite' ) ); // Delete permanently. $row_actions['delete'] = sprintf( '<a href="%1$s" title="%2$s" data-type="%3$s">%4$s</a>', esc_url( wp_nonce_url( add_query_arg( [ 'action' => 'delete', 'form_id' => $form->ID, 'status' => 'trash', ], $this->base_url ), 'wpforms_delete_form_nonce' ) ), $is_form_template ? esc_attr__( 'Delete this template permanently', 'wpforms-lite' ) : esc_attr__( 'Delete this form permanently', 'wpforms-lite' ), $is_form_template ? 'template' : 'form', esc_html__( 'Delete Permanently', 'wpforms-lite' ) ); return $row_actions; } /** * Search reset message. * * @since 1.7.3 * * @param string $message Search reset block message. * @param string $search_term Search term. * @param array $count Count forms in different views. * @param string $current_view Current view. * * @return string */ public function search_reset_message( $message, $search_term, $count, $current_view ) { if ( $current_view !== 'trash' ) { return $message; } $count['trash'] = ! empty( $count['trash'] ) ? $count['trash'] : 0; return sprintf( wp_kses( /* translators: %1$d - number of forms found in the trash, %2$s - search term. */ _n( 'Found <strong>%1$d form</strong> in <em>the trash</em> containing <em>"%2$s"</em>', 'Found <strong>%1$d forms</strong> in <em>the trash</em> containing <em>"%2$s"</em>', (int) $count['trash'], 'wpforms-lite' ), [ 'strong' => [], 'em' => [], ] ), (int) $count['trash'], esc_html( $search_term ) ); } /** * Extra controls to be displayed between bulk actions and pagination. * * @since 1.7.3 * * @param string $which The location of the table navigation: 'top' or 'bottom'. */ public function extra_tablenav( $which ) { if ( ! wpforms_current_user_can( 'delete_form_single' ) ) { return; } if ( $this->current_view !== 'trash' ) { return; } // Preserve current view after applying bulk action. echo '<input type="hidden" name="status" value="trash">'; // Display Empty Trash button. printf( '<a href="%1$s" class="button delete-all">%2$s</a>', esc_url( wp_nonce_url( add_query_arg( [ 'action' => 'empty_trash', 'form_id' => 1, // Technically, `empty_trash` is one of the bulk actions, therefore we need to provide fake form_id to proceed. 'status' => 'trash', ], $this->base_url ), 'wpforms_empty_trash_form_nonce' ) ), esc_html__( 'Empty Trash', 'wpforms-lite' ) ); } } Forms/BulkActions.php 0000644 00000026376 15252506741 0010606 0 ustar 00 <?php namespace WPForms\Admin\Forms; use WPForms\Admin\Notice; /** * Bulk actions on All Forms page. * * @since 1.7.3 */ class BulkActions { /** * Allowed actions. * * @since 1.7.3 * * @const array */ const ALLOWED_ACTIONS = [ 'trash', 'restore', 'delete', 'duplicate', 'empty_trash', ]; /** * Forms ids. * * @since 1.7.3 * * @var array */ private $ids; /** * Current action. * * @since 1.7.3 * * @var string */ private $action; /** * Current view. * * @since 1.7.3 * * @var string */ private $view; /** * Determine if the class is allowed to load. * * @since 1.7.3 * * @return bool */ private function allow_load() { // Load only on the `All Forms` admin page. return wpforms_is_admin_page( 'overview' ); } /** * Initialize class. * * @since 1.7.3 */ public function init() { if ( ! $this->allow_load() ) { return; } $this->view = wpforms()->obj( 'forms_views' )->get_current_view(); $this->hooks(); } /** * Hooks. * * @since 1.7.3 */ private function hooks() { add_action( 'load-toplevel_page_wpforms-overview', [ $this, 'notices' ] ); add_action( 'load-toplevel_page_wpforms-overview', [ $this, 'process' ] ); add_filter( 'removable_query_args', [ $this, 'removable_query_args' ] ); } /** * Process the bulk actions. * * @since 1.7.3 */ public function process() { // phpcs:disable WordPress.Security.NonceVerification.Recommended $this->ids = isset( $_GET['form_id'] ) ? array_map( 'absint', (array) $_GET['form_id'] ) : []; $this->action = isset( $_REQUEST['action'] ) ? sanitize_key( $_REQUEST['action'] ) : false; if ( $this->action === '-1' ) { $this->action = ! empty( $_REQUEST['action2'] ) ? sanitize_key( $_REQUEST['action2'] ) : false; } // phpcs:enable WordPress.Security.NonceVerification.Recommended if ( empty( $this->ids ) || empty( $this->action ) ) { return; } // Check exact action values. if ( ! in_array( $this->action, self::ALLOWED_ACTIONS, true ) ) { return; } if ( empty( $_GET['_wpnonce'] ) ) { return; } // Check the nonce. if ( ! wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), 'bulk-forms' ) && ! wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), 'wpforms_' . $this->action . '_form_nonce' ) ) { return; } // Finally, we can process the action. $this->process_action(); } /** * Process action. * * @since 1.7.3 * * @uses process_action_trash * @uses process_action_restore * @uses process_action_delete * @uses process_action_duplicate * @uses process_action_empty_trash */ private function process_action() { $method = "process_action_{$this->action}"; // Check that we have a method for this action. if ( ! method_exists( $this, $method ) ) { return; } if ( empty( $this->ids ) || ! is_array( $this->ids ) ) { return; } $query_args = []; if ( count( $this->ids ) === 1 ) { $query_args['type'] = wpforms_is_form_template( $this->ids[0] ) ? 'template' : 'form'; } $result = []; foreach ( $this->ids as $id ) { $result[ $id ] = $this->$method( $id ); } $count_result = count( array_keys( array_filter( $result ) ) ); // Empty trash action returns count of deleted forms. if ( $method === 'process_action_empty_trash' ) { $count_result = $result[1] ?? 0; } $query_args[ rtrim( $this->action, 'e' ) . 'ed' ] = $count_result; // Unset get vars and perform redirect to avoid action reuse. wp_safe_redirect( add_query_arg( $query_args, remove_query_arg( [ 'action', 'action2', '_wpnonce', 'form_id', 'paged', '_wp_http_referer' ] ) ) ); exit; } /** * Trash the form. * * @since 1.7.3 * * @param int $id Form ID to trash. * * @return bool */ private function process_action_trash( $id ) { return wpforms()->obj( 'form' )->update_status( $id, 'trash' ); } /** * Restore the form. * * @since 1.7.3 * * @param int $id Form ID to restore from trash. * * @return bool */ private function process_action_restore( $id ) { return wpforms()->obj( 'form' )->update_status( $id, 'publish' ); } /** * Delete the form. * * @since 1.7.3 * * @param int $id Form ID to delete. * * @return bool */ private function process_action_delete( $id ) { return wpforms()->obj( 'form' )->delete( $id ); } /** * Duplicate the form. * * @since 1.7.3 * * @param int $id Form ID to duplicate. * * @return bool */ private function process_action_duplicate( $id ) { if ( ! wpforms_current_user_can( 'create_forms' ) ) { return false; } if ( ! wpforms_current_user_can( 'view_form_single', $id ) ) { return false; } return wpforms()->obj( 'form' )->duplicate( $id ); } /** * Empty trash. * * @since 1.7.3 * * @param int $id Form ID. This parameter is not used in this method, * but we need to keep it here because all the `process_action_*` methods * should be called with the $id parameter. * * @return bool */ private function process_action_empty_trash( $id ) { // Empty trash is actually the "delete all forms in trash" action. // So, after the execution we should display the same notice as for the `delete` action. $this->action = 'delete'; return wpforms()->obj( 'form' )->empty_trash(); } /** * Define bulk actions available for forms overview table. * * @since 1.7.3 * * @return array */ public function get_dropdown_items() { $items = []; if ( wpforms_current_user_can( 'delete_forms' ) ) { if ( $this->view === 'trash' ) { $items = [ 'restore' => esc_html__( 'Restore', 'wpforms-lite' ), 'delete' => esc_html__( 'Delete Permanently', 'wpforms-lite' ), ]; } else { $items = [ 'trash' => esc_html__( 'Move to Trash', 'wpforms-lite' ), ]; } } // phpcs:disable WPForms.Comments.ParamTagHooks.InvalidParamTagsQuantity /** * Filters the Bulk Actions dropdown items. * * @since 1.7.5 * * @param array $items Dropdown items. */ $items = apply_filters( 'wpforms_admin_forms_bulk_actions_get_dropdown_items', $items ); // phpcs:enable WPForms.Comments.ParamTagHooks.InvalidParamTagsQuantity if ( empty( $items ) ) { // We should have dummy item, otherwise, WP will hide the Bulk Actions Dropdown, // which is not good from a design point of view. return [ '' => '—', ]; } return $items; } /** * Admin notices. * * @since 1.7.3 */ public function notices() { // phpcs:disable WordPress.Security.NonceVerification $results = [ 'trashed' => ! empty( $_REQUEST['trashed'] ) ? sanitize_key( $_REQUEST['trashed'] ) : false, 'restored' => ! empty( $_REQUEST['restored'] ) ? sanitize_key( $_REQUEST['restored'] ) : false, 'deleted' => ! empty( $_REQUEST['deleted'] ) ? sanitize_key( $_REQUEST['deleted'] ) : false, 'duplicated' => ! empty( $_REQUEST['duplicated'] ) ? sanitize_key( $_REQUEST['duplicated'] ) : false, 'type' => ! empty( $_REQUEST['type'] ) ? sanitize_key( $_REQUEST['type'] ) : 'form', ]; // phpcs:enable WordPress.Security.NonceVerification // Display notice in case of error. if ( in_array( 'error', $results, true ) ) { Notice::add( esc_html__( 'Security check failed. Please try again.', 'wpforms-lite' ), 'error' ); return; } $this->notices_success( $results ); } /** * Admin success notices. * * @since 1.7.3 * * @param array $results Action results data. */ private function notices_success( array $results ) { $type = $results['type'] ?? ''; if ( ! in_array( $type, [ 'form', 'template' ], true ) ) { return; } $method = "get_notice_success_for_{$type}"; $actions = [ 'trashed', 'restored', 'deleted', 'duplicated' ]; foreach ( $actions as $action ) { $count = (int) $results[ $action ]; if ( ! $count ) { continue; } $notice = $this->$method( $action, $count ); if ( ! $notice ) { continue; } Notice::add( $notice, 'info' ); } } /** * Remove certain arguments from a query string that WordPress should always hide for users. * * @since 1.7.3 * * @param array $removable_query_args An array of parameters to remove from the URL. * * @return array Extended/filtered array of parameters to remove from the URL. */ public function removable_query_args( $removable_query_args ) { $removable_query_args[] = 'trashed'; $removable_query_args[] = 'restored'; $removable_query_args[] = 'deleted'; $removable_query_args[] = 'duplicated'; return $removable_query_args; } /** * Get notice success message for form. * * @since 1.9.2.3 * * @param string $action Action type. * @param int $count Count of forms. * * @return string * @noinspection PhpUnusedPrivateMethodInspection */ private function get_notice_success_for_form( string $action, int $count ): string { switch ( $action ) { case 'restored': /* translators: %1$d - restored forms count. */ $notice = _n( '%1$d form was successfully restored.', '%1$d forms were successfully restored.', $count, 'wpforms-lite' ); break; case 'deleted': /* translators: %1$d - deleted forms count. */ $notice = _n( '%1$d form was successfully permanently deleted.', '%1$d forms were successfully permanently deleted.', $count, 'wpforms-lite' ); break; case 'duplicated': /* translators: %1$d - duplicated forms count. */ $notice = _n( '%1$d form was successfully duplicated.', '%1$d forms were successfully duplicated.', $count, 'wpforms-lite' ); break; case 'trashed': /* translators: %1$d - trashed forms count. */ $notice = _n( '%1$d form was successfully moved to Trash.', '%1$d forms were successfully moved to Trash.', $count, 'wpforms-lite' ); break; default: // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.AddEmptyLineBeforeReturnStatement return ''; } return sprintf( $notice, $count ); } /** * Get notice success message for template. * * @since 1.9.2.3 * * @param string $action Action type. * @param int $count Count of forms. * * @return string * @noinspection PhpUnusedPrivateMethodInspection */ private function get_notice_success_for_template( string $action, int $count ): string { switch ( $action ) { case 'restored': /* translators: %1$d - restored templates count. */ $notice = _n( '%1$d template was successfully restored.', '%1$d templates were successfully restored.', $count, 'wpforms-lite' ); break; case 'deleted': /* translators: %1$d - deleted templates count. */ $notice = _n( '%1$d template was successfully permanently deleted.', '%1$d templates were successfully permanently deleted.', $count, 'wpforms-lite' ); break; case 'duplicated': /* translators: %1$d - duplicated templates count. */ $notice = _n( '%1$d template was successfully duplicated.', '%1$d templates were successfully duplicated.', $count, 'wpforms-lite' ); break; case 'trashed': /* translators: %1$d - trashed templates count. */ $notice = _n( '%1$d template was successfully moved to Trash.', '%1$d templates were successfully moved to Trash.', $count, 'wpforms-lite' ); break; default: // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.AddEmptyLineBeforeReturnStatement return ''; } return sprintf( $notice, $count ); } } Base/Tables/Facades/ColumnsBase.php 0000644 00000003073 15252506741 0013114 0 ustar 00 <?php namespace WPForms\Admin\Base\Tables\Facades; /** * Column facade class. * * Hides the complexity of columns' collection behind a simple interface. * * @since 1.8.6 */ abstract class ColumnsBase { /** * Get columns. * * Returns all possible columns. * * @since 1.8.6 * * @return array Array of columns as objects. */ protected static function get_all(): array { return []; } /** * Get columns' keys for the columns which user selected to be displayed. * * It returns an array of keys in the order they should be displayed. * It returns draggable and non-draggable columns. * * @since 1.8.6 * * @return array */ public static function get_selected_columns_keys(): array { return []; } /** * Check if the form has selected columns. * * @since 1.8.6 * * @return bool */ public static function has_selected_columns(): bool { return ! empty( static::get_selected_columns_keys() ); } /** * Get columns' keys for the columns which the user has not selected to be displayed. * * It returns draggable and non-draggable columns. * * @since 1.8.6 * * @return array */ public static function get_not_selected_columns_keys(): array { $selected = static::get_selected_columns_keys(); $all = array_keys( static::get_all() ); return array_diff( $all, $selected ); } /** * Validate column key. * * @since 1.8.6 * * @param string|int $key Column key. * * @return bool */ public static function validate_column_key( $key ): bool { return isset( static::get_all()[ $key ] ); } } Base/Tables/DataObjects/ColumnBase.php 0000644 00000004157 15252506741 0013572 0 ustar 00 <?php namespace WPForms\Admin\Base\Tables\DataObjects; /** * Column data object base class. * * @since 1.8.6 */ abstract class ColumnBase { /** * Column ID. * * @since 1.8.6 * * @var string|int */ protected $id; /** * Column label. * * @since 1.8.6 * * @var string */ protected $label; /** * Label HTML markup. * * @since 1.8.6 * * @var string */ protected $label_html; /** * Is column draggable. * * @since 1.8.6 * * @var bool */ protected $is_draggable; /** * Column type. * * @since 1.8.6 * * @var string */ protected $type; /** * Is column readonly. * * @since 1.8.6 * * @var bool */ protected $readonly; /** * Column constructor. * * @since 1.8.6 * * @param int|string $id Column ID. * @param array $settings Column settings. */ public function __construct( $id, array $settings ) { $this->id = $id; $this->label = $settings['label'] ?? ''; $this->label_html = empty( $settings['label_html'] ) ? $this->label : $settings['label_html']; $this->is_draggable = $settings['draggable'] ?? true; $this->type = empty( $settings['type'] ) ? $id : $settings['type']; $this->readonly = $settings['readonly'] ?? false; } /** * Get column ID. * * @since 1.8.6 * * @return string|int */ public function get_id() { return $this->id; } /** * Get column label. * * @since 1.8.6 * * @return string */ public function get_label(): string { return $this->label; } /** * Get column label HTML. * * @since 1.8.6 * * @return string */ public function get_label_html(): string { return $this->label_html; } /** * Get the column type. * * @since 1.8.6 * * @return string */ public function get_type(): string { return $this->type; } /** * Is column draggable. * * @since 1.8.6 * * @return bool */ public function is_draggable(): bool { return $this->is_draggable; } /** * Is column readonly. * * @since 1.8.6 * * @return bool */ public function is_readonly(): bool { return $this->readonly; } } Blocks/Links.php 0000644 00000023721 15252506741 0007566 0 ustar 00 <?php namespace WPForms\Admin\Blocks; /** * Class for rendering links in the admin area. * * @since 1.9.7 */ class Links { /** * Render links. * * @since 1.9.7 * * @param array $utm_params UTM parameters to append to links. */ public static function render( array $utm_params ): void { $links = self::get_links( $utm_params ); echo '<div class="wpforms-links">'; foreach ( $links as $slug => $link ) { self::render_link( $slug, $link ); } echo '</div>'; } /** * Render a single link. * * @since 1.9.7 * * @param string $slug The slug for the link. * @param array $link The link data. */ private static function render_link( string $slug, array $link ): void { $url = $link['url'] ?? '#'; $text = $link['text'] ?? ''; $class = $link['class'] ?? ''; $target = $link['target'] ?? '_self'; $icon = $link['icon'] ?? ''; printf( '<a href="%1$s" target="%2$s" rel="noopener noreferrer" class="wpforms-link wpforms-link-%3$s %4$s">%6$s%5$s</a>', esc_url( $url ), esc_attr( $target ), esc_attr( $slug ), esc_attr( $class ), esc_html( $text ), wp_kses( $icon, [ 'svg' => [ 'xmlns' => true, 'width' => true, 'height' => true, 'viewbox' => true, 'fill' => true, ], 'path' => [ 'd' => true, 'fill' => true, ], ] ) ); } /** * Get links. * * @since 1.9.7 * * @param array $utm_params UTM parameters to append to links. * * @return array */ private static function get_links( array $utm_params ): array { return [ 'docs' => [ 'url' => self::get_utm_link( 'https://wpforms.com/docs/', $utm_params['docs'] ?? [] ), 'text' => esc_html__( 'Docs', 'wpforms-lite' ), 'target' => '_blank', 'icon' => '<svg xmlns="http://www.w3.org/2000/svg" width="13" height="17" viewBox="0 0 13 17" fill="none"><path d="M2.33765 14.5854H10.3376C10.5876 14.5854 10.8376 14.3667 10.8376 14.0854V5.08545H8.33765C7.77515 5.08545 7.33765 4.64795 7.33765 4.08545V1.58545H2.33765C2.0564 1.58545 1.83765 1.83545 1.83765 2.08545V14.0854C1.83765 14.3667 2.0564 14.5854 2.33765 14.5854ZM2.33765 0.0854492H7.4939C8.02515 0.0854492 8.52515 0.304199 8.90015 0.679199L11.7439 3.52295C12.1189 3.89795 12.3376 4.39795 12.3376 4.9292V14.0854C12.3376 15.2104 11.4314 16.0854 10.3376 16.0854H2.33765C1.21265 16.0854 0.337646 15.2104 0.337646 14.0854V2.08545C0.337646 0.991699 1.21265 0.0854492 2.33765 0.0854492ZM4.08765 8.08545H8.58765C8.9939 8.08545 9.33765 8.4292 9.33765 8.83545C9.33765 9.27295 8.9939 9.58545 8.58765 9.58545H4.08765C3.65015 9.58545 3.33765 9.27295 3.33765 8.83545C3.33765 8.4292 3.65015 8.08545 4.08765 8.08545ZM4.08765 11.0854H8.58765C8.9939 11.0854 9.33765 11.4292 9.33765 11.8354C9.33765 12.2729 8.9939 12.5854 8.58765 12.5854H4.08765C3.65015 12.5854 3.33765 12.2729 3.33765 11.8354C3.33765 11.4292 3.65015 11.0854 4.08765 11.0854Z" fill="#646970"/></svg>', ], 'videos' => [ 'url' => 'https://www.youtube.com/@wpforms/videos', 'text' => esc_html__( 'Videos', 'wpforms-lite' ), 'target' => '_blank', 'icon' => '<svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 17 17" fill="none"><path d="M14.8376 8.06982C14.8376 5.75732 13.5876 3.63232 11.5876 2.44482C9.5564 1.28857 7.08765 1.28857 5.08765 2.44482C3.0564 3.63232 1.83765 5.75732 1.83765 8.06982C1.83765 10.4136 3.0564 12.5386 5.08765 13.7261C7.08765 14.8823 9.5564 14.8823 11.5876 13.7261C13.5876 12.5386 14.8376 10.4136 14.8376 8.06982ZM0.337646 8.06982C0.337646 5.22607 1.83765 2.60107 4.33765 1.16357C6.8064 -0.273926 9.83765 -0.273926 12.3376 1.16357C14.8064 2.60107 16.3376 5.22607 16.3376 8.06982C16.3376 10.9448 14.8064 13.5698 12.3376 15.0073C9.83765 16.4448 6.8064 16.4448 4.33765 15.0073C1.83765 13.5698 0.337646 10.9448 0.337646 8.06982ZM6.21265 4.69482C6.4314 4.53857 6.7439 4.53857 6.96265 4.69482L11.4626 7.44482C11.6814 7.56982 11.8376 7.81982 11.8376 8.10107C11.8376 8.35107 11.6814 8.60107 11.4626 8.72607L6.96265 11.4761C6.7439 11.6323 6.4314 11.6323 6.21265 11.5073C5.96265 11.3511 5.83765 11.1011 5.83765 10.8511V5.35107C5.83765 5.06982 5.96265 4.81982 6.21265 4.69482Z" fill="#646970"/></svg>', ], 'support' => [ 'url' => wpforms()->is_pro() ? self::get_utm_link( 'https://wpforms.com/account/support/', $utm_params['support'] ?? [] ) : 'https://wordpress.org/support/plugin/wpforms-lite/', 'text' => wpforms()->is_pro() ? esc_html__( 'Support', 'wpforms-lite' ) : esc_html__( 'Support Forum', 'wpforms-lite' ), 'target' => '_blank', 'icon' => wpforms()->is_pro() ? '<svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 17 17" fill="none"><path d="M14.8376 8.06982C14.8376 5.75732 13.5876 3.63232 11.5876 2.44482C9.5564 1.28857 7.08765 1.28857 5.08765 2.44482C3.0564 3.63232 1.83765 5.75732 1.83765 8.06982C1.83765 10.4136 3.0564 12.5386 5.08765 13.7261C7.08765 14.8823 9.5564 14.8823 11.5876 13.7261C13.5876 12.5386 14.8376 10.4136 14.8376 8.06982ZM0.337646 8.06982C0.337646 5.22607 1.83765 2.60107 4.33765 1.16357C6.8064 -0.273926 9.83765 -0.273926 12.3376 1.16357C14.8064 2.60107 16.3376 5.22607 16.3376 8.06982C16.3376 10.9448 14.8064 13.5698 12.3376 15.0073C9.83765 16.4448 6.8064 16.4448 4.33765 15.0073C1.83765 13.5698 0.337646 10.9448 0.337646 8.06982ZM5.6189 5.25732C5.8689 4.53857 6.52515 4.06982 7.27515 4.06982H9.08765C10.1814 4.06982 11.0876 4.97607 11.0876 6.06982C11.0876 6.75732 10.6814 7.41357 10.0876 7.75732L9.08765 8.35107C9.0564 8.75732 8.7439 9.06982 8.33765 9.06982C7.90015 9.06982 7.58765 8.75732 7.58765 8.31982V7.91357C7.58765 7.63232 7.71265 7.38232 7.96265 7.25732L9.33765 6.47607C9.4939 6.38232 9.58765 6.22607 9.58765 6.06982C9.58765 5.78857 9.3689 5.60107 9.08765 5.60107H7.27515C7.1814 5.60107 7.08765 5.66357 7.0564 5.75732L7.02515 5.78857C6.90015 6.19482 6.46265 6.38232 6.08765 6.25732C5.6814 6.10107 5.4939 5.66357 5.6189 5.28857V5.25732ZM7.33765 11.0698C7.33765 10.5386 7.77515 10.0698 8.33765 10.0698C8.8689 10.0698 9.33765 10.5386 9.33765 11.0698C9.33765 11.6323 8.8689 12.0698 8.33765 12.0698C7.77515 12.0698 7.33765 11.6323 7.33765 11.0698Z" fill="#646970"/></svg>' : '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="16" viewBox="0 0 20 16" fill="none"><path d="M3.09554 9.83838C3.06625 9.89697 3.03695 9.95557 2.97836 9.98486C2.91976 10.1313 2.83187 10.2485 2.77328 10.395C3.27133 10.2778 3.74008 10.0728 4.20883 9.86768C4.35531 9.80908 4.4725 9.75049 4.58968 9.69189C4.88265 9.54541 5.17562 9.51611 5.49789 9.57471C5.84945 9.6333 6.20101 9.6626 6.61117 9.6626C9.42367 9.6626 11.2987 7.7876 11.2987 5.9126C11.2987 4.06689 9.42367 2.1626 6.61117 2.1626C3.76937 2.1626 1.92367 4.06689 1.92367 5.9126C1.92367 6.73291 2.24593 7.52393 2.86117 8.19775C3.30062 8.63721 3.38851 9.28174 3.09554 9.83838ZM6.61117 11.0688C6.11312 11.0688 5.67367 11.0396 5.20492 10.9517C5.08773 11.0103 4.94125 11.0981 4.79476 11.1567C3.74008 11.6255 2.48031 12.0063 1.22054 12.0063C0.927575 12.0063 0.663904 11.8599 0.546716 11.5962C0.458825 11.3325 0.488122 11.0396 0.6932 10.8345C1.10336 10.3657 1.45492 9.83838 1.77718 9.31104C1.80648 9.25244 1.83578 9.19385 1.83578 9.16455C1.01547 8.28564 0.517419 7.14307 0.517419 5.9126C0.517419 3.0708 3.24203 0.756348 6.61117 0.756348C9.95101 0.756348 12.7049 3.0708 12.7049 5.9126C12.7049 8.78369 9.95101 11.0688 6.61117 11.0688ZM13.1737 14.8188C10.7713 14.8188 8.69125 13.647 7.69515 11.9478C8.1932 11.8599 8.69125 11.7427 9.16 11.5962C9.92172 12.6509 11.3573 13.4126 13.1444 13.4126C13.5252 13.4126 13.9061 13.3833 14.2577 13.3247C14.5799 13.2661 14.8729 13.2954 15.1659 13.4419C15.283 13.5005 15.4002 13.5591 15.5467 13.6177C16.0155 13.8228 16.4842 13.9985 16.9823 14.145C16.9237 13.9985 16.8358 13.8813 16.7772 13.7349C16.7479 13.6763 16.6893 13.6177 16.66 13.5591C16.3963 13.0317 16.4842 12.3872 16.8944 11.9478C17.5096 11.2739 17.8612 10.4829 17.8612 9.6626C17.8612 7.93408 16.1912 6.14697 13.6424 5.94189V5.9126C13.6424 5.44385 13.5545 4.9751 13.4373 4.53564C16.6893 4.65283 19.2674 6.90869 19.2674 9.6626C19.2674 10.8931 18.7401 12.0356 17.9198 12.9146C17.9198 12.9731 17.9491 13.0024 17.9784 13.061C18.3006 13.5884 18.6522 14.1157 19.0623 14.5845C19.2674 14.7896 19.2967 15.0825 19.2088 15.3462C19.0916 15.6099 18.828 15.7563 18.5643 15.7563C17.3045 15.7563 16.0155 15.3755 14.9608 14.9067C14.8143 14.8481 14.6678 14.7603 14.5506 14.7017C14.0819 14.7896 13.6424 14.8188 13.1737 14.8188Z" fill="#50575E"/></svg>', ], 'whats-new' => [ 'text' => esc_html__( 'What’s New', 'wpforms-lite' ), 'class' => 'wpforms-splash-modal-open', 'icon' => '<svg xmlns="http://www.w3.org/2000/svg" width="17" height="20" viewBox="0 0 17 20" fill="none"><path d="M13.6014 1.63137L14.7985 6.09878C15.4146 6.22486 16.0232 6.80589 16.2497 7.65107C16.4762 8.49626 16.2477 9.33393 15.7771 9.75119L16.9661 14.1884C17.0712 14.5808 16.9268 15.0077 16.605 15.2557C16.2833 15.5037 15.8364 15.5264 15.4919 15.3275L13.8079 14.3552C11.9708 13.2946 9.79038 13.0053 7.73779 13.5553L7.4963 13.62L8.53158 17.4837C8.67717 18.027 8.33762 18.571 7.82447 18.7085L5.89262 19.2261C5.34929 19.3717 4.81346 19.0623 4.66788 18.519L3.6326 14.6553C2.54594 14.9465 1.47428 14.3277 1.18311 13.2411L0.406655 10.3433C0.123572 9.28681 0.734202 8.18497 1.82087 7.8938L5.92605 6.79382C7.97865 6.24383 9.7223 4.9031 10.783 3.06598L11.7633 1.41214C11.9622 1.06769 12.3605 0.863896 12.7632 0.917765C13.1659 0.971634 13.5044 1.26915 13.6014 1.63137ZM12.2924 4.47327C10.9482 6.58047 8.85851 8.07862 6.44369 8.72567L6.20221 8.79037L6.97867 11.6882L7.22015 11.6234C9.63496 10.9764 12.2019 11.2592 14.4195 12.412L12.2924 4.47327Z" fill="#646970"/></svg>', ], ]; } /** * Get UTM link. * * @since 1.9.7 * * @param string $url The URL to which UTM parameters will be added. * @param array $utm_params UTM parameters to append to the URL. * * @return string */ private static function get_utm_link( string $url, array $utm_params ): string { return wpforms_utm_link( $url, $utm_params['medium'] ?? '', $utm_params['content'] ?? '', $utm_params['term'] ?? '' ); } } Traits/HasScreenOptions.php 0000644 00000011655 15252506741 0011771 0 ustar 00 <?php namespace WPForms\Admin\Traits; /** * Enables screen options for the current screen. * * @since 1.8.8 */ trait HasScreenOptions { /** * A configuration array for the screen options. * * @var array Array of screen options. * * @since 1.8.8 */ private $screen_options; /** * Screen options ID. * * @var string Screen options ID. * * @since 1.8.8 */ private $screen_options_id; /** * Initialize the screen options. * * This method should be called during init of the class that uses this trait. * If the class itself is allowed to load, it should set $allowed to true. * * @param bool $allowed Whether to allow screen options or not. * * @since 1.8.8 */ public function init_screen_options( bool $allowed = false ) { // This should always run. $this->filter_screen_options(); if ( ! $allowed ) { return; } add_action( 'admin_head', [ $this, 'add_screen_options' ] ); add_filter( 'screen_settings', [ $this, 'render_screen_options' ], 10, 2 ); add_filter( "set_screen_option_{$this->screen_options_id}", [ $this, 'save_screen_options' ], 10, 2 ); add_filter( 'screen_options_show_submit', '__return_true' ); } /** * Filter screen options. * * @since 1.8.8 */ public function filter_screen_options() { $options = get_user_option( $this->screen_options_id ); foreach ( $this->screen_options as $group => $options_group ) { foreach ( $options_group['options'] as $option ) { add_filter( "get_user_option_{$this->screen_options_id}_{$group}_{$option['option']}", function () use ( $option, $group, $options ) { $key = $group . '_' . $option['option']; return $options[ $key ] ?? $option['default']; } ); } } } /** * Configure screen options. * * @since 1.8.8 */ public function add_screen_options() { foreach ( $this->screen_options as $group => $options_group ) { foreach ( $options_group['options'] as $option ) { add_screen_option( $group . '_' . $option['option'], [ 'label' => $option['label'], 'option' => $option['option'], 'default' => $option['default'], ] ); } } } /** * Save screen options. * * @since 1.8.8 * * @param mixed $status Status of the screen option. * @param string $option Option name. * * @return mixed */ public function save_screen_options( $status, $option ) { // phpcs:ignore Generic.Metrics.NestingLevel.MaxExceeded if ( $option === $this->screen_options_id ) { $value = []; foreach ( $this->screen_options as $group => $options_group ) { $options = $options_group['options']; foreach ( $options as $group_option ) { $key = $group . '_' . $group_option['option']; if ( ! isset( $_POST[ $key ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing $value[ $key ] = false; continue; } $value[ $key ] = ! empty( $_POST[ $key ] ) ? sanitize_text_field( wp_unslash( $_POST[ $key ] ) ) : $group_option['default']; // phpcs:ignore WordPress.Security.NonceVerification.Missing } } return $value; } return $status; } /** * Render screen options. * * @since 1.8.8 * * @param string $screen_settings HTML markup of custom screen settings. * * @return string */ public function render_screen_options( $screen_settings ) { // phpcs:ignore Generic.Metrics.NestingLevel.MaxExceeded foreach ( $this->screen_options as $group => $options_group ) { $screen_settings .= '<fieldset>'; $screen_settings .= '<legend>' . esc_html( $options_group['heading'] ) . '</legend>'; foreach ( $options_group['options'] as $option ) { $option_value = get_user_option( "{$this->screen_options_id}_{$group}_{$option['option']}" ); $key = $group . '_' . $option['option']; switch ( $option['type'] ) { case 'checkbox': $screen_settings .= sprintf( '<label> <input type="checkbox" id="%1$s" name="%1$s" value="1" %2$s>%3$s </label>', $key, checked( (bool) $option_value, true, false ), esc_html( $option['label'] ) ); break; case 'number': $screen_settings .= sprintf( '<label for="%1$s">%2$s</label> <input type="number" id="%1$s" name="%1$s" value="%3$s" %4$s>', $key, esc_html( $option['label'] ), esc_attr( $option_value ), wpforms_html_attributes( '', [], [], $option['args'] ?? [] ) ); break; case 'text': $screen_settings .= sprintf( '<label for="%1$s">%2$s</label> <input type="text" id="%1$s" name="%1$s" value="%3$s">', $key, esc_html( $option['label'] ), esc_attr( $option_value ) ); break; } } $screen_settings .= sprintf( '<input name="wp_screen_options[option]" type="hidden" value="%s"><input name="wp_screen_options[value]" type="hidden" value="">', $this->screen_options_id ); $screen_settings .= '</fieldset>'; } return $screen_settings; } } Traits/FormTemplates.php 0000644 00000060355 15252506741 0011325 0 ustar 00 <?php namespace WPForms\Admin\Traits; use WPForms\Admin\Addons\Addons; use WPForms\Admin\Builder\Templates; /** * Form Templates trait. * * @since 1.7.7 */ trait FormTemplates { /** * Addons data handler class instance. * * @since 1.7.7 * * @var Addons */ private $addons_obj; /** * Is addon templates available? * * @since 1.7.7 * * @var bool */ private $is_addon_templates_available = false; /** * Is custom templates available? * * @since 1.7.7 * * @var bool */ private $is_custom_templates_available = false; /** * Prepared templates list. * * @since 1.7.7 * * @var array */ private $prepared_templates = []; /** * Output templates content section. * * @since 1.7.7 */ private function output_templates_content(): void { $templates_obj = wpforms()->obj( 'builder_templates' ); if ( ! $templates_obj ) { return; } $templates_hash = $templates_obj->get_hash(); $templates_hash_option = get_option( Templates::TEMPLATES_HASH_OPTION, '' ); $cache_obj = wpforms()->obj( 'builder_templates_cache' ); // Compare the current hash and the previous one to detect changes in the template list. if ( $cache_obj && $templates_hash !== $templates_hash_option ) { // Update the hash in the option. update_option( Templates::TEMPLATES_HASH_OPTION, $templates_hash ); // Wipe all caches - for the admin page, Form Builder, and infinite scroll. $cache_obj->clear_all(); } // Check if infinite scroll is enabled. if ( Templates::is_infinite_scroll_enabled() ) { // Generate content with the initial batch only. // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo $this->generate_templates_content_with_infinite_scroll(); return; } // Attempt to get cached content. $content = $cache_obj ? $cache_obj->get_content_cache() : ''; if ( empty( $content ) ) { $content = $this->generate_templates_content_cache(); } // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo $content; } /** * Get the templates wrapper HTML with shared sidebar and structure. * * @since 2.0.0 * * @param string $list_content Inner HTML for the .list div. * @param string $list_attributes Optional data attributes string for #wpforms-setup-templates-list. * @param bool $include_loading Whether to include the loading indicator. * * @return string HTML content. */ private function get_templates_wrapper_html( string $list_content, string $list_attributes = '', bool $include_loading = false ): string { ob_start(); ?> <div class="wpforms-setup-templates"> <div class="wpforms-setup-templates-sidebar"> <div class="wpforms-setup-templates-search-wrap"> <i class="fa fa-search"></i> <label> <input type="text" id="wpforms-setup-template-search" value="" placeholder="<?php esc_attr_e( 'Search Templates', 'wpforms-lite' ); ?>"> </label> </div> <ul class="wpforms-setup-templates-categories"> <?php $this->template_categories(); ?> </ul> </div> <div id="wpforms-setup-templates-list"<?php echo $list_attributes !== '' ? ' ' . $list_attributes : ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>> <div class="list"> <?php echo $list_content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> </div> <?php if ( $include_loading ) : ?> <div class="wpforms-templates-loading" style="display:none;"> <p> <i class="wpforms-loading-spinner wpforms-loading-inline" aria-hidden="true"></i> <?php esc_html_e( 'Loading more templates...', 'wpforms-lite' ); ?> </p> </div> <?php endif; ?> <div class="wpforms-templates-no-results"> <p> <?php esc_html_e( "Sorry, we didn't find any templates that match your criteria.", 'wpforms-lite' ); ?> </p> </div> <div class="wpforms-user-templates-empty-state wpforms-hidden"> <?php $this->user_template_empty_state(); ?> </div> </div> </div> <?php return ob_get_clean(); } /** * Generate and save cached templates content. * * @since 1.8.6 * * @retur string */ public function generate_templates_content_cache() { $this->prepare_templates_data(); ob_start(); $this->template_select_options(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped $list_content = ob_get_clean(); $content = $this->get_templates_wrapper_html( $list_content ); wpforms()->obj( 'builder_templates_cache' )->save_content_cache( $content ); return $content; } /** * Prepare templates data for output. * * @since 1.7.7 */ private function prepare_templates_data() { $templates = wpforms()->obj( 'builder_templates' )->get_templates(); if ( empty( $templates ) ) { return; } wpforms()->obj( 'builder_templates' )->update_favorites_list(); // Loop through each available template. foreach ( $templates as $id => $template ) { $this->prepared_templates[ $id ] = $this->prepare_template_render_arguments( $template ); } } /** * Generate and display categories menu. * * @since 1.7.7 */ private function template_categories() { $templates_count = $this->get_count_in_categories(); $generic_categories = [ 'all' => esc_html__( 'All Templates', 'wpforms-lite' ), ]; if ( isset( $templates_count['all'], $templates_count['available'] ) && $templates_count['all'] !== $templates_count['available'] ) { $generic_categories['available'] = esc_html__( 'Available Templates', 'wpforms-lite' ); } $generic_categories['favorites'] = esc_html__( 'Favorite Templates', 'wpforms-lite' ); $generic_categories['new'] = esc_html__( 'New Templates', 'wpforms-lite' ); $generic_categories['user'] = esc_html__( 'My Templates', 'wpforms-lite' ); $this->output_categories( $generic_categories, $templates_count ); printf( '<li class="divider"></li>' ); $common_categories = []; if ( $this->is_custom_templates_available ) { $common_categories['custom'] = esc_html__( 'Custom Templates', 'wpforms-lite' ); } if ( $this->is_addon_templates_available ) { $common_categories['addons'] = esc_html__( 'Addon Templates', 'wpforms-lite' ); } $categories = array_merge( $common_categories, wpforms()->obj( 'builder_templates' )->get_categories() ); $this->output_categories( $categories, $templates_count ); } /** * Output categories list. * * @since 1.7.7 * * @param array $categories Categories list. * @param array $templates_count Templates count by categories. * * @noinspection HtmlUnknownAttribute*/ private function output_categories( $categories, $templates_count ) { $all_subcategories = wpforms()->obj( 'builder_templates' )->get_subcategories(); foreach ( $categories as $slug => $name ) { $class = ''; if ( $slug === 'all' ) { $class = 'class="active"'; } elseif ( empty( $templates_count[ $slug ] ) && $slug !== 'user' ) { // WPForms user templates are always available. $class = 'class="wpforms-hidden"'; } $count = $templates_count[ $slug ] ?? '0'; printf( '<li data-category="%1$s" %2$s data-count="%4$s"><div>%3$s<span>%4$s</span><i class="fa fa-chevron-down chevron"></i></div>%5$s</li>', esc_attr( $slug ), $class, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped esc_html( $name ), esc_html( $count ), $this->output_subcategories( $all_subcategories, $slug, $templates_count['subcategories'] ) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ); } } /** * Output subcategories list. * * @since 1.8.4 * * @param array $all_subcategories Subcategories list. * @param array $parent_slug Parent category slug. * @param array $subcategories_count Subcategories count. */ private function output_subcategories( $all_subcategories, $parent_slug, $subcategories_count ) { $subcategories = []; $output = ''; foreach ( $all_subcategories as $subcategory_slug => $subcategory ) { if ( $subcategory['parent'] === $parent_slug ) { $subcategories[ $subcategory_slug ] = $subcategory; } } if ( ! empty( $subcategories ) ) { $output .= '<ul class="wpforms-setup-templates-subcategories">'; foreach ( $subcategories as $slug => $subcategory ) { $count = $subcategories_count[ $slug ] ?? '0'; $output .= sprintf( '<li data-subcategory="%1$s"><span>%2$s</span><span>%3$s</span></li>', esc_attr( $slug ), esc_html( $subcategory['name'] ), esc_html( $count ) ); } $output .= '</ul>'; } return $output; } /** * Generate a block of templates to choose from. * * @since 1.7.7 * * @param array $templates Deprecated. * @param string $slug Deprecated. * * @noinspection PhpMissingParamTypeInspection * @noinspection PhpUnusedParameterInspection */ public function template_select_options( $templates = [], $slug = '' ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed /** * Action hook before the list of form templates. * * @since 1.9.3 * * @param array $templates List of form templates. */ do_action( 'wpforms_admin_form_templates_list_before', $templates ); /** * Filter the number of templates to display. * * Useful for speeding up the setup panel loading while debugging. * * @since 1.9.2 * * @param int|bool $limit Number of templates to display. */ $limit = apply_filters( 'wpforms_builder_setup_templates_limit', false ); if ( $limit ) { $this->prepared_templates = array_slice( $this->prepared_templates, 0, (int) $limit, true ); } foreach ( $this->prepared_templates as $template ) { echo wpforms_render( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 'builder/templates-item', $template, true ); } } /** * Output user templates empty state. * * @since 1.8.8 */ private function user_template_empty_state() { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wpforms_render( 'admin/empty-states/no-user-templates' ); } /** * Prepare arguments for rendering template item. * * @since 1.7.7 * * @param array $template Template data. * * @return array Arguments. */ private function prepare_template_render_arguments( $template ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh $template['plugin_dir'] = $template['plugin_dir'] ?? ''; $template['source'] = $this->get_template_source( $template ); $template['url'] = ! empty( $template['url'] ) ? $template['url'] : ''; $template['has_access'] = ! empty( $template['license'] ) ? $template['has_access'] : true; $template['favorite'] = $template['favorite'] ?? wpforms()->obj( 'builder_templates' )->is_favorite( $template['slug'] ); $args = []; $args['template_id'] = ! empty( $template['id'] ) ? $template['id'] : $template['slug']; $args['categories'] = $this->get_template_categories( $template ); $args['subcategories'] = $this->get_template_subcategories( $template ); $args['fields'] = $this->get_template_fields( $template ); $args['demo_url'] = ''; if ( ! empty( $template['url'] ) ) { $medium = wpforms_is_admin_page( 'templates' ) ? 'Form Templates Subpage' : 'builder-templates'; $args['demo_url'] = wpforms_utm_link( $template['url'], $medium, $template['name'] ); } $template_license = ! empty( $template['license'] ) ? $template['license'] : ''; $template_name = sprintf( /* translators: %s - form template name. */ esc_html__( '%s template', 'wpforms-lite' ), esc_html( $template['name'] ) ); $args['badge_text'] = ''; $args['license_class'] = ''; $args['education_class'] = ''; $args['education_attributes'] = ''; if ( $template['source'] === 'wpforms-addon' ) { $args['badge_text'] = esc_html__( 'Addon', 'wpforms-lite' ); // At least one addon template available. $this->is_addon_templates_available = true; } if ( $template['source'] === 'wpforms-custom' ) { $args['badge_text'] = esc_html__( 'Custom', 'wpforms-lite' ); // At least one custom template available. $this->is_custom_templates_available = true; } $args['create_url'] = ''; $args['edit_url'] = ''; $args['edit_action_text'] = ''; $args['is_open'] = false; $args['can_create'] = wpforms_current_user_can( 'create_forms' ); $args['can_edit'] = wpforms_current_user_can( 'edit_forms' ); $args['can_delete'] = wpforms_current_user_can( 'delete_forms' ); $args['post_id'] = ! empty( $template['post_id'] ) ? $template['post_id'] : ''; if ( $template['source'] === 'wpforms-user-template' ) { $args['create_url'] = esc_url( $template['create_url'] ); $args['edit_url'] = esc_url( $template['edit_url'] ); $args['edit_action_text'] = $template['edit_action_text']; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $args['is_open'] = wpforms_is_admin_page( 'builder' ) && isset( $_GET['form_id'] ) && (int) $_GET['form_id'] === $template['post_id']; $ownership = get_current_user_id() === (int) get_post_field( 'post_author', $args['post_id'] ) ? 'own' : 'others'; $args['can_edit'] = wpforms_current_user_can( "edit_{$ownership}_forms", $args['post_id'] ); $args['can_delete'] = wpforms_current_user_can( "delete_{$ownership}_forms", $args['post_id'] ); } $args['action_text'] = $this->get_action_button_text( $template ); if ( empty( $template['has_access'] ) ) { $args['license_class'] = ' pro'; $args['badge_text'] = $template_license; $args['education_class'] = ' education-modal'; $args['education_attributes'] = sprintf( ' data-name="%1$s" data-license="%2$s" data-action="upgrade"', esc_attr( $template_name ), esc_attr( $template_license ) ); } $args['addons_attributes'] = $this->prepare_addons_attributes( $template ); $args['selected'] = ! empty( $this->form_data['meta']['template'] ) && $this->form_data['meta']['template'] === $args['template_id']; $args['badge_class'] = ! empty( $args['badge_text'] ) ? ' badge' : ''; $args['template'] = $template; return $args; } /** * Get action button text. * * @since 1.7.7 * * @param array $template Template data. * * @return string */ private function get_action_button_text( $template ) { if ( ! empty( $template['action_text'] ) ) { return $template['action_text']; } if ( $template['slug'] === 'blank' ) { return __( 'Create Blank Form', 'wpforms-lite' ); } if ( wpforms_is_admin_page( 'templates' ) ) { return __( 'Create Form', 'wpforms-lite' ); } return __( 'Use Template', 'wpforms-lite' ); } /** * Generate addon attributes. * * @since 1.7.7 * * @param array $template Template data. * * @return string Addon attributes. */ private function prepare_addons_attributes( $template ) { $addons_attributes = ''; $required_addons = false; $already_installed = []; if ( ! empty( $template['addons'] ) && is_array( $template['addons'] ) ) { $required_addons = $this->addons_obj->get_by_slugs( $template['addons'] ); foreach ( $required_addons as $i => $addon ) { if ( ! isset( $addon['action'], $addon['title'], $addon['slug'] ) || ! in_array( $addon['action'], [ 'install', 'activate' ], true ) ) { unset( $required_addons[ $i ] ); } if ( $addon['action'] === 'activate' ) { $already_installed[] = $addon['slug']; } } } if ( ! empty( $required_addons ) ) { $addons_names = implode( ', ', wp_list_pluck( $required_addons, 'title' ) ); $addons_slugs = implode( ',', wp_list_pluck( $required_addons, 'slug' ) ); $installed_slugs = implode( ',', $already_installed ); $addons_attributes = sprintf( ' data-addons-names="%1$s" data-addons="%2$s" data-installed="%3$s"', esc_attr( $addons_names ), esc_attr( $addons_slugs ), esc_attr( $installed_slugs ) ); } return $addons_attributes; } /** * Determine a template source. * * @since 1.7.7 * * @param array $template Template data. * * @return string Template source. */ private function get_template_source( $template ) { if ( ! empty( $template['source'] ) ) { return $template['source']; } $source = 'wpforms-addon'; static $addons = null; if ( $addons === null ) { $addons = array_keys( $this->addons_obj->get_all() ); } if ( $template['plugin_dir'] === 'wpforms' || $template['plugin_dir'] === 'wpforms-lite' ) { $source = 'wpforms-core'; } if ( $source !== 'wpforms-core' && ! in_array( $template['plugin_dir'], $addons, true ) ) { $source = 'wpforms-custom'; } return $source; } /** * Determine template categories. * * @since 1.7.7 * * @param array $template Template data. * * @return string Template categories coma separated. */ private function get_template_categories( $template ) { $categories = ! empty( $template['categories'] ) ? (array) $template['categories'] : []; $source = $this->get_template_source( $template ); if ( $source === 'wpforms-addon' ) { $categories[] = 'addons'; } if ( $source === 'wpforms-custom' ) { $categories[] = 'custom'; } if ( isset( $template['created_at'] ) && strtotime( $template['created_at'] ) > strtotime( '-3 Months' ) ) { $categories[] = 'new'; } return implode( ',', $categories ); } /** * Determine template subcategories. * * @since 1.8.4 * * @param array $template Template data. * * @return string Template subcategories coma separated. */ private function get_template_subcategories( $template ) { $subcategories = ! empty( $template['subcategories'] ) ? (array) $template['subcategories'] : []; $subcategories = array_keys( $subcategories ); return implode( ',', $subcategories ); } /** * Determine template fields. * * @since 1.8.6 * * @param array $template Template data. * * @return string Template fields, comma separated. */ private function get_template_fields( array $template ): string { $fields = ! empty( $template['fields'] ) ? (array) $template['fields'] : []; /** * Filter template fields. * * @since 1.8.6 * * @param array $fields Template fields. */ $fields = (array) apply_filters( 'wpforms_setup_template_fields', $fields ); return implode( ',', $fields ); } /** * Get categories templates count. * * @since 1.7.7 * * @return array */ private function get_count_in_categories() { $all_categories = []; $available_templates_count = 0; $favorites_templates_count = 0; $user_templates_count = 0; foreach ( $this->prepared_templates as $template_data ) { $template = $template_data['template']; $categories = explode( ',', $template_data['categories'] ); if ( $template['has_access'] ) { ++$available_templates_count; } if ( $template['favorite'] ) { ++$favorites_templates_count; } if ( $template['source'] === 'wpforms-user-template' ) { ++$user_templates_count; } if ( is_array( $categories ) ) { array_push( $all_categories, ...$categories ); continue; } $all_categories[] = $categories; } $categories_count = array_count_values( $all_categories ); $categories_count['all'] = count( $this->prepared_templates ); $categories_count['available'] = $available_templates_count; $categories_count['favorites'] = $favorites_templates_count; $categories_count['user'] = $user_templates_count; $categories_count['subcategories'] = $this->get_count_in_subcategories(); return $categories_count; } /** * Get subcategories templates count. * * @since 1.8.7 * * @return array */ private function get_count_in_subcategories(): array { $all_subcategories = []; foreach ( $this->prepared_templates as $template_data ) { $subcategories = explode( ',', $template_data['subcategories'] ); if ( is_array( $subcategories ) ) { array_push( $all_subcategories, ...$subcategories ); continue; } $all_subcategories[] = $subcategories; } return array_count_values( $all_subcategories ); } /** * Generate templates content with infinite scroll support. * * @since 2.0.0 * * @return string HTML content. */ private function generate_templates_content_with_infinite_scroll(): string { $this->prepare_templates_data(); $total_templates = count( $this->prepared_templates ); $initial_limit = 11; // Get the first batch of templates (11 because 1 additional item is added dynamically in JS). $initial_batch = array_slice( $this->prepared_templates, 0, $initial_limit ); ob_start(); /** This action is documented in src/Admin/Traits/FormTemplates.php. */ do_action( 'wpforms_admin_form_templates_list_before', [] ); foreach ( $initial_batch as $template ) { echo wpforms_render( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 'builder/templates-item', $template, true ); } $list_content = ob_get_clean(); $list_attributes = sprintf( 'data-total-templates="%d" data-loaded-templates="%d"', absint( $total_templates ), absint( count( $initial_batch ) ) ); return $this->get_templates_wrapper_html( $list_content, $list_attributes, true ); } /** * Get filtered templates with pagination. * * @since 2.0.0 * * @param int $offset Offset for pagination. * @param int $limit Number of templates to return. * @param string $category Category filter. * @param string $subcategory Subcategory filter. * @param string $search Search query. * @param bool $favorite Filter favorites only. * * @return array { * Filtered templates' data. * * @type array $templates Array of template data. * @type int $total Total matching templates. * @type bool $has_more More templates available. * } */ public function get_filtered_templates( int $offset = 0, int $limit = 12, string $category = 'all', string $subcategory = '', string $search = '', bool $favorite = false ): array { $filtered = []; // Apply filters to prepared templates. foreach ( $this->prepared_templates as $template_data ) { if ( ! $this->template_matches_filters( $template_data, $category, $subcategory, $search, $favorite ) ) { continue; } $filtered[] = $template_data; } $total = count( $filtered ); $slice = array_slice( $filtered, $offset, $limit ); $has_more = ( $offset + $limit ) < $total; return [ 'templates' => $slice, 'total' => $total, 'has_more' => $has_more, ]; } /** * Check if the template matches filters. * * @since 2.0.0 * * @param array $template_data Template data. * @param string $category Category filter. * @param string $subcategory Subcategory filter. * @param string $search Search query. * @param bool $favorite Favorites filter. * * @return bool True if the template matches all filters. */ private function template_matches_filters( array $template_data, string $category, string $subcategory, string $search, bool $favorite ): bool { // Favorite filter. if ( $favorite && empty( $template_data['template']['favorite'] ) ) { return false; } // Category filter. if ( $category !== 'all' ) { $categories = explode( ',', $template_data['categories'] ); if ( $category === 'available' && empty( $template_data['template']['has_access'] ) ) { return false; } if ( $category === 'favorites' && empty( $template_data['template']['favorite'] ) ) { return false; } if ( ! in_array( $category, [ 'available', 'favorites' ], true ) && ! in_array( $category, $categories, true ) ) { return false; } } // Subcategory filter. if ( ! empty( $subcategory ) ) { $subcategories = explode( ',', $template_data['subcategories'] ); if ( ! in_array( $subcategory, $subcategories, true ) ) { return false; } } // Search filter. if ( ! empty( $search ) ) { $search_lower = strtolower( $search ); $name_lower = strtolower( $template_data['template']['name'] ); $desc_lower = strtolower( $template_data['template']['description'] ); $fields_lower = strtolower( $template_data['fields'] ); if ( strpos( $name_lower, $search_lower ) === false && strpos( $desc_lower, $search_lower ) === false && strpos( $fields_lower, $search_lower ) === false ) { return false; } } return true; } } SiteHealth.php 0000644 00000005425 15252506741 0007324 0 ustar 00 <?php namespace WPForms\Admin; /** * Site Health WPForms Info. * * @since 1.5.5 */ class SiteHealth { /** * Init Site Health. * * @since 1.5.5 */ final public function init() { $this->hooks(); } /** * Integration hooks. * * @since 1.5.5 */ protected function hooks() { add_filter( 'debug_information', [ $this, 'add_info_section' ] ); } /** * Add WPForms section to Info tab. * * @since 1.5.5 * * @param array $debug_info Array of all information. * * @return array Array with added WPForms info section. */ public function add_info_section( $debug_info ) { $wpforms = [ 'label' => 'WPForms', 'fields' => [ 'version' => [ 'label' => esc_html__( 'Version', 'wpforms-lite' ), 'value' => WPFORMS_VERSION, ], ], ]; // Install date. $activated = get_option( 'wpforms_activated', [] ); if ( ! empty( $activated['lite'] ) ) { $wpforms['fields']['lite'] = [ 'label' => esc_html__( 'Lite install date', 'wpforms-lite' ), 'value' => wpforms_datetime_format( $activated['lite'], '', true ), ]; } if ( ! empty( $activated['pro'] ) ) { $wpforms['fields']['pro'] = [ 'label' => esc_html__( 'Pro install date', 'wpforms-lite' ), 'value' => wpforms_datetime_format( $activated['pro'], '', true ), ]; } // Permissions for the upload directory. $upload_dir = wpforms_upload_dir(); $wpforms['fields']['upload_dir'] = [ 'label' => esc_html__( 'Uploads directory', 'wpforms-lite' ), 'value' => empty( $upload_dir['error'] ) && ! empty( $upload_dir['path'] ) && wp_is_writable( $upload_dir['path'] ) ? esc_html__( 'Writable', 'wpforms-lite' ) : esc_html__( 'Not writable', 'wpforms-lite' ), ]; // DB tables. $db_tables = wpforms()->get_existing_custom_tables(); if ( $db_tables ) { $db_tables_str = empty( $db_tables ) ? esc_html__( 'Not found', 'wpforms-lite' ) : implode( ', ', $db_tables ); $wpforms['fields']['db_tables'] = [ 'label' => esc_html__( 'DB tables', 'wpforms-lite' ), 'value' => $db_tables_str, 'private' => true, ]; } // Total forms. $wpforms['fields']['total_forms'] = [ 'label' => esc_html__( 'Total forms', 'wpforms-lite' ), 'value' => wp_count_posts( 'wpforms' )->publish, ]; if ( ! wpforms()->is_pro() ) { $forms = wpforms()->obj( 'form' )->get( '', [ 'fields' => 'ids' ] ); if ( empty( $forms ) || ! is_array( $forms ) ) { $forms = []; } $count = 0; foreach ( $forms as $form_id ) { $count += (int) get_post_meta( $form_id, 'wpforms_entries_count', true ); } $wpforms['fields']['total_submissions'] = [ 'label' => esc_html__( 'Total submissions (since v1.5.0)', 'wpforms-lite' ), 'value' => $count, ]; } $debug_info['wpforms'] = $wpforms; return $debug_info; } } Loader.php 0000644 00000003234 15252506741 0006474 0 ustar 00 <?php namespace WPForms\Admin; /** * Class Loader gives ability to track/load all admin modules. * * @since 1.5.0 */ class Loader { /** * Get the instance of a class and store it in itself. * * @since 1.5.0 */ public static function get_instance() { static $instance; if ( ! $instance ) { $instance = new self(); } return $instance; } /** * Loader constructor. * * @since 1.5.0 */ public function __construct() { $core_class_names = [ 'Connect', 'FlyoutMenu', 'Builder\LicenseAlert', 'Builder\Builder', 'Pages\Community', 'Pages\SMTP', 'Pages\Analytics', 'Entries\PrintPreview', ]; $class_names = \apply_filters( 'wpforms_admin_classes_available', $core_class_names ); foreach ( $class_names as $class_name ) { $this->register_class( $class_name ); } } /** * Register a new class. * * @since 1.5.0 * * @param string $class_name Class name to register. */ public function register_class( $class_name ) { $class_name = sanitize_text_field( $class_name ); // Load Lite class if exists. if ( class_exists( 'WPForms\Lite\Admin\\' . $class_name ) && ! wpforms()->is_pro() ) { $class_name = 'WPForms\Lite\Admin\\' . $class_name; new $class_name(); return; } // Load Pro class if exists. if ( class_exists( 'WPForms\Pro\Admin\\' . $class_name ) && wpforms()->is_pro() ) { $class_name = 'WPForms\Pro\Admin\\' . $class_name; new $class_name(); return; } // Load general class if neither Pro nor Lite class exists. if ( class_exists( __NAMESPACE__ . '\\' . $class_name ) ) { $class_name = __NAMESPACE__ . '\\' . $class_name; new $class_name(); } } } Helpers/Chart.php 0000644 00000010464 15252506741 0007734 0 ustar 00 <?php namespace WPForms\Admin\Helpers; use DateInterval; use DatePeriod; use DateTimeImmutable; /** * Chart dataset processing helper methods. * * @since 1.8.2 */ class Chart { /** * Default date format. * * @since 1.8.2 */ const DATE_FORMAT = 'Y-m-d'; /** * Default date-time format. * * @since 1.8.2 */ const DATETIME_FORMAT = 'Y-m-d H:i:s'; /** * Processes the provided dataset to make sure the formatting needed for the "Chart.js" instance is provided. * * @since 1.8.2 * * @param array $query Dataset retrieved from the database. * @param DateTimeImmutable $start_date Start date for the timespan. * @param DateTimeImmutable $end_date End date for the timespan. * * @return array */ public static function process_chart_dataset_data( $query, $start_date, $end_date ) { // Bail early if the given query contains no records to iterate. if ( ! is_array( $query ) || empty( $query ) ) { return [ 0, [] ]; } $dataset = []; $timezone = wp_timezone(); // Retrieve the timezone object for the site. $mysql_timezone = timezone_open( 'UTC' ); // In the database, all datetime are stored in UTC. foreach ( $query as $row ) { $row_day = isset( $row['day'] ) ? sanitize_text_field( $row['day'] ) : ''; $row_count = isset( $row['count'] ) ? abs( (float) $row['count'] ) : 0; // Skip the rest of the current iteration if the date (day) is unavailable. if ( empty( $row_day ) ) { continue; } // Since we won’t need the initial datetime instances after the query, // there is no need to create immutable date objects. $row_datetime = date_create_from_format( self::DATETIME_FORMAT, $row_day, $mysql_timezone ); // Skip the rest of the current iteration if the date creation function fails. if ( ! $row_datetime ) { continue; } $row_datetime->setTimezone( $timezone ); $row_date_formatted = $row_datetime->format( self::DATE_FORMAT ); // We must take into account entries submitted at different hours of the day, // because it is possible that more than one entry could be submitted on a given day. if ( ! isset( $dataset[ $row_date_formatted ] ) ) { $dataset[ $row_date_formatted ] = $row_count; continue; } $dataset_count = $dataset[ $row_date_formatted ]; $dataset[ $row_date_formatted ] = $dataset_count + $row_count; } return self::format_chart_dataset_data( $dataset, $start_date, $end_date ); } /** * Format given forms dataset to ensure correct data structure is parsed for serving the "chart.js" instance. * i.e., [ '2023-02-11' => [ 'day' => '2023-02-11', 'count' => 12 ] ]. * * @since 1.8.2 * * @param array $dataset Dataset for the chart. * @param DateTimeImmutable $start_date Start date for the timespan. * @param DateTimeImmutable $end_date End date for the timespan. * * @return array */ private static function format_chart_dataset_data( $dataset, $start_date, $end_date ) { // In the event that there is no dataset to process, leave early. if ( empty( $dataset ) ) { return [ 0, [] ]; } $interval = new DateInterval( 'P1D' ); // Variable that store the date interval of period 1 day. $period = new DatePeriod( $start_date, $interval, $end_date ); // Used for iteration between start and end date period. $data = []; // Placeholder for the actual chart dataset data. $total_entries = 0; $has_non_zero_count = false; // Use loop to store date into array. foreach ( $period as $date ) { $date_formatted = $date->format( self::DATE_FORMAT ); $count = isset( $dataset[ $date_formatted ] ) ? (float) $dataset[ $date_formatted ] : 0; $total_entries += $count; $data[ $date_formatted ] = [ 'day' => $date_formatted, 'count' => $count, ]; // This check helps determine whether there is at least one non-zero count value in the dataset being processed. // It's used to optimize the function's behavior and to decide whether to include certain data in the returned result. if ( $count > 0 && ! $has_non_zero_count ) { $has_non_zero_count = true; } } return [ $total_entries, $has_non_zero_count ? $data : [], // We will return an empty array to indicate that there is no data to display. ]; } } Helpers/Datepicker.php 0000644 00000033705 15252506741 0010751 0 ustar 00 <?php namespace WPForms\Admin\Helpers; use DateTimeImmutable; /** * Timespan and popover date-picker helper methods. * * @since 1.8.2 */ class Datepicker { /** * Number of timespan days by default. * "Last 30 Days", by default. * * @since 1.8.2 */ const TIMESPAN_DAYS = '30'; /** * Timespan (date range) delimiter. * * @since 1.8.2 */ const TIMESPAN_DELIMITER = ' - '; /** * Default date format. * * @since 1.8.2 */ const DATE_FORMAT = 'Y-m-d'; /** * Default date-time format. * * @since 1.8.2 */ const DATETIME_FORMAT = 'Y-m-d H:i:s'; /** * Sets the timespan (or date range) selected. * * Includes: * 1. Start date object in WP timezone. * 2. End date object in WP timezone. * 3. Number of "Last X days", if applicable, otherwise returns "custom". * 4. Label associated with the selected date filter choice. @see "get_date_filter_choices". * * @since 1.8.2 * * @return array */ public static function process_timespan() { $dates = (string) filter_input( INPUT_GET, 'date', FILTER_SANITIZE_FULL_SPECIAL_CHARS ); // Return default timespan if dates are empty. if ( empty( $dates ) ) { return self::get_timespan_dates( self::TIMESPAN_DAYS ); } $dates = self::maybe_validate_string_timespan( $dates ); list( $start_date, $end_date ) = explode( self::TIMESPAN_DELIMITER, $dates ); // Return default timespan if start date is more recent than end date. if ( strtotime( $start_date ) > strtotime( $end_date ) ) { return self::get_timespan_dates( self::TIMESPAN_DAYS ); } $timezone = wp_timezone(); // Retrieve the timezone string for the site. $start_date = date_create_immutable( $start_date, $timezone ); $end_date = date_create_immutable( $end_date, $timezone ); // Return default timespan if date creation fails. if ( ! $start_date || ! $end_date ) { return self::get_timespan_dates( self::TIMESPAN_DAYS ); } // Set time to 0:0:0 for start date and 23:59:59 for end date. $start_date = $start_date->setTime( 0, 0, 0 ); $end_date = $end_date->setTime( 23, 59, 59 ); $days_diff = ''; $current_date = date_create_immutable( 'now', $timezone )->setTime( 23, 59, 59 ); // Calculate days difference only if end date is equal to current date. if ( ! $current_date->diff( $end_date )->format( '%a' ) ) { $days_diff = $end_date->diff( $start_date )->format( '%a' ); } list( $days, $timespan_label ) = self::get_date_filter_choices( $days_diff ); return [ $start_date, // WP timezone. $end_date, // WP timezone. $days, // e.g., 22. $timespan_label, // e.g., Custom. ]; } /** * Sets the timespan (or date range) for performing mysql queries. * * Includes: * 1. Start date object in WP timezone. * 2. End date object in WP timezone. * * @param null|array $timespan Given timespan (dates) preferably in WP timezone. * * @since 1.8.2 * * @return array */ public static function process_timespan_mysql( $timespan = null ) { // Retrieve and validate timespan if none is given. if ( empty( $timespan ) || ! is_array( $timespan ) ) { $timespan = self::process_timespan(); } list( $start_date, $end_date ) = $timespan; // Ideally should be in WP timezone. // If the time period is not a date object, return empty values. if ( ! ( $start_date instanceof DateTimeImmutable ) || ! ( $end_date instanceof DateTimeImmutable ) ) { return [ '', '' ]; } // If given timespan is already in UTC timezone, return as it is. if ( date_timezone_get( $start_date )->getName() === 'UTC' && date_timezone_get( $end_date )->getName() === 'UTC' ) { return [ $start_date, // UTC timezone. $end_date, // UTC timezone. ]; } $mysql_timezone = timezone_open( 'UTC' ); return [ $start_date->setTimezone( $mysql_timezone ), // UTC timezone. $end_date->setTimezone( $mysql_timezone ), // UTC timezone. ]; } /** * Helper method to generate WP and UTC based date-time instances. * * Includes: * 1. Start date object in WP timezone. * 2. End date object in WP timezone. * 3. Start date object in UTC timezone. * 4. End date object in UTC timezone. * * @since 1.8.2 * * @param string $dates Given timespan (dates) in string. i.e. "2023-01-16 - 2023-02-15". * * @return bool|array */ public static function process_string_timespan( $dates ) { $dates = self::maybe_validate_string_timespan( $dates ); list( $start_date, $end_date ) = explode( self::TIMESPAN_DELIMITER, $dates ); // Return false if the start date is more recent than the end date. if ( strtotime( $start_date ) > strtotime( $end_date ) ) { return false; } $timezone = wp_timezone(); // Retrieve the timezone object for the site. $start_date = date_create_immutable( $start_date, $timezone ); $end_date = date_create_immutable( $end_date, $timezone ); // Return false if the date creation fails. if ( ! $start_date || ! $end_date ) { return false; } // Set the time to 0:0:0 for the start date and 23:59:59 for the end date. $start_date = $start_date->setTime( 0, 0, 0 ); $end_date = $end_date->setTime( 23, 59, 59 ); // Since we will need the initial datetime instances after the query, // we need to return new objects when modifications made. // Convert the dates to UTC timezone. $mysql_timezone = timezone_open( 'UTC' ); $utc_start_date = $start_date->setTimezone( $mysql_timezone ); $utc_end_date = $end_date->setTimezone( $mysql_timezone ); return [ $start_date, // WP timezone. $end_date, // WP timezone. $utc_start_date, // UTC timezone. $utc_end_date, // UTC timezone. ]; } /** * Sets the timespan (or date range) for performing mysql queries. * * Includes: * 1. A list of date filter options for the datepicker module. * 2. Currently selected filter or date range values. Last "X" days, or i.e. Feb 8, 2023 - Mar 9, 2023. * 3. Assigned timespan dates. * * @param null|array $timespan Given timespan (dates) preferably in WP timezone. * * @since 1.8.2 * * @return array */ public static function process_datepicker_choices( $timespan = null ) { // Retrieve and validate timespan if none is given. if ( empty( $timespan ) || ! is_array( $timespan ) ) { $timespan = self::process_timespan(); } list( $start_date, $end_date, $days ) = $timespan; $filters = self::get_date_filter_choices(); $selected = isset( $filters[ $days ] ) ? $days : 'custom'; $value = self::concat_dates( $start_date, $end_date ); $chosen_filter = $selected === 'custom' ? $value : $filters[ $selected ]; $choices = []; foreach ( $filters as $choice => $label ) { $timespan_dates = self::get_timespan_dates( $choice ); $checked = checked( $selected, $choice, false ); $choices[] = sprintf( '<label class="%s">%s<input type="radio" aria-hidden="true" name="timespan" value="%s" %s></label>', $checked ? 'is-selected' : '', esc_html( $label ), esc_attr( self::concat_dates( ...$timespan_dates ) ), esc_attr( $checked ) ); } return [ $choices, $chosen_filter, $value, ]; } /** * Based on the specified date-time range, calculates the comparable prior time period to estimate trends. * * Includes: * 1. Start date object in the given (original) timezone. * 2. End date object in the given (original) timezone. * * @since 1.8.2 * @since 1.8.8 Added $days_diff optional parameter. * * @param DateTimeImmutable $start_date Start date for the timespan. * @param DateTimeImmutable $end_date End date for the timespan. * @param null|int $days_diff Optional. Number of days in the timespan. If provided, it won't be calculated. * * @return bool|array */ public static function get_prev_timespan_dates( $start_date, $end_date, $days_diff = null ) { if ( ! ( $start_date instanceof DateTimeImmutable ) || ! ( $end_date instanceof DateTimeImmutable ) ) { return false; } // Calculate $days_diff if not provided. if ( ! is_numeric( $days_diff ) ) { $days_diff = $end_date->diff( $start_date )->format( '%a' ); } // If $days_diff is non-positive, set $days_modifier to 1; otherwise, use $days_diff. $days_modifier = max( (int) $days_diff, 1 ); return [ $start_date->modify( "-{$days_modifier} day" ), $start_date->modify( '-1 second' ), ]; } /** * Get the site's date format from WordPress settings and convert it to a format compatible with Moment.js. * * @since 1.8.5.4 * * @return string */ public static function get_wp_date_format_for_momentjs() { // Get the date format from WordPress settings. $date_format = get_option( 'date_format', 'F j, Y' ); // Define a mapping of PHP date format characters to Moment.js format characters. $format_mapping = [ 'd' => 'DD', 'D' => 'ddd', 'j' => 'D', 'l' => 'dddd', 'S' => '', // PHP's S (English ordinal suffix) is not directly supported in Moment.js. 'w' => 'd', 'z' => '', // PHP's z (Day of the year) is not directly supported in Moment.js. 'W' => '', // PHP's W (ISO-8601 week number of year) is not directly supported in Moment.js. 'F' => 'MMMM', 'm' => 'MM', 'M' => 'MMM', 'n' => 'M', 't' => '', // PHP's t (Number of days in the given month) is not directly supported in Moment.js. 'L' => '', // PHP's L (Whether it's a leap year) is not directly supported in Moment.js. 'o' => 'YYYY', 'Y' => 'YYYY', 'y' => 'YY', 'a' => 'a', 'A' => 'A', 'B' => '', // PHP's B (Swatch Internet time) is not directly supported in Moment.js. 'g' => 'h', 'G' => 'H', 'h' => 'hh', 'H' => 'HH', 'i' => 'mm', 's' => 'ss', 'u' => '', // PHP's u (Microseconds) is not directly supported in Moment.js. 'e' => '', // PHP's e (Timezone identifier) is not directly supported in Moment.js. 'I' => '', // PHP's I (Whether or not the date is in daylight saving time) is not directly supported in Moment.js. 'O' => '', // PHP's O (Difference to Greenwich time (GMT) without colon) is not directly supported in Moment.js. 'P' => '', // PHP's P (Difference to Greenwich time (GMT) with colon) is not directly supported in Moment.js. 'T' => '', // PHP's T (Timezone abbreviation) is not directly supported in Moment.js. 'Z' => '', // PHP's Z (Timezone offset in seconds) is not directly supported in Moment.js. 'c' => 'YYYY-MM-DD', // PHP's c (ISO 8601 date) is not directly supported in Moment.js. 'r' => 'ddd, DD MMM YYYY', // PHP's r (RFC 2822 formatted date) is not directly supported in Moment.js. 'U' => '', // PHP's U (Seconds since the Unix Epoch) is not directly supported in Moment.js. ]; // Convert PHP format to JavaScript format. $momentjs_format = strtr( $date_format, $format_mapping ); // Use 'MMM D, YYYY' as a fallback if the conversion is not available. return empty( $momentjs_format ) ? 'MMM D, YYYY' : $momentjs_format; } /** * The number of days is converted to the start and end date range. * * @since 1.8.2 * * @param string $days Timespan days. * * @return array */ private static function get_timespan_dates( $days ) { list( $timespan_key, $timespan_label ) = self::get_date_filter_choices( $days ); // Bail early, if the given number of days is NOT a number nor a numeric string. if ( ! is_numeric( $days ) ) { return [ '', '', $timespan_key, $timespan_label ]; } $end_date = date_create_immutable( 'now', wp_timezone() ); $start_date = $end_date; if ( (int) $days > 0 ) { $start_date = $start_date->modify( "-{$days} day" ); } $start_date = $start_date->setTime( 0, 0, 0 ); $end_date = $end_date->setTime( 23, 59, 59 ); return [ $start_date, // WP timezone. $end_date, // WP timezone. $timespan_key, // i.e. 30. $timespan_label, // i.e. Last 30 days. ]; } /** * Check the delimiter to see if the end date is specified. * We can assume that the start and end dates are the same if the end date is missing. * * @since 1.8.2 * * @param string $dates Given timespan (dates) in string. i.e. "2023-01-16 - 2023-02-15" or "2023-01-16". * * @return string */ private static function maybe_validate_string_timespan( $dates ) { // "-" (en dash) is used as a delimiter for the datepicker module. if ( strpos( $dates, self::TIMESPAN_DELIMITER ) !== false ) { return $dates; } return $dates . self::TIMESPAN_DELIMITER . $dates; } /** * Returns a list of date filter options for the datepicker module. * * @since 1.8.2 * * @param string|null $key Optional. Key associated with available filters. * * @return array */ public static function get_date_filter_choices( $key = null ) { // Available date filters. $choices = [ '0' => esc_html__( 'Today', 'wpforms-lite' ), '1' => esc_html__( 'Yesterday', 'wpforms-lite' ), '7' => esc_html__( 'Last 7 days', 'wpforms-lite' ), '30' => esc_html__( 'Last 30 days', 'wpforms-lite' ), '90' => esc_html__( 'Last 90 days', 'wpforms-lite' ), '365' => esc_html__( 'Last 1 year', 'wpforms-lite' ), 'custom' => esc_html__( 'Custom', 'wpforms-lite' ), ]; // Bail early, and return the full list of options. if ( is_null( $key ) ) { return $choices; } // Return the "Custom" filter if the given key is not found. $key = isset( $choices[ $key ] ) ? $key : 'custom'; return [ $key, $choices[ $key ] ]; } /** * Concatenate given dates into a single string. i.e. "2023-01-16 - 2023-02-15". * * @since 1.8.2 * * @param DateTimeImmutable $start_date Start date. * @param DateTimeImmutable $end_date End date. * @param int|string $fallback Fallback value if dates are not valid. * * @return string */ private static function concat_dates( $start_date, $end_date, $fallback = '' ) { // Bail early, if the given dates are not valid. if ( ! ( $start_date instanceof DateTimeImmutable ) || ! ( $end_date instanceof DateTimeImmutable ) ) { return $fallback; } return implode( self::TIMESPAN_DELIMITER, [ $start_date->format( self::DATE_FORMAT ), $end_date->format( self::DATE_FORMAT ), ] ); } } Tools/Import/Views/ImportViewsInterface.php 0000644 00000001126 15252506741 0015044 0 ustar 00 <?php namespace WPForms\Admin\Tools\Import\Views; /** * Import Views Interface. * * @since 1.10.1 */ interface ImportViewsInterface { /** * Initialize class. * * @since 1.10.1 */ public function init(): void; /** * Check if the current user has the capability to view the page. * * @since 1.10.1 * * @return bool */ public function current_user_can(): bool; /** * Page content. * * @since 1.10.1 */ public function display(): void; /** * Get the Tab label. * * @since 1.10.1 * * @return string */ public function get_tab_label(): string; } Tools/Import/Views/Forms/Page.php 0000644 00000030216 15252506741 0012677 0 ustar 00 <?php namespace WPForms\Admin\Tools\Import\Views\Forms; use WP_Error; use WPForms\Helpers\File; use WPForms\Admin\Tools\Importers; use WPForms\Admin\Tools\Tools; use WPForms_Form_Handler; use WPForms\Admin\Tools\Import\Views\ImportViewsInterface; /** * Forms Import Page class. * * Handles importing WPForms JSON exports and third-party form plugins. * * @since 1.10.1 */ class Page implements ImportViewsInterface { /** * View slug. * * @since 1.10.1 * * @var string */ protected $slug = 'import'; /** * Registered importers. * * @since 1.10.1 * * @var array */ public $importers = []; /** * Initialize class. * * @since 1.10.1 */ public function init(): void { $this->hooks(); $this->importers = ( new Importers() )->get_importers(); } /** * Register hooks. * * @since 1.10.1 */ private function hooks(): void { add_action( 'wpforms_tools_init', [ $this, 'import_process' ] ); } /** * Check if the current user has the capability to view the page. * * @since 1.10.1 * * @return bool */ public function current_user_can(): bool { return wpforms_current_user_can( 'create_forms' ); } /** * Get the Tab label. * * @since 1.10.1 * * @return string */ public function get_tab_label(): string { return __( 'Import Forms', 'wpforms-lite' ); } /** * Import process. * * @since 1.10.1 */ public function import_process(): void { // phpcs:disable WordPress.Security.NonceVerification.Missing if ( empty( $_POST['action'] ) || $_POST['action'] !== 'import_form' || empty( $_FILES['file']['tmp_name'] ) || ! isset( $_POST['submit-import'] ) || ! $this->verify_nonce() ) { return; } // phpcs:enable WordPress.Security.NonceVerification.Missing $this->process(); } /** * Page content. * * @since 1.10.1 */ public function display(): void { if ( ! current_user_can( 'unfiltered_html' ) ) { $this->error_unfiltered_html_import_message(); return; } $this->success_import_message(); $this->wpforms_block(); $this->other_forms_block(); } /** * Error message for users with no `unfiltered_html` permission. * * @since 1.10.1 * * @noinspection HtmlUnknownTarget */ private function error_unfiltered_html_import_message(): void { printf( '<div class="notice notice-error inline"><p>%s</p></div>', sprintf( wp_kses( /* translators: %s - WPForms contact page URL. */ __( 'You can\'t import forms because you don\'t have unfiltered HTML permissions. Please contact your site administrator or <a href="%s" target="_blank" rel="noopener noreferrer">reach out to our support team</a>.', 'wpforms-lite' ), [ 'a' => [ 'href' => [], 'target' => [], 'rel' => [], ], ] ), esc_url( wpforms_utm_link( 'https://wpforms.com/contact/', 'Tools - Import', 'Support Link - Import Forms No Permissions' ) ) ) ); } /** * Success import message. * * @since 1.10.1 * * @noinspection HtmlUnknownTarget */ private function success_import_message(): void { // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( isset( $_GET['wpforms_notice'] ) && $_GET['wpforms_notice'] === 'forms-imported' ) { ?> <div class="updated notice is-dismissible"> <p> <?php esc_html_e( 'Import was successfully finished.', 'wpforms-lite' ); ?> <?php if ( wpforms_current_user_can( 'view_forms' ) ) { printf( wp_kses( /* translators: %s - forms list page URL. */ __( 'You can go and <a href="%s">check your forms</a>.', 'wpforms-lite' ), [ 'a' => [ 'href' => [] ] ] ), esc_url( admin_url( 'admin.php?page=wpforms-overview' ) ) ); } ?> </p> </div> <?php } } /** * WPForms section. * * @since 1.10.1 */ private function wpforms_block(): void { ?> <div class="wpforms-setting-row tools wpforms-settings-row-divider"> <h4><?php esc_html_e( 'WPForms Import', 'wpforms-lite' ); ?></h4> <p><?php esc_html_e( 'Select a WPForms export file.', 'wpforms-lite' ); ?></p> <form method="post" enctype="multipart/form-data" action="<?php echo esc_attr( $this->get_link() ); ?>"> <div class="wpforms-file-upload"> <input type="file" name="file" id="wpforms-tools-form-import" class="inputfile" data-multiple-caption="{count} <?php esc_attr_e( 'files selected', 'wpforms-lite' ); ?>" accept=".json" aria-label="<?php esc_attr_e( 'Upload WPForms export file', 'wpforms-lite' ); ?>" /> <label for="wpforms-tools-form-import"> <span class="fld" aria-hidden="true"><span class="placeholder"><?php esc_html_e( 'No file chosen', 'wpforms-lite' ); ?></span></span> <strong class="wpforms-btn wpforms-btn-md wpforms-btn-light-grey" aria-hidden="true"> <i class="fa fa-cloud-upload" aria-hidden="true"></i><?php esc_html_e( 'Choose a File', 'wpforms-lite' ); ?> </strong> </label> </div> <input type="hidden" name="action" value="import_form"> <button name="submit-import" class="wpforms-btn wpforms-btn-md wpforms-btn-orange" id="wpforms-import" aria-disabled="true"> <?php esc_html_e( 'Import Forms', 'wpforms-lite' ); ?> </button> <?php wp_nonce_field( 'wpforms_' . $this->slug . '_nonce', 'wpforms-tools-' . $this->slug . '-nonce' ); ?> </form> </div> <?php } /** * Other forms section. * * @since 1.10.1 * * @noinspection HtmlUnknownAttribute */ private function other_forms_block(): void { ?> <div class="wpforms-setting-row tools" id="wpforms-importers"> <h4><?php esc_html_e( 'Import Forms from Other Form Plugins', 'wpforms-lite' ); ?></h4> <p><?php esc_html_e( 'Not happy with other WordPress contact form plugins?', 'wpforms-lite' ); ?></p> <p><?php esc_html_e( 'WPForms makes it easy for you to switch by allowing you to import your third-party forms with a single click.', 'wpforms-lite' ); ?></p> <div class="wpforms-importers-wrap"> <?php if ( empty( $this->importers ) ) { ?> <p><?php esc_html_e( 'No form importers are currently enabled.', 'wpforms-lite' ); ?> </p> <?php } else { ?> <form action="<?php echo esc_url( admin_url( 'admin.php' ) ); ?>"> <span class="choicesjs-select-wrap"> <select id="wpforms-tools-form-other-import" class="choicesjs-select" name="provider" data-search="<?php echo esc_attr( wpforms_choices_js_is_search_enabled( $this->importers ) ); ?>" required> <option value=""><?php esc_html_e( 'Select previous form plugin', 'wpforms-lite' ); ?></option> <?php foreach ( $this->importers as $importer ) { $status = ''; if ( empty( $importer['installed'] ) ) { $status = esc_html__( 'Not Installed', 'wpforms-lite' ); } elseif ( empty( $importer['active'] ) ) { $status = esc_html__( 'Not Active', 'wpforms-lite' ); } printf( '<option value="%s" %s>%s %s</option>', esc_attr( $importer['slug'] ), ! empty( $status ) ? 'disabled' : '', esc_html( $importer['name'] ), ! empty( $status ) ? '(' . esc_html( $status ) . ')' : '' // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ); } ?> </select> </span> <input type="hidden" name="page" value="<?php echo esc_attr( Tools::SLUG ); ?>"> <input type="hidden" name="view" value="importer"> <button class="wpforms-btn wpforms-btn-md wpforms-btn-orange" id="wpforms-import-other" aria-disabled="true"> <?php esc_html_e( 'Import Forms', 'wpforms-lite' ); ?> </button> </form> <?php } ?> </div> </div> <?php } /** * Import processing. * * @since 1.10.1 */ private function process(): void { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks // Add a filter of the link rel attr to avoid JSON damage. add_filter( 'wp_targeted_link_rel', '__return_empty_string', 50, 1 ); $ext = ''; // phpcs:disable WordPress.Security.NonceVerification.Missing if ( isset( $_FILES['file']['name'] ) ) { $ext = strtolower( pathinfo( sanitize_text_field( wp_unslash( $_FILES['file']['name'] ) ), PATHINFO_EXTENSION ) ); } if ( $ext !== 'json' ) { wp_die( esc_html__( 'Please upload a valid .json form export file.', 'wpforms-lite' ), esc_html__( 'Error', 'wpforms-lite' ), [ 'response' => 400, ] ); } // The wp_unslash() function breaks upload on Windows. // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.NonceVerification.Missing $filename = isset( $_FILES['file']['tmp_name'] ) ? sanitize_text_field( $_FILES['file']['tmp_name'] ) : ''; // phpcs:enable WordPress.Security.NonceVerification.Missing $result = $this->import_forms( $filename ); if ( $result !== null ) { wp_die( esc_html( $result->get_error_message() ), esc_html__( 'Error', 'wpforms-lite' ), [ 'response' => 400, ] ); } wp_safe_redirect( add_query_arg( [ 'wpforms_notice' => 'forms-imported' ] ) ); exit; } /** * Import forms from a file. * * @since 1.10.1 * * @param string $filename File containing forms to be imported. * * @return null|WP_Error */ private function import_forms( string $filename ): ?WP_Error { if ( ! current_user_can( 'unfiltered_html' ) ) { return new WP_Error( 'no_permission', __( 'The unfiltered HTML permissions are required to import form.', 'wpforms-lite' ) ); } // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents $forms = json_decode( File::remove_utf8_bom( file_get_contents( $filename ) ), true ); if ( empty( $forms ) || ! is_array( $forms ) ) { return new WP_Error( 'bad_json', __( 'Please upload a valid .json form export file.', 'wpforms-lite' ) ); } if ( ! $this->save_forms( $forms ) ) { return new WP_Error( 'no_permission', __( 'There was an error saving your form. Please check your file and try again.', 'wpforms-lite' ) ); } return null; } /** * Save forms. * * @since 1.10.1 * * @param array $forms Forms. * * @return bool */ private function save_forms( array $forms ): bool { foreach ( $forms as $form ) { $title = ! empty( $form['settings']['form_title'] ) ? $form['settings']['form_title'] : ''; $desc = ! empty( $form['settings']['form_desc'] ) ? $form['settings']['form_desc'] : ''; $new_id = wp_insert_post( [ 'post_title' => wp_slash( $title ), 'post_status' => 'publish', 'post_type' => 'wpforms', 'post_excerpt' => wp_slash( $desc ), ] ); // When we cannot insert one form into the DB or update it, // we will have a similar issue with the following form in the JSON file. // So, it is better to bail out and inform the user that we cannot proceed. if ( ! $new_id ) { return false; } $form['id'] = $new_id; if ( ! $this->update_form( $form ) ) { return false; } } return true; } /** * Update form. * * @since 1.10.1 * * @param array $form Form. * * @return bool */ private function update_form( array $form ): bool { if ( wpforms_is_form_data_slashing_enabled() ) { $form = wp_slash( $form ); } $result = wp_update_post( [ 'ID' => $form['id'], 'post_content' => wpforms_encode( $form ), ] ); if ( ! $result ) { return false; } if ( empty( $form['settings']['form_tags'] ) ) { return true; } $result = wp_set_post_terms( $form['id'], implode( ',', (array) $form['settings']['form_tags'] ), WPForms_Form_Handler::TAGS_TAXONOMY ); if ( ! $result ) { return false; } return true; } /** * Get a link to the import view. * * @since 1.10.1 * * @return string */ private function get_link(): string { return add_query_arg( [ 'page' => Tools::SLUG, 'view' => $this->slug, 'tab' => 'forms', ], admin_url( 'admin.php' ) ); } /** * Verify nonce field. * * @since 1.10.1 * * @return bool */ private function verify_nonce(): bool { $nonce_name = 'wpforms-tools-' . $this->slug . '-nonce'; $nonce = isset( $_POST[ $nonce_name ] ) ? sanitize_text_field( wp_unslash( $_POST[ $nonce_name ] ) ) : ''; return (bool) wp_verify_nonce( $nonce, 'wpforms_' . $this->slug . '_nonce' ); } } Tools/Views/ActionSchedulerList.php 0000644 00000004623 15252506741 0013376 0 ustar 00 <?php namespace WPForms\Admin\Tools\Views; use ActionScheduler as Scheduler; use ActionScheduler_ListTable; /** * Action Scheduler list table. * * @since 1.7.6 */ class ActionSchedulerList extends ActionScheduler_ListTable { /** * ActionSchedulerList constructor. * * @since 1.7.6 */ public function __construct() { parent::__construct( Scheduler::store(), Scheduler::logger(), Scheduler::runner() ); $this->process_actions(); } /** * Display the table heading. * * @since 1.7.6 */ protected function display_header() { ?> <h1><?php echo esc_html__( 'Scheduled Actions', 'wpforms-lite' ); ?></h1> <p> <?php echo sprintf( wp_kses( /* translators: %s - Action Scheduler website URL. */ __( 'WPForms is using the <a href="%s" target="_blank" rel="noopener noreferrer">Action Scheduler</a> library, which allows it to queue and process bigger tasks in the background without making your site slower for your visitors. Below you can see the list of all tasks and their status. This table can be very useful when debugging certain issues.', 'wpforms-lite' ), [ 'a' => [ 'href' => [], 'rel' => [], 'target' => [], ], ] ), 'https://actionscheduler.org/' ); ?> </p> <p> <?php echo esc_html__( 'Action Scheduler library is also used by other plugins, like WP Mail SMTP and WooCommerce, so you might see tasks that are not related to our plugin in the table below.', 'wpforms-lite' ); ?> </p> <?php // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( ! empty( $_GET['s'] ) ) { ?> <div id="wpforms-reset-filter"> <?php echo wp_kses( sprintf( /* translators: %s - search term. */ __( 'Search results for <strong>%s</strong>', 'wpforms-lite' ), // phpcs:ignore WordPress.Security.NonceVerification.Recommended sanitize_text_field( wp_unslash( $_GET['s'] ) ) ), [ 'strong' => [], ] ); ?> <a href="<?php echo esc_url( remove_query_arg( 's' ) ); ?>"> <span class="reset fa fa-times-circle"></span> </a> </div> <?php } } /** * Return the sortable column specified for this request to order the results by, if any. * * @since 1.9.1 * * @return string[] */ protected function get_request_query_args_to_persist() { return array_merge( parent::get_request_query_args_to_persist(), [ 'view' ] ); } } Tools/Views/EntryAutomation.php 0000644 00000004073 15252506741 0012627 0 ustar 00 <?php namespace WPForms\Admin\Tools\Views; use WPForms\Admin\Education\Admin\Tools\EntryAutomation as Education; /** * Class EntryAutomation. * * @since 1.9.6.1 */ class EntryAutomation extends View { /** * View slug. * * @since 1.9.6.1 * * @var string */ protected $slug = 'entry-automation'; /** * Init view. * * @since 1.9.6.1 */ public function init(): void { ( new Education() )->init(); $this->hooks(); } /** * Register hooks. * * @since 1.9.6.1 */ private function hooks(): void { add_filter( 'admin_body_class', [ $this, 'body_classes' ] ); } /** * Add body classes for the view. * * @since 1.9.6.1 * * @param string $classes Existing body classes. * * @return string */ public function body_classes( string $classes ): string { if ( ! wpforms_is_admin_page( 'tools', 'entry-automation' ) ) { return $classes; } return $classes . ' wpforms-admin-tools-view-entry-automation'; } /** * Get view label. * * @since 1.9.6.1 * * @return string */ public function get_label(): string { return esc_html__( 'Entry Automation', 'wpforms-lite' ); } /** * Checking user capability to view. * * @since 1.9.6.1 * * @return bool */ public function check_capability(): bool { /** * Check if the user has the capability to view this entry automation. * * @since 1.9.6.1 * * @param bool $capability Whether the user has the capability to view this entry automation. */ return (bool) apply_filters( 'wpforms_admin_tools_views_entry_automation_check_capability', wpforms_current_user_can() && wpforms()->obj( 'addons' )->get_addon( 'entry-automation' ) ); } /** * Display view content. * * @since 1.9.6.1 */ public function display(): void { ?> <div class="tools wpforms-settings-row-entry-automation"> <div class="wpforms-entry-automation-content"> <?php /** * Display the content. * * @since 1.9.6.1 */ do_action( 'wpforms_admin_tools_views_entry_automation_display' ); ?> </div> </div> <?php } } Tools/Views/CodeSnippets.php 0000644 00000005435 15252506741 0012070 0 ustar 00 <?php namespace WPForms\Admin\Tools\Views; use WPForms\Integrations\WPCode\WPCode; /** * Class WPCode view. * * @since 1.8.5 */ class CodeSnippets extends View { /** * View slug. * * @since 1.8.5 * * @var string */ protected $slug = 'wpcode'; /** * WPCode action required. * * @since 1.8.5 * * @var string */ private $action; /** * WPCode snippets. * * @since 1.8.5 * * @var array */ private $snippets; /** * WPCode plugin slug or download URL. * * @since 1.8.5 * * @var string */ private $plugin; /** * Whether WPCode action is required. * * @since 1.8.5 * * @var bool */ private $action_required; /** * Init view. * * @since 1.8.5 */ public function init() { $wpcode = new WPCode(); $this->snippets = $wpcode->load_wpforms_snippets(); $plugin_slug = $wpcode->is_pro_installed() ? $wpcode->pro_plugin_slug : $wpcode->lite_plugin_slug; $update_required = $wpcode->is_plugin_installed() && version_compare( $wpcode->plugin_version(), '2.0.10', '<' ); $installed_action = $update_required ? 'update' : 'activate'; $this->action_required = $update_required || ! $wpcode->is_plugin_installed() || ! $wpcode->is_plugin_active(); $this->action = $wpcode->is_plugin_installed() ? $installed_action : 'install'; $this->plugin = $this->action === 'activate' ? $plugin_slug : $wpcode->lite_download_url; $this->hooks(); } /** * Add hooks. * * @since 1.8.5 * * @return void */ private function hooks() { if ( $this->action !== 'update' ) { return; } add_filter( 'upgrader_package_options', static function ( $options ) { $options['clear_destination'] = true; return $options; } ); } /** * Get view label. * * @since 1.8.5 * * @return string * @noinspection PhpMissingReturnTypeInspection * @noinspection ReturnTypeCanBeDeclaredInspection */ public function get_label() { return esc_html__( 'Code Snippets', 'wpforms-lite' ); } /** * Checking user capability to view. * * @since 1.8.5 * * @return bool * @noinspection PhpMissingReturnTypeInspection * @noinspection ReturnTypeCanBeDeclaredInspection */ public function check_capability() { return wpforms_current_user_can(); } /** * Display view content. * * @since 1.8.5 * * @noinspection PhpMissingReturnTypeInspection * @noinspection ReturnTypeCanBeDeclaredInspection */ public function display() { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wpforms_render( 'integrations/wpcode/code-snippets', [ 'snippets' => $this->snippets, 'action_required' => $this->action_required, 'action' => $this->action, 'plugin' => $this->plugin, ], true ); } } Tools/Views/View.php 0000644 00000003320 15252506741 0010371 0 ustar 00 <?php namespace WPForms\Admin\Tools\Views; use WPForms\Admin\Tools\Tools; /** * Single View class. * * @since 1.6.6 */ abstract class View { /** * View slug. * * @since 1.6.6 * * @var string */ protected $slug; /** * Init. * * @since 1.6.6 */ abstract public function init(); /** * Get link to the view page. * * @since 1.6.6 * * @return string */ public function get_link() { return add_query_arg( [ 'page' => Tools::SLUG, 'view' => $this->slug, ], admin_url( 'admin.php' ) ); } /** * Get view label. * * @since 1.6.6 * * @return string */ abstract public function get_label(); /** * Checking user capability to view. * * @since 1.6.6 * * @return bool */ abstract public function check_capability(); /** * Checking if needs display in navigation. * * @since 1.6.6 * * @return bool */ public function hide_from_nav() { return false; } /** * Checking if navigation needs display. * * @since 1.6.6 * * @return bool */ public function show_nav() { return true; } /** * Display nonce field. * * @since 1.6.6 */ public function nonce_field() { wp_nonce_field( 'wpforms_' . $this->slug . '_nonce', 'wpforms-tools-' . $this->slug . '-nonce' ); } /** * Verify nonce field. * * @since 1.6.6 */ public function verify_nonce(): bool { $nonce_name = 'wpforms-tools-' . $this->slug . '-nonce'; $nonce = isset( $_POST[ $nonce_name ] ) ? sanitize_text_field( wp_unslash( $_POST[ $nonce_name ] ) ) : ''; return (bool) wp_verify_nonce( $nonce, 'wpforms_' . $this->slug . '_nonce' ); } /** * Display view content. * * @since 1.6.6 */ abstract public function display(); } Tools/Views/Importer.php 0000644 00000025105 15252506741 0011265 0 ustar 00 <?php namespace WPForms\Admin\Tools\Views; use WPForms\Admin\Tools\Importers; /** * Class Importer. * * @since 1.6.6 */ class Importer extends View { /** * View slug. * * @since 1.6.6 * * @var string */ protected $slug = 'import'; /** * Registered importers. * * @since 1.6.6 * * @var array */ public $importers = []; /** * Available forms for a specific importer. * * @since 1.6.6 * * @var array */ public $importer_forms = []; /** * Init view. * * @since 1.6.6 */ public function init() { $importers = new Importers(); $this->importers = $importers->get_importers(); if ( ! empty( $_GET['provider'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended $this->importer_forms = $importers->get_importer_forms( sanitize_key( $_GET['provider'] ) );//phpcs:ignore WordPress.Security.NonceVerification.Recommended } // Load the Underscores templates for importers. add_action( 'admin_print_scripts', [ $this, 'importer_templates' ] ); } /** * Get view label. * * @since 1.6.6 * * @return string */ public function get_label() { return ''; } /** * Checking user capability to view. * * @since 1.6.6 * * @return bool */ public function check_capability() { return wpforms_current_user_can( 'create_forms' ); } /** * Checking if needs display in navigation. * * @since 1.6.6 * * @return bool */ public function hide_from_nav() { return true; } /** * Importer view content. * * @since 1.6.6 */ public function display() { if ( ! current_user_can( 'unfiltered_html' ) ) { $this->error_unfiltered_html_import_message(); return; } $this->heading_block(); $this->forms_block(); $this->analyze_block(); $this->process_block(); } /** * Error message for users with no `unfiltered_html` permission. * * @since 1.10.1 */ private function error_unfiltered_html_import_message(): void { printf( '<div class="notice notice-error"><p>%s</p></div>', sprintf( wp_kses( /* translators: %s - WPForms contact page URL. */ __( 'You can\'t import forms because you don\'t have unfiltered HTML permissions. Please contact your site administrator or <a href="%s" target="_blank" rel="noopener noreferrer">reach out to our support team</a>.', 'wpforms-lite' ), [ 'a' => [ 'href' => [], 'target' => [], 'rel' => [], ], ] ), esc_url( wpforms_utm_link( 'https://wpforms.com/contact/', 'Tools - Import', 'Support Link - Form Importer No Permissions' ) ) ) ); } /** * Get provider. * * @since 1.6.6 * * @return string */ private function get_provider_name() { //phpcs:ignore WordPress.Security.NonceVerification.Recommended $slug = ! empty( $_GET['provider'] ) ? sanitize_key( $_GET['provider'] ) : ''; return isset( $this->importers[ $slug ] ) ? $this->importers[ $slug ]['name'] : ''; } /** * Heading block. * * @since 1.6.6 */ private function heading_block() { ?> <div class="wpforms-setting-row tools wpforms-clear section-heading no-desc"> <div class="wpforms-setting-field"> <h4><?php esc_html_e( 'Form Import', 'wpforms-lite' ); ?></h4> </div> </div> <?php } /** * Forms block. * * @since 1.6.6 */ private function forms_block() { ?> <div id="wpforms-importer-forms"> <div class="wpforms-setting-row tools"> <p><?php esc_html_e( 'Select the forms you would like to import.', 'wpforms-lite' ); ?></p> <div class="checkbox-multiselect-columns"> <div class="first-column"> <h5 class="header"><?php esc_html_e( 'Available Forms', 'wpforms-lite' ); ?></h5> <ul> <?php if ( empty( $this->importer_forms ) ) { echo '<li>' . esc_html__( 'No forms found.', 'wpforms-lite' ) . '</li>'; } else { foreach ( $this->importer_forms as $id => $form ) { printf( '<li><label><input type="checkbox" name="forms[]" value="%s">%s</label></li>', esc_attr( $id ), esc_attr( sanitize_text_field( $form ) ) ); } } ?> </ul> <?php if ( ! empty( $this->importer_forms ) ) : ?> <a href="#" class="all"><?php esc_html_e( 'Select All', 'wpforms-lite' ); ?></a> <?php endif; ?> </div> <div class="second-column"> <h5 class="header"><?php esc_html_e( 'Forms to Import', 'wpforms-lite' ); ?></h5> <ul></ul> </div> </div> </div> <?php if ( ! empty( $this->importer_forms ) ) : ?> <p class="submit"> <button class="wpforms-btn wpforms-btn-md wpforms-btn-orange" id="wpforms-importer-forms-submit"><?php esc_html_e( 'Import', 'wpforms-lite' ); ?></button> </p> <?php endif; ?> </div> <?php } /** * Analyze block. * * @since 1.6.6 */ private function analyze_block() { ?> <div id="wpforms-importer-analyze"> <p class="process-analyze"> <i class="fa fa-spinner fa-spin" aria-hidden="true"></i> <?php printf( wp_kses( /* translators: %s - provider name. */ __( 'Analyzing <span class="form-current">1</span> of <span class="form-total">0</span> forms from %s.', 'wpforms-lite' ), [ 'span' => [ 'class' => [], ], ] ), esc_attr( sanitize_text_field( $this->get_provider_name() ) ) ); ?> </p> <div class="upgrade"> <h5><?php esc_html_e( 'Heads up!', 'wpforms-lite' ); ?></h5> <p><?php esc_html_e( 'One or more of your forms contain fields that are not available in WPForms Lite. To properly import these fields, we recommend upgrading to WPForms Pro.', 'wpforms-lite' ); ?></p> <p><?php esc_html_e( 'You can continue with the import without upgrading, and we will do our best to match the fields. However, some of them will be omitted due to compatibility issues.', 'wpforms-lite' ); ?></p> <p> <a href="<?php echo esc_url( wpforms_admin_upgrade_link( 'tools-import' ) ); ?>" target="_blank" rel="noopener noreferrer" class="wpforms-btn wpforms-btn-md wpforms-btn-orange wpforms-upgrade-modal"><?php esc_html_e( 'Upgrade to WPForms Pro', 'wpforms-lite' ); ?></a> <a href="#" class="wpforms-btn wpforms-btn-md wpforms-btn-light-grey" id="wpforms-importer-continue-submit"><?php esc_html_e( 'Continue Import without Upgrading', 'wpforms-lite' ); ?></a> </p> <hr> <p><?php esc_html_e( 'Below is the list of form fields that may be impacted:', 'wpforms-lite' ); ?></p> </div> </div> <?php } /** * Process block. * * @since 1.6.6 */ private function process_block() { ?> <div id="wpforms-importer-process"> <p class="process-count"> <i class="fa fa-spinner fa-spin" aria-hidden="true"></i> <?php printf( wp_kses( /* translators: %s - provider name. */ __( 'Importing <span class="form-current">1</span> of <span class="form-total">0</span> forms from %s.', 'wpforms-lite' ), [ 'span' => [ 'class' => [], ], ] ), esc_attr( sanitize_text_field( $this->get_provider_name() ) ) ); ?> </p> <p class="process-completed"> <?php echo wp_kses( __( 'Congrats, the import process has finished! We have successfully imported <span class="forms-completed"></span> forms. You can review the results below.', 'wpforms-lite' ), [ 'span' => [ 'class' => [], ], ] ); ?> </p> <div class="status"></div> </div> <?php } /** * Various Underscores templates for form importing. * * @since 1.6.6 */ public function importer_templates() { ?> <script type="text/html" id="tmpl-wpforms-importer-upgrade"> <# _.each( data, function( item, key ) { #> <ul> <li class="form">{{ item.name }}</li> <# _.each( item.fields, function( val, key ) { #> <li>{{ val }}</li> <# }) #> </ul> <# }) #> </script> <script type="text/html" id="tmpl-wpforms-importer-status-error"> <div class="item"> <div class="wpforms-clear"> <span class="name"> <i class="status-icon fa fa-times" aria-hidden="true"></i> {{ data.name }} </span> </div> <p>{{ data.msg }}</p> </div> </script> <script type="text/html" id="tmpl-wpforms-importer-status-update"> <div class="item"> <div class="wpforms-clear"> <span class="name"> <# if ( ! _.isEmpty( data.upgrade_omit ) ) { #> <i class="status-icon fa fa-exclamation-circle" aria-hidden="true"></i> <# } else if ( ! _.isEmpty( data.upgrade_plain ) ) { #> <i class="status-icon fa fa-exclamation-triangle" aria-hidden="true"></i> <# } else if ( ! _.isEmpty( data.unsupported ) ) { #> <i class="status-icon fa fa-info-circle" aria-hidden="true"></i> <# } else { #> <i class="status-icon fa fa-check" aria-hidden="true"></i> <# } #> {{ data.name }} </span> <span class="actions"> <a href="{{ data.edit }}" target="_blank"><?php esc_html_e( 'Edit', 'wpforms-lite' ); ?></a> <span class="sep">|</span> <a href="{{ data.preview }}" target="_blank"><?php esc_html_e( 'Preview', 'wpforms-lite' ); ?></a> </span> </div> <# if ( ! _.isEmpty( data.upgrade_omit ) ) { #> <p><?php esc_html_e( 'The following fields are available in PRO and were not imported:', 'wpforms-lite' ); ?></p> <ul> <# _.each( data.upgrade_omit, function( val, key ) { #> <li>{{ val }}</li> <# }) #> </ul> <# } #> <# if ( ! _.isEmpty( data.upgrade_plain ) ) { #> <p><?php esc_html_e( 'The following fields are available in PRO and were imported as text fields:', 'wpforms-lite' ); ?></p> <ul> <# _.each( data.upgrade_plain, function( val, key ) { #> <li>{{ val }}</li> <# }) #> </ul> <# } #> <# if ( ! _.isEmpty( data.unsupported ) ) { #> <p><?php esc_html_e( 'The following fields are not supported and were not imported:', 'wpforms-lite' ); ?></p> <ul> <# _.each( data.unsupported, function( val, key ) { #> <li>{{ val }}</li> <# }) #> </ul> <# } #> <# if ( ! _.isEmpty( data.upgrade_plain ) || ! _.isEmpty( data.upgrade_omit ) ) { #> <p> <?php esc_html_e( 'Upgrade to the PRO plan to import these fields.', 'wpforms-lite' ); ?><br><br> <a href="<?php echo esc_url( wpforms_admin_upgrade_link( 'tools-import' ) ); ?>" class="wpforms-btn wpforms-btn-orange wpforms-btn-md wpforms-upgrade-modal" target="_blank" rel="noopener noreferrer"> <?php esc_html_e( 'Upgrade Now', 'wpforms-lite' ); ?> </a> </p> <# } #> </div> </script> <?php } } Tools/Views/System.php 0000644 00000033305 15252506741 0010751 0 ustar 00 <?php // phpcs:ignore Generic.Commenting.DocComment.MissingShort /** @noinspection PhpIllegalPsrClassPathInspection */ namespace WPForms\Admin\Tools\Views; use WPForms\Helpers\DB; /** * Class System. * * @since 1.6.6 */ class System extends View { /** * View slug. * * @since 1.6.6 * * @var string */ protected $slug = 'system'; /** * Init view. * * @since 1.6.6 */ public function init() {} /** * Get view label. * * @since 1.6.6 * * @return string */ public function get_label() { return esc_html__( 'System Info', 'wpforms-lite' ); } /** * Checking user capability to view. * * @since 1.6.6 * * @return bool */ public function check_capability() { return wpforms_current_user_can(); } /** * System view content. * * @since 1.6.6 */ public function display() { ?> <div class="wpforms-setting-row tools wpforms-settings-row-system-information"> <h4 id="form-export"><?php esc_html_e( 'System Information', 'wpforms-lite' ); ?></h4> <textarea id="wpforms-system-information" class="info-area" readonly> <?php echo esc_textarea( $this->get_system_info() ); ?> </textarea> <button type="button" id="wpforms-system-information-copy" class="wpforms-btn wpforms-btn-md wpforms-btn-light-grey"> <?php esc_html_e( 'Copy System Information', 'wpforms-lite' ); ?> </button> </div> <div class="wpforms-setting-row tools wpforms-settings-row-test-ssl"> <h4 id="ssl-verify"><?php esc_html_e( 'Test SSL Connections', 'wpforms-lite' ); ?></h4> <p class="desc"><?php esc_html_e( 'Click the button below to verify your web server can perform SSL connections successfully.', 'wpforms-lite' ); ?></p> <button type="button" id="wpforms-ssl-verify" class="wpforms-btn wpforms-btn-md wpforms-btn-orange"> <?php esc_html_e( 'Test Connection', 'wpforms-lite' ); ?> </button> </div> <?php DB::flush_existing_tables_cache(); if ( DB::custom_tables_exist() ) { return; } ?> <div class="wpforms-setting-row tools wpforms-settings-row-recreate-tables"> <h4 id="recreate-tables"><?php esc_html_e( 'Recreate custom tables', 'wpforms-lite' ); ?></h4> <p class="desc"><?php esc_html_e( 'Click the button below to recreate WPForms custom database tables.', 'wpforms-lite' ); ?></p> <button type="button" id="wpforms-recreate-tables" class="wpforms-btn wpforms-btn-md wpforms-btn-orange"> <?php esc_html_e( 'Recreate Tables', 'wpforms-lite' ); ?> </button> </div> <?php } /** * Get system information. * * Based on a function from Easy Digital Downloads by Pippin Williamson. * * @link https://github.com/easydigitaldownloads/easy-digital-downloads/blob/master/includes/admin/tools.php#L470 * * @since 1.6.6 * * @return string */ public function get_system_info() { $data = '### Begin System Info ###' . "\n\n"; $data .= $this->wpforms_info(); $data .= $this->site_info(); $data .= $this->wp_info(); $data .= $this->uploads_info(); $data .= $this->plugins_info(); $data .= $this->server_info(); $data .= "\n" . '### End System Info ###'; return $data; } /** * Get WPForms info. * * @since 1.6.6 * * @return string */ private function wpforms_info() { $activated = get_option( 'wpforms_activated', [] ); $data = '-- WPForms Info' . "\n\n"; $values = []; if ( ! empty( $activated['pro'] ) ) { $values['Pro'] = $this->get_formatted_datetime( $activated['pro'] ); } if ( ! empty( $activated['lite'] ) ) { $values['Lite'] = $this->get_formatted_datetime( $activated['lite'] ); } $values['Lite Connect'] = $this->get_lite_connect_info(); /** * Filters the WPForms information values in the system info. * * Allows developers to add or modify information in the WPForms info section * that appears in the system information tool. * * @since 1.10.0 * * @param array $values Associative array of WPForms information key-value pairs. */ $values = (array) apply_filters( 'wpforms_admin_tools_views_system_wpforms_info', $values ); foreach ( $values as $key => $value ) { $data .= $key . ': ' . $value . "\n"; } return $data; } /** * Get Site info. * * @since 1.6.6 * * @return string */ private function site_info() { $data = "\n" . '-- Site Info' . "\n\n"; $data .= 'Site URL: ' . site_url() . "\n"; $data .= 'Home URL: ' . home_url() . "\n"; $data .= 'Multisite: ' . ( is_multisite() ? 'Yes' : 'No' ) . "\n"; return $data; } /** * Get WordPress Configuration info. * * @since 1.6.6 * * @return string */ private function wp_info() { global $wpdb; $theme_data = wp_get_theme(); $theme = $theme_data->name . ' ' . $theme_data->version; $data = "\n" . '-- WordPress Configuration' . "\n\n"; $data .= 'Version: ' . get_bloginfo( 'version' ) . "\n"; $data .= 'Language: ' . get_locale() . "\n"; $data .= 'User Language: ' . get_user_locale() . "\n"; $data .= 'Permalink Structure: ' . ( get_option( 'permalink_structure' ) ? get_option( 'permalink_structure' ) : 'Default' ) . "\n"; $data .= 'Active Theme: ' . $theme . "\n"; $data .= 'Show On Front: ' . get_option( 'show_on_front' ) . "\n"; // Only show page specs if the front page is set to 'page'. if ( get_option( 'show_on_front' ) === 'page' ) { $front_page_id = get_option( 'page_on_front' ); $blog_page_id = get_option( 'page_for_posts' ); $data .= 'Page On Front: ' . ( $front_page_id ? get_the_title( $front_page_id ) . ' (#' . $front_page_id . ')' : 'Unset' ) . "\n"; $data .= 'Page For Posts: ' . ( $blog_page_id ? get_the_title( $blog_page_id ) . ' (#' . $blog_page_id . ')' : 'Unset' ) . "\n"; } $data .= 'ABSPATH: ' . ABSPATH . "\n"; $data .= 'Table Prefix: ' . 'Length: ' . strlen( $wpdb->prefix ) . ' Status: ' . ( strlen( $wpdb->prefix ) > 16 ? 'ERROR: Too long' : 'Acceptable' ) . "\n"; //phpcs:ignore $data .= 'WP_DEBUG: ' . ( defined( 'WP_DEBUG' ) ? WP_DEBUG ? 'Enabled' : 'Disabled' : 'Not set' ) . "\n"; $data .= 'WPFORMS_DEBUG: ' . ( defined( 'WPFORMS_DEBUG' ) ? WPFORMS_DEBUG ? 'Enabled' : 'Disabled' : 'Not set' ) . "\n"; $data .= 'Memory Limit: ' . WP_MEMORY_LIMIT . "\n"; $data .= 'Registered Post Stati: ' . implode( ', ', get_post_stati() ) . "\n"; $data .= 'Revisions: ' . ( WP_POST_REVISIONS ? WP_POST_REVISIONS > 1 ? 'Limited to ' . WP_POST_REVISIONS : 'Enabled' : 'Disabled' ) . "\n"; return $data; } /** * Get Uploads/Constants info. * * @since 1.6.6 * * @return string */ private function uploads_info() { // @todo WPForms configuration/specific details. $data = "\n" . '-- WordPress Uploads/Constants' . "\n\n"; $data .= 'WP_CONTENT_DIR: ' . ( defined( 'WP_CONTENT_DIR' ) ? WP_CONTENT_DIR ? WP_CONTENT_DIR : 'Disabled' : 'Not set' ) . "\n"; $data .= 'WP_CONTENT_URL: ' . ( defined( 'WP_CONTENT_URL' ) ? WP_CONTENT_URL ? WP_CONTENT_URL : 'Disabled' : 'Not set' ) . "\n"; $data .= 'UPLOADS: ' . ( defined( 'UPLOADS' ) ? UPLOADS ? UPLOADS : 'Disabled' : 'Not set' ) . "\n"; $uploads_dir = wp_upload_dir(); $data .= 'wp_uploads_dir() path: ' . $uploads_dir['path'] . "\n"; $data .= 'wp_uploads_dir() url: ' . $uploads_dir['url'] . "\n"; $data .= 'wp_uploads_dir() basedir: ' . $uploads_dir['basedir'] . "\n"; $data .= 'wp_uploads_dir() baseurl: ' . $uploads_dir['baseurl'] . "\n"; return $data; } /** * Get Plugins info. * * @since 1.6.6 * * @return string */ private function plugins_info() { // Get plugins that have an update. $data = $this->mu_plugins(); $data .= $this->installed_plugins(); $data .= $this->multisite_plugins(); return $data; } /** * Get MU Plugins info. * * @since 1.6.6 * * @return string */ private function mu_plugins() { $data = ''; // Must-use plugins. // NOTE: MU plugins can't show updates! $muplugins = get_mu_plugins(); if ( ! empty( $muplugins ) && count( $muplugins ) > 0 ) { $data = "\n" . '-- Must-Use Plugins' . "\n\n"; foreach ( $muplugins as $plugin_data ) { $data .= $plugin_data['Name'] . ': ' . $plugin_data['Version'] . "\n"; } } return $data; } /** * Get Installed Plugins info. * * @since 1.6.6 * * @return string */ private function installed_plugins() { $updates = get_plugin_updates(); // WordPress active plugins. $data = "\n" . '-- WordPress Active Plugins' . "\n\n"; $plugins = get_plugins(); $active_plugins = get_option( 'active_plugins', [] ); foreach ( $plugins as $plugin_path => $plugin ) { if ( ! in_array( $plugin_path, $active_plugins, true ) ) { continue; } $update = ( array_key_exists( $plugin_path, $updates ) ) ? ' (needs update - ' . $updates[ $plugin_path ]->update->new_version . ')' : ''; $data .= $plugin['Name'] . ': ' . $plugin['Version'] . $update . "\n"; } // WordPress inactive plugins. $data .= "\n" . '-- WordPress Inactive Plugins' . "\n\n"; foreach ( $plugins as $plugin_path => $plugin ) { if ( in_array( $plugin_path, $active_plugins, true ) ) { continue; } $update = ( array_key_exists( $plugin_path, $updates ) ) ? ' (needs update - ' . $updates[ $plugin_path ]->update->new_version . ')' : ''; $data .= $plugin['Name'] . ': ' . $plugin['Version'] . $update . "\n"; } return $data; } /** * Get Multisite Plugins info. * * @since 1.6.6 * * @return string */ private function multisite_plugins() { $data = ''; if ( ! is_multisite() ) { return $data; } $updates = get_plugin_updates(); // WordPress Multisite active plugins. $data = "\n" . '-- Network Active Plugins' . "\n\n"; $plugins = wp_get_active_network_plugins(); $active_plugins = get_site_option( 'active_sitewide_plugins', [] ); foreach ( $plugins as $plugin_path ) { $plugin_base = plugin_basename( $plugin_path ); if ( ! array_key_exists( $plugin_base, $active_plugins ) ) { continue; } $update = ( array_key_exists( $plugin_path, $updates ) ) ? ' (needs update - ' . $updates[ $plugin_path ]->update->new_version . ')' : ''; $plugin = get_plugin_data( $plugin_path ); $data .= $plugin['Name'] . ': ' . $plugin['Version'] . $update . "\n"; } return $data; } /** * Get Server info. * * @since 1.6.6 * * @return string */ private function server_info() { global $wpdb; // Server configuration (really just versions). $data = "\n" . '-- Webserver Configuration' . "\n\n"; $data .= 'PHP Version: ' . PHP_VERSION . "\n"; $data .= 'MySQL Version: ' . $wpdb->db_version() . "\n"; $data .= 'Webserver Info: ' . ( isset( $_SERVER['SERVER_SOFTWARE'] ) ? sanitize_text_field( wp_unslash( $_SERVER['SERVER_SOFTWARE'] ) ) : '' ) . "\n"; // PHP configs... now we're getting to the important stuff. $data .= "\n" . '-- PHP Configuration' . "\n\n"; $data .= 'Memory Limit: ' . ini_get( 'memory_limit' ) . "\n"; $data .= 'Upload Max Size: ' . ini_get( 'upload_max_filesize' ) . "\n"; $data .= 'Post Max Size: ' . ini_get( 'post_max_size' ) . "\n"; $data .= 'Upload Max Filesize: ' . ini_get( 'upload_max_filesize' ) . "\n"; $data .= 'Time Limit: ' . ini_get( 'max_execution_time' ) . "\n"; $data .= 'Max Input Vars: ' . ini_get( 'max_input_vars' ) . "\n"; $data .= 'Display Errors: ' . ( ini_get( 'display_errors' ) ? 'On (' . ini_get( 'display_errors' ) . ')' : 'N/A' ) . "\n"; // PHP extensions and such. $data .= "\n" . '-- PHP Extensions' . "\n\n"; $data .= 'cURL: ' . ( function_exists( 'curl_init' ) ? 'Supported' : 'Not Supported' ) . "\n"; $data .= 'fsockopen: ' . ( function_exists( 'fsockopen' ) ? 'Supported' : 'Not Supported' ) . "\n"; $data .= 'SOAP Client: ' . ( class_exists( 'SoapClient', false ) ? 'Installed' : 'Not Installed' ) . "\n"; $data .= 'Suhosin: ' . ( extension_loaded( 'suhosin' ) ? 'Installed' : 'Not Installed' ) . "\n"; // Session stuff. $data .= "\n" . '-- Session Configuration' . "\n\n"; $data .= 'Session: ' . ( isset( $_SESSION ) ? 'Enabled' : 'Disabled' ) . "\n"; // The rest of this is only relevant if session is enabled. if ( isset( $_SESSION ) ) { $data .= 'Session Name: ' . esc_html( ini_get( 'session.name' ) ) . "\n"; $data .= 'Cookie Path: ' . esc_html( ini_get( 'session.cookie_path' ) ) . "\n"; $data .= 'Save Path: ' . esc_html( ini_get( 'session.save_path' ) ) . "\n"; $data .= 'Use Cookies: ' . ( ini_get( 'session.use_cookies' ) ? 'On' : 'Off' ) . "\n"; $data .= 'Use Only Cookies: ' . ( ini_get( 'session.use_only_cookies' ) ? 'On' : 'Off' ) . "\n"; } return $data; } /** * Get Lite Connect status info string. * * @since 1.7.5 * * @return string */ private function get_lite_connect_info() { $lc_enabled = wpforms_setting( 'lite-connect-enabled' ); $lc_enabled_since = wpforms_setting( 'lite-connect-enabled-since' ); $date = $this->get_formatted_datetime( $lc_enabled_since ); if ( $lc_enabled ) { $string = $lc_enabled_since ? 'Backup is enabled since ' . $date : 'Backup is enabled'; } else { $string = $lc_enabled_since ? 'Backup is not enabled. Previously was enabled since ' . $date : 'Backup is not enabled'; } return $string; } /** * Get formatted datetime. * * @since 1.8.5 * * @param int|string $date Date. * * @return string */ private function get_formatted_datetime( $date ) { return sprintf( '%1$s at %2$s (GMT)', gmdate( 'M j, Y', $date ), gmdate( 'g:ia', $date ) ); } } Tools/Views/AiMcp.php 0000644 00000003556 15252506741 0010463 0 ustar 00 <?php namespace WPForms\Admin\Tools\Views; use WPForms\Integrations\AiMcp\AiMcp as AiMcpIntegration; /** * AI MCP Tools tab — promotes WPVibe MCP bridge and gates write access for Abilities API. * * @since 1.10.2 */ class AiMcp extends View { /** * View slug. * * @since 1.10.2 * * @var string */ protected $slug = 'ai-mcp'; /** * Init the view. No-op because asset enqueuing and AJAX registration live * on the Integration class so they run independently of which tab is open. * * @since 1.10.2 */ public function init(): void { // Hooks live on the AiMcp Integration class so the AJAX handler can register regardless of which Tools tab is open. } /** * Tab label used in the Tools navigation. * * @since 1.10.2 * * @return string */ public function get_label(): string { return __( 'AI MCP', 'wpforms-lite' ); } /** * Gate the tab on admin-only capability. Same bar as Logs and CodeSnippets. * * @since 1.10.2 * * @return bool */ public function check_capability(): bool { /** * Filters whether the current user may view the AI MCP tab. * * @since 1.10.2 * * @param bool $can Defaults to manage_options plus the Abilities API (WP 6.9+) being present. * Without the Abilities API no WPForms ability registers, so the tab and its * write toggle would have nothing to act on. */ return (bool) apply_filters( 'wpforms_admin_tools_views_ai_mcp_check_capability', wpforms_current_user_can() && function_exists( 'wp_register_ability' ) ); } /** * Resolve WPVibe state + the current toggle value and render the template. * * @since 1.10.2 */ public function display(): void { echo wpforms_render( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 'admin/tools/ai-mcp', AiMcpIntegration::get_template_data(), true ); } } Tools/Views/Logs.php 0000644 00000020312 15252506741 0010363 0 ustar 00 <?php namespace WPForms\Admin\Tools\Views; use WPForms\Logger\Log; use WPForms\Logger\ListTable; /** * Class Logs. * * @since 1.6.6 */ class Logs extends View { /** * View slug. * * @since 1.6.6 * * @var string */ protected $slug = 'logs'; /** * ListTable instance. * * @since 1.6.6 * * @var ListTable */ private $list_table = []; /** * Init view. * * @since 1.6.6 */ public function init() { $this->logs_controller(); } /** * Get view label. * * @since 1.6.6 * * @return string */ public function get_label() { return esc_html__( 'Logs', 'wpforms-lite' ); } /** * Checking user capability to view. * * @since 1.6.6 * * @return bool */ public function check_capability() { return wpforms_current_user_can(); } /** * Get ListTable instance. * * @since 1.6.6 * * @return ListTable */ private function get_list_table(): ListTable { if ( empty( $this->list_table ) ) { $log_obj = wpforms()->obj( 'log' ); if ( $log_obj ) { $this->list_table = $log_obj->get_list_table(); } } return $this->list_table; } /** * Display view content. * * @since 1.6.6 */ public function display() { ?> <form action="<?php echo esc_url( $this->get_link() ); ?>" method="POST"> <?php $this->nonce_field(); ?> <div class="wpforms-setting-row tools"> <h4><?php esc_html_e( 'Log Settings', 'wpforms-lite' ); ?></h4> <p><?php esc_html_e( 'Enable and configure the logging functionality while debugging behavior of various parts of the plugin, including form and entry processing.', 'wpforms-lite' ); ?></p> </div> <div class="wpforms-setting-row tools wpforms-setting-row-toggle wpforms-clear" id="wpforms-setting-row-logs-enable"> <div class="wpforms-setting-label"> <label for="wpforms-setting-logs-enable"><?php esc_html_e( 'Enable Logs', 'wpforms-lite' ); ?></label> </div> <div class="wpforms-setting-field"> <span class="wpforms-toggle-control"> <input type="checkbox" id="wpforms-setting-logs-enable" name="logs-enable" value="1" <?php checked( wpforms_setting( 'logs-enable' ) ); ?>> <label class="wpforms-toggle-control-icon" for="wpforms-setting-logs-enable"></label> <label for="wpforms-setting-logs-enable" class="wpforms-toggle-control-status" data-on="On" data-off="Off"> <?php wpforms_setting( 'logs-enable' ) ? esc_html_e( 'On', 'wpforms-lite' ) : esc_html_e( 'Off', 'wpforms-lite' ); ?> </label> </span> <p class="desc"> <?php esc_html_e( 'Start logging WPForms-related events. This is recommended only while debugging.', 'wpforms-lite' ); ?> </p> </div> </div> <div class="wpforms-logs-settings <?php echo wpforms_setting( 'logs-enable' ) ? '' : 'wpforms-hidden'; ?>"> <?php $this->types_block(); $this->user_roles_block(); $this->users_block(); ?> </div> <p class="submit"> <button class="wpforms-btn wpforms-btn-md wpforms-btn-orange" name="wpforms-settings-submit"> <?php esc_html_e( 'Save Settings', 'wpforms-lite' ); ?> </button> </p> </form> <?php $logs_list_table = $this->get_list_table(); if ( wpforms_setting( 'logs-enable' ) || $logs_list_table->get_total() ) { $logs_list_table->display_page(); } } /** * Types block. * * @since 1.6.6 */ private function types_block() { ?> <div class="wpforms-setting-row tools wpforms-setting-row-select wpforms-clear" id="wpforms-setting-row-log-types"> <div class="wpforms-setting-label"> <label for="wpforms-setting-logs-types"><?php esc_html_e( 'Log Types', 'wpforms-lite' ); ?></label> </div> <div class="wpforms-setting-field"> <span class="choicesjs-select-wrap"> <select id="wpforms-setting-logs-types" class="choicesjs-select" name="logs-types[]" multiple size="1"> <?php $log_types = wpforms_setting( 'logs-types', [] ); foreach ( Log::get_log_types() as $slug => $name ) { ?> <option value="<?php echo esc_attr( $slug ); ?>" <?php selected( in_array( $slug, $log_types, true ) ); ?> > <?php echo esc_html( $name ); ?> </option> <?php } ?> </select> </span> <p class="desc"><?php esc_html_e( 'Select the types of events you want to log. Everything is logged by default.', 'wpforms-lite' ); ?></p> </div> </div> <?php } /** * User roles block. * * @since 1.6.6 */ private function user_roles_block() { ?> <div class="wpforms-setting-row tools wpforms-setting-row-select wpforms-clear" id="wpforms-setting-row-log-user-roles"> <div class="wpforms-setting-label"> <label for="wpforms-setting-logs-user-roles"><?php esc_html_e( 'User Roles', 'wpforms-lite' ); ?></label> </div> <div class="wpforms-setting-field"> <span class="choicesjs-select-wrap"> <?php $logs_user_roles = wpforms_setting( 'logs-user-roles', [] ); $roles = wp_list_pluck( get_editable_roles(), 'name' ); ?> <select id="wpforms-setting-logs-user-roles" class="choicesjs-select" name="logs-user-roles[]" multiple size="1"> <?php foreach ( $roles as $slug => $name ) { ?> <option value="<?php echo esc_attr( $slug ); ?>" <?php selected( in_array( $slug, $logs_user_roles, true ) ); ?> > <?php echo esc_html( $name ); ?> </option> <?php } ?> </select> <span class="hidden" id="wpforms-setting-logs-user-roles-selectform-spinner"> <i class="fa fa-cog fa-spin fa-lg"></i> </span> </span> <p class="desc"> <?php esc_html_e( 'Select the user roles you want to log. All roles are logged by default.', 'wpforms-lite' ); ?> </p> </div> </div> <?php } /** * Users block. * * @since 1.6.6 */ private function users_block() { ?> <div class="wpforms-setting-row tools wpforms-setting-row-select wpforms-clear" id="wpforms-setting-row-log-users"> <div class="wpforms-setting-label"> <label for="wpforms-setting-logs-users"><?php esc_html_e( 'Users', 'wpforms-lite' ); ?></label> </div> <div class="wpforms-setting-field"> <span class="choicesjs-select-wrap"> <select id="wpforms-setting-logs-users" class="choicesjs-select" name="logs-users[]" multiple size="1"> <?php $users = get_users( [ 'fields' => [ 'ID', 'display_name' ] ] ); $users = wp_list_pluck( $users, 'display_name', 'ID' ); $logs_users = wpforms_setting( 'logs-users', [] ); foreach ( $users as $slug => $name ) { ?> <option value="<?php echo esc_attr( $slug ); ?>" <?php selected( in_array( $slug, $logs_users, true ) ); ?> > <?php echo esc_html( $name ); ?> </option> <?php } ?> </select> <span class="hidden" id="wpforms-setting-logs-users-selectform-spinner"> <i class="fa fa-cog fa-spin fa-lg"></i> </span> </span> <p class="desc"> <?php esc_html_e( 'Log events for specific users only. All users are logged by default.', 'wpforms-lite' ); ?> </p> </div> </div> <?php } /** * Controller. * * @since 1.6.6 */ private function logs_controller() { if ( $this->verify_nonce() ) { $this->update_settings(); } $logs_list_table = $this->get_list_table(); $logs_list_table->process_admin_ui(); } /** * Update settings. * * @since 1.8.7 * * @return void */ public function update_settings() { $settings = get_option( 'wpforms_settings' ); $was_enabled = ! empty( $settings['logs-enable'] ) ? $settings['logs-enable'] : 0; $settings['logs-enable'] = filter_input( INPUT_POST, 'logs-enable', FILTER_VALIDATE_BOOLEAN ); $logs_types = filter_input( INPUT_POST, 'logs-types', FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_REQUIRE_ARRAY ); $logs_user_roles = filter_input( INPUT_POST, 'logs-user-roles', FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_REQUIRE_ARRAY ); $logs_users = filter_input( INPUT_POST, 'logs-users', FILTER_SANITIZE_NUMBER_INT, FILTER_REQUIRE_ARRAY ); if ( $was_enabled ) { $settings['logs-types'] = $logs_types ? $logs_types : []; $settings['logs-user-roles'] = $logs_user_roles ? $logs_user_roles : []; $settings['logs-users'] = $logs_users ? array_map( 'absint', $logs_users ) : []; } wpforms_update_settings( $settings ); } } Tools/Views/ActionScheduler.php 0000644 00000002571 15252506741 0012542 0 ustar 00 <?php namespace WPForms\Admin\Tools\Views; use ActionScheduler_AdminView; /** * Class ActionScheduler view. * * @since 1.6.6 */ class ActionScheduler extends View { /** * View slug. * * @since 1.6.6 * * @var string */ protected $slug = 'action-scheduler'; /** * Init view. * * @since 1.6.6 */ public function init() { if ( $this->admin_view_exists() ) { ActionScheduler_AdminView::instance()->process_admin_ui(); } } /** * Get link to the view. * * @since 1.6.9 * * @return string */ public function get_link() { return add_query_arg( [ 's' => 'wpforms', ], parent::get_link() ); } /** * Get view label. * * @since 1.6.6 * * @return string */ public function get_label() { return esc_html__( 'Scheduled Actions', 'wpforms-lite' ); } /** * Checking user capability to view. * * @since 1.6.6 * * @return bool */ public function check_capability() { return wpforms_current_user_can(); } /** * Display view content. * * @since 1.6.6 */ public function display() { if ( ! $this->admin_view_exists() ) { return; } ( new ActionSchedulerList() )->display_page(); } /** * Check if ActionScheduler_AdminView class exists. * * @since 1.6.6 * * @return bool */ private function admin_view_exists() { return class_exists( 'ActionScheduler_AdminView' ); } } Tools/Views/Export.php 0000644 00000011666 15252506741 0010754 0 ustar 00 <?php namespace WPForms\Admin\Tools\Views; use WPForms\Admin\Education\Admin\Tools\ExportEntries; // phpcs:ignore WPForms.PHP.UseStatement.UnusedUseStatement use WPForms\Admin\Tools\Export\Views\ExportViewsInterface; use WPForms\Admin\Tools\Export\Views\Forms\Page as FormsPage; use WPForms\Admin\Tools\Export\Views\Templates\Page as TemplatesPage; use WPForms\Admin\Tools\Tools; /** * Class Export. * Handles the Export page with a tabbed interface for Entries, Forms, and Templates export. * * @since 1.6.6 * @since 1.10.1 More export blocks to separate tabs and handle independently. */ class Export extends View { /** * View slug. * * @since 1.6.6 * * @var string */ protected $slug = 'export'; /** * Available sub-views (tabs). * * @since 1.10.1 * * @var array */ private $sub_views = []; /** * The current tab slug. * * @since 1.10.1 * * @var string */ private $active_tab_slug; /** * The current tab view. * * @since 1.10.1 * * @var null|ExportViewsInterface */ private $active_view; /** * Init view. * * @since 1.10.1 */ public function init(): void { $this->init_active_view(); } /** * Get view label. * * @since 1.6.6 * * @return string */ public function get_label(): string { return esc_html__( 'Export', 'wpforms-lite' ); } /** * Checking user capability to view. * * @since 1.6.6 * * @return bool */ public function check_capability(): bool { return wpforms_current_user_can( [ 'edit_forms', 'view_entries' ] ); } /** * Display view content. * * @since 1.6.6 */ public function display(): void { if ( empty( $this->active_view ) ) { return; } do_action( 'wpforms_admin_tools_export_top' ); $this->display_tabs(); $this->active_view->display(); do_action( 'wpforms_admin_tools_export_bottom' ); } /** * Initialize the active sub-view (tab). * * @since 1.10.1 */ private function init_active_view(): void { $view_ids = array_keys( $this->get_sub_views() ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended $this->active_tab_slug = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : (string) reset( $view_ids ); // If the user tries to load an invalid view - fallback to the first available. if ( ! in_array( $this->active_tab_slug, $view_ids, true ) ) { $this->active_tab_slug = reset( $view_ids ); } if ( ! isset( $this->sub_views[ $this->active_tab_slug ] ) ) { return; } $this->active_view = $this->sub_views[ $this->active_tab_slug ]; $this->active_view->init(); } /** * Get available sub-views (tabs). * * @since 1.10.1 * * @return array */ private function get_sub_views(): array { if ( ! empty( $this->sub_views ) ) { return $this->sub_views; } $views = [ 'entries' => new ExportEntries(), ]; /** * Allow extending export views. * * @since 1.10.1 * * @param array $views Array of views where the key is slug. */ $this->sub_views = (array) apply_filters( 'wpforms_admin_tools_views_export_get_sub_views', $views ); // Forms and Templates are always available. $this->sub_views['forms'] = new FormsPage(); $this->sub_views['templates'] = new TemplatesPage(); if ( wpforms()->is_pro() ) { // Ensure entries is first in an array (default tab) for Pro users. $this->sub_views = array_merge( [ 'entries' => $this->sub_views['entries'] ], $this->sub_views ); } else { // For Lite users, put Forms and Templates first since the Entries tab is an upgrade pitch. $this->sub_views = array_merge( [ 'forms' => $this->sub_views['forms'], 'templates' => $this->sub_views['templates'], ], $this->sub_views ); } $this->sub_views = array_filter( $this->sub_views, static function ( $view ) { return $view->current_user_can(); } ); return $this->sub_views; } /** * Display tabs. * * @since 1.10.1 */ private function display_tabs(): void { $views = $this->get_sub_views(); // Remove views that should not be displayed as tabs. $views = array_filter( $views, static function ( $view ) { return ! empty( $view->get_tab_label() ); } ); // If there is only one view - no need to display tabs. if ( count( $views ) === 1 ) { return; } ?> <div class="wpforms-tabs-wrapper"> <nav class="nav-tab-wrapper"> <?php foreach ( $views as $slug => $view ) : ?> <a href="<?php echo esc_url( $this->get_tab_url( $slug ) ); ?>" class="nav-tab <?php echo $slug === $this->active_tab_slug ? 'nav-tab-active' : ''; ?>"> <?php echo esc_html( $view->get_tab_label() ); ?> </a> <?php endforeach; ?> </nav> </div> <?php } /** * Get tab URL. * * @since 1.10.1 * * @param string $tab Tab slug. * * @return string */ private function get_tab_url( string $tab ): string { return add_query_arg( [ 'page' => Tools::SLUG, 'view' => 'export', 'tab' => $tab, ], admin_url( 'admin.php' ) ); } } Tools/Views/Import.php 0000644 00000011161 15252506741 0010733 0 ustar 00 <?php namespace WPForms\Admin\Tools\Views; use WPForms\Admin\Education\Admin\Tools\ImportEntries; use WPForms\Admin\Tools\Import\Views\Forms\Page as FormsPage; // phpcs:ignore WPForms.PHP.UseStatement.UnusedUseStatement use WPForms\Admin\Tools\Import\Views\ImportViewsInterface; use WPForms\Admin\Tools\Tools; /** * Import View class. * Handles the Import page with a tabbed interface for Entries and Forms import. * * @since 1.6.6 * @since 1.10.1 More import blocks to separate tabs and handle independently. */ class Import extends View { /** * View slug. * * @since 1.6.6 * * @var string */ protected $slug = 'import'; /** * Available sub-views (tabs). * * @since 1.10.1 * * @var array */ private $sub_views = []; /** * The current tab slug. * * @since 1.10.1 * * @var string */ private $active_tab_slug; /** * The current tab view. * * @since 1.10.1 * * @var null|ImportViewsInterface */ private $active_view; /** * Init view. * * @since 1.6.6 */ public function init(): void { $this->init_active_view(); } /** * Get view label. * * @since 1.6.6 * * @return string */ public function get_label(): string { return esc_html__( 'Import', 'wpforms-lite' ); } /** * Checking user capability to view. * * @since 1.6.6 * * @return bool */ public function check_capability(): bool { return wpforms_current_user_can( [ 'create_forms', 'edit_forms', 'view_entries' ] ); } /** * Display view content. * * @since 1.10.1 */ public function display(): void { if ( empty( $this->active_view ) ) { return; } $this->display_tabs(); $this->active_view->display(); } /** * Initialize the active sub-view (tab). * * @since 1.10.1 */ private function init_active_view(): void { $view_ids = array_keys( $this->get_sub_views() ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended $this->active_tab_slug = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : (string) reset( $view_ids ); // If the user tries to load an invalid view, fallback is to the first available. if ( ! in_array( $this->active_tab_slug, $view_ids, true ) ) { $this->active_tab_slug = reset( $view_ids ); } if ( ! isset( $this->sub_views[ $this->active_tab_slug ] ) ) { return; } $this->active_view = $this->sub_views[ $this->active_tab_slug ]; $this->active_view->init(); } /** * Get available sub-views (tabs). * * @since 1.10.1 * * @return array */ private function get_sub_views(): array { if ( ! empty( $this->sub_views ) ) { return $this->sub_views; } $views = [ 'entries' => new ImportEntries(), ]; /** * Allow extending import views. * * @since 1.10.1 * * @param array $views Array of views where the key is slug. */ $this->sub_views = (array) apply_filters( 'wpforms_admin_tools_views_import_get_sub_views', $views ); // Forms are always available. $this->sub_views['forms'] = new FormsPage(); if ( wpforms()->is_pro() ) { // Ensure entries is first in an array (default tab) for Pro users. $this->sub_views = array_merge( [ 'entries' => $this->sub_views['entries'] ], $this->sub_views ); } else { // For Lite users, put Forms first since the Entries tab is an upgrade pitch. $this->sub_views = array_merge( [ 'forms' => $this->sub_views['forms'] ], $this->sub_views ); } $this->sub_views = array_filter( $this->sub_views, static function ( $view ) { return $view->current_user_can(); } ); return $this->sub_views; } /** * Display tabs. * * @since 1.10.1 */ private function display_tabs(): void { $views = $this->get_sub_views(); // Remove views that should not be displayed as tabs. $views = array_filter( $views, static function ( $view ) { return ! empty( $view->get_tab_label() ); } ); // If there is only one view - no need to display tabs. if ( count( $views ) === 1 ) { return; } ?> <div class="wpforms-tabs-wrapper"> <nav class="nav-tab-wrapper"> <?php foreach ( $views as $slug => $view ) : ?> <a href="<?php echo esc_url( $this->get_tab_url( $slug ) ); ?>" class="nav-tab <?php echo $slug === $this->active_tab_slug ? 'nav-tab-active' : ''; ?>"> <?php echo esc_html( $view->get_tab_label() ); ?> </a> <?php endforeach; ?> </nav> </div> <?php } /** * Get tab URL. * * @since 1.10.1 * * @param string $tab Tab slug. * * @return string */ private function get_tab_url( string $tab ): string { return add_query_arg( [ 'page' => Tools::SLUG, 'view' => 'import', 'tab' => $tab, ], admin_url( 'admin.php' ) ); } } Tools/Importers/PirateForms.php 0000644 00000044622 15252506741 0012613 0 ustar 00 <?php // phpcs:ignore Generic.Commenting.DocComment.MissingShort /** @noinspection PhpUndefinedClassInspection */ namespace WPForms\Admin\Tools\Importers; use PirateForms_Util; use WP_Ajax_Upgrader_Skin; use WP_Query; use WPForms\Helpers\PluginSilentUpgrader; /** * Pirate Forms Importer class. * * @since 1.6.6 */ class PirateForms extends Base { /** * Direct URL to download the latest version of WP Mail SMTP plugin from WP.org repo. * * @since 1.6.6 * * @var string */ private const URL_SMTP_ZIP = 'https://downloads.wordpress.org/plugin/wp-mail-smtp.zip'; /** * WP Mail SMTP plugin basename. * * @since 1.6.6 * * @var string */ private const SLUG_SMTP_PLUGIN = 'wp-mail-smtp/wp_mail_smtp.php'; /** * Default PirateForms smart tags. * * @since 1.6.6 * * @var array */ public static $tags = [ '[email]', ]; /** * Define required properties. * * @since 1.6.6 */ public function init() { $this->name = 'Pirate Forms'; $this->slug = 'pirate-forms'; $this->path = 'pirate-forms/pirate-forms.php'; } /** * Get ALL THE FORMS. * We need only IDs and names here. * * @since 1.6.6 * * @return array */ public function get_forms() { if ( ! current_user_can( 'edit_published_posts' ) ) { return []; } // Union those arrays, as array_merge() does keys reindexing. $forms = $this->get_default_forms() + $this->get_pro_forms(); // Sort by IDs ASC. ksort( $forms ); return $forms; } /** * Pirate Forms has a default form, which doesn't have an ID. * * @since 1.6.6 * * @return array */ protected function get_default_forms() { $form = PirateForms_Util::get_form_options(); // Make sure that it's there and not broken. if ( empty( $form ) ) { return []; } return [ 0 => esc_html__( 'Default Form', 'wpforms-lite' ) ]; } /** * Copy-paste from Pro plugin code, it doesn't have API to get this data easily. * * @since 1.6.6 * * @return array */ protected function get_pro_forms() { $forms = []; $query = new WP_Query( [ 'post_type' => 'pf_form', 'post_status' => 'publish', 'posts_per_page' => - 1, 'update_post_meta_cache' => false, 'update_post_term_cache' => false, ] ); if ( $query->have_posts() ) { while ( $query->have_posts() ) { $query->the_post(); $forms[ get_the_ID() ] = get_the_title(); } } return $forms; } /** * Get a single form options. * * @since 1.6.6 * * @param int $id Form ID. * * @return array */ public function get_form( $id ) { return PirateForms_Util::get_form_options( (int) $id ); } /** * Import a single form using AJAX. * * @since 1.6.6 */ public function import_form() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded, Generic.Metrics.NestingLevel.MaxExceeded // Run a security check. check_ajax_referer( 'wpforms-admin', 'nonce' ); // Check for permissions. if ( ! wpforms_current_user_can( 'create_forms' ) ) { wp_send_json_error(); } $analyze = isset( $_POST['analyze'] ); $pf_form_id = isset( $_POST['form_id'] ) ? (int) $_POST['form_id'] : 0; $pf_form = $this->get_form( $pf_form_id ); $pf_fields_custom = PirateForms_Util::get_post_meta( $pf_form_id, 'custom' ); $pf_fields_default = [ 'name', 'email', 'subject', 'message', 'attachment', 'checkbox', 'recaptcha', ]; $fields_pro_plain = [ 'tel' ]; // Convert them in Lite to the closest Standard alternatives. $fields_pro_omit = [ 'label', 'file', 'attachment' ]; // Strict PRO fields with no Lite alternatives. $upgrade_plain = []; $upgrade_omit = []; $unsupported = []; $fields = []; if ( ! empty( $pf_fields_custom[0] ) ) { $pf_fields_custom = $pf_fields_custom[0]; } else { $pf_fields_custom = []; } if ( empty( $pf_form_id ) ) { $pf_form_name = esc_html__( 'Default Form', 'wpforms-lite' ); } else { $pf_form_name = get_post_field( 'post_title', $pf_form_id ); } // phpcs:ignore WPForms.Comments.PHPDocHooks.RequiredHookDocumentation, WPForms.PHP.ValidateHooks.InvalidHookName $pf_form_name = wpforms_decode_string( apply_filters( 'the_title', $pf_form_name, $pf_form_id ) ); // Prepare all DEFAULT fields. foreach ( $pf_fields_default as $field ) { // Ignore fields that are not displayed or not added at all. if ( empty( $pf_form[ 'pirateformsopt_' . $field . '_field' ] ) ) { continue; } // Ignore certain fields as they are dealt with later. if ( $field === 'recaptcha' ) { continue; } $required = $pf_form[ 'pirateformsopt_' . $field . '_field' ] === 'req' ? '1' : ''; $label = ! empty( $pf_form[ 'pirateformsopt_label_' . $field ] ) ? $pf_form[ 'pirateformsopt_label_' . $field ] : ucwords( $field ); // If it is Lite, and it's a field type not included, make a note then continue to the next field. if ( in_array( $field, $fields_pro_plain, true ) && ! wpforms()->is_pro() ) { $upgrade_plain[] = $label; } if ( in_array( $field, $fields_pro_omit, true ) && ! wpforms()->is_pro() ) { $upgrade_omit[] = $label; continue; } // Determine the next field ID to assign. if ( empty( $fields ) ) { $field_id = 1; } else { $field_id = (int) max( array_keys( $fields ) ) + 1; } // Separately process certain fields. switch ( $field ) { case 'name': case 'email': case 'subject': case 'message': $type = $field; if ( $field === 'subject' ) { $type = 'text'; } elseif ( $field === 'message' ) { $type = 'textarea'; } $fields[ $field_id ] = [ 'id' => $field_id, 'type' => $type, 'label' => $label, 'required' => $required, 'size' => 'medium', ]; if ( $field === 'name' ) { $fields[ $field_id ]['format'] = 'simple'; } break; case 'checkbox': $fields[ $field_id ] = [ 'id' => $field_id, 'type' => 'checkbox', 'label' => esc_html__( 'Single Checkbox Field', 'wpforms-lite' ), 'choices' => [ 1 => [ 'label' => $label, 'value' => '', ], ], 'size' => 'medium', 'required' => $required, 'label_hide' => true, ]; break; case 'attachment': case 'file': $fields[ $field_id ] = [ 'id' => $field_id, 'type' => 'file-upload', 'label' => $label, 'required' => $required, 'label_hide' => true, ]; // If PF attachments were saved into FS, we need to save them in WP Media. // That will allow admins to easily delete it if needed. if ( ! empty( $pf_form['pirateformsopt_save_attachment'] ) && $pf_form['pirateformsopt_save_attachment'] === 'yes' ) { $fields[ $field_id ]['media_library'] = true; } break; } } // Prepare all CUSTOM fields. foreach ( $pf_fields_custom as $field ) { // Ignore fields that are not displayed. if ( empty( $field['display'] ) ) { continue; } $required = $field['display'] === 'req' ? '1' : ''; // Possible values in PF: 'yes', 'req'. $label = sanitize_text_field( $field['label'] ); // If it is Lite, and it's a field type not included, make a note then continue to the next field. if ( in_array( $field['type'], $fields_pro_plain, true ) && ! wpforms()->is_pro() ) { $upgrade_plain[] = $label; } if ( in_array( $field['type'], $fields_pro_omit, true ) && ! wpforms()->is_pro() ) { $upgrade_omit[] = $label; continue; } // Determine the next field ID to assign. if ( empty( $fields ) ) { $field_id = 1; } else { $field_id = (int) max( array_keys( $fields ) ) + 1; } switch ( $field['type'] ) { case 'text': case 'textarea': case 'number': case 'tel': $type = $field['type']; if ( $field['type'] === 'textarea' ) { $type = 'textarea'; } if ( $field['type'] === 'tel' ) { $type = 'phone'; } $fields[ $field_id ] = [ 'id' => $field_id, 'type' => $type, 'label' => $label, 'required' => $required, 'size' => 'medium', ]; if ( $field['type'] === 'tel' ) { $fields[ $field_id ]['format'] = 'international'; } break; case 'checkbox': $fields[ $field_id ] = [ 'id' => $field_id, 'type' => 'checkbox', 'label' => esc_html__( 'Single Checkbox Field', 'wpforms-lite' ), 'choices' => [ 1 => [ 'label' => $label, 'value' => '', ], ], 'size' => 'medium', 'required' => $required, 'label_hide' => true, ]; break; case 'select': case 'multiselect': $options = []; $i = 1; $type = 'select'; if ( $field['type'] === 'multiselect' ) { $type = 'checkbox'; } foreach ( explode( PHP_EOL, $field['options'] ) as $option ) { $options[ $i ] = [ 'label' => $option, 'value' => '', 'image' => '', ]; ++$i; } $fields[ $field_id ] = [ 'id' => $field_id, 'type' => $type, 'label' => $label, 'required' => $required, 'size' => 'medium', 'choices' => $options, ]; break; case 'label': $fields[ $field_id ] = [ 'id' => $field_id, 'type' => 'html', 'code' => $field['label'], 'label_disable' => true, ]; break; case 'file': $fields[ $field_id ] = [ 'id' => $field_id, 'type' => 'file-upload', 'label' => $label, 'required' => $required, 'label_hide' => true, ]; // If PF attachments were saved into FS, we need to save them in WP Media. // That will allow admins to easily delete it if needed. if ( ! empty( $pf_form['pirateformsopt_save_attachment'] ) && $pf_form['pirateformsopt_save_attachment'] === 'yes' ) { $fields[ $field_id ]['media_library'] = true; } break; } } // If we are analyzing the form (in Lite only), // we can stop here and return the details about this form. if ( $analyze ) { wp_send_json_success( [ 'name' => $pf_form_name, 'upgrade_plain' => $upgrade_plain, 'upgrade_omit' => $upgrade_omit, ] ); } // Make sure we have imported some fields. if ( empty( $fields ) ) { wp_send_json_success( [ 'error' => true, 'name' => $pf_form_name, 'msg' => esc_html__( 'No form fields found.', 'wpforms-lite' ), ] ); } // Create a form array, that holds all the data. $form = [ 'id' => '', 'field_id' => '', 'fields' => $fields, 'settings' => [ 'form_title' => $pf_form_name, 'form_desc' => '', 'submit_text' => stripslashes( $pf_form['pirateformsopt_label_submit_btn'] ), 'submit_text_processing' => esc_html__( 'Sending', 'wpforms-lite' ), 'notification_enable' => '1', 'notifications' => [ 1 => [ 'notification_name' => esc_html__( 'Default Notification', 'wpforms-lite' ), 'email' => $pf_form['pirateformsopt_email_recipients'], 'subject' => sprintf( /* translators: %s - form name. */ esc_html__( 'New Entry: %s', 'wpforms-lite' ), $pf_form_name ), 'sender_name' => get_bloginfo( 'name' ), 'sender_address' => $this->get_smarttags( $pf_form['pirateformsopt_email'], $fields ), 'replyto' => '', 'message' => '{all_fields}', ], ], 'confirmations' => [ 1 => [ 'type' => empty( $pf_form['pirateformsopt_thank_you_url'] ) ? 'message' : 'page', 'page' => (int) $pf_form['pirateformsopt_thank_you_url'], 'message' => ! empty( $pf_form['pirateformsopt_label_submit'] ) ? $pf_form['pirateformsopt_label_submit'] : esc_html__( 'Thanks for contacting us! We will be in touch with you shortly.', 'wpforms-lite' ), 'message_scroll' => '1', ], ], 'disable_entries' => $pf_form['pirateformsopt_store'] === 'yes' ? '0' : '1', 'import_form_id' => $pf_form_id, ], ]; // Do not save user IP address and UA. if ( empty( $pf_form['pirateformsopt_store_ip'] ) || $pf_form['pirateformsopt_store_ip'] !== 'yes' ) { $wpforms_settings = get_option( 'wpforms_settings', [] ); $wpforms_settings['gdpr'] = true; update_option( 'wpforms_settings', $wpforms_settings ); $form['settings']['disable_ip'] = true; } // Save recaptcha keys. if ( ! empty( $pf_form['pirateformsopt_recaptcha_field'] ) && $pf_form['pirateformsopt_recaptcha_field'] === 'yes' ) { // If the user has already defined v2 reCAPTCHA keys, use those. $site_key = wpforms_setting( 'recaptcha-site-key', '' ); $secret_key = wpforms_setting( 'recaptcha-secret-key', '' ); // Try to abstract keys from PF. if ( empty( $site_key ) || empty( $secret_key ) ) { if ( ! empty( $pf_form['pirateformsopt_recaptcha_sitekey'] ) && ! empty( $pf_form['pirateformsopt_recaptcha_secretkey'] ) ) { $wpforms_settings = get_option( 'wpforms_settings', [] ); $wpforms_settings['recaptcha-site-key'] = $pf_form['pirateformsopt_recaptcha_sitekey']; $wpforms_settings['recaptcha-secret-key'] = $pf_form['pirateformsopt_recaptcha_secretkey']; $wpforms_settings['recaptcha-type'] = 'v2'; update_option( 'wpforms_settings', $wpforms_settings ); } } if ( ( ! empty( $site_key ) && ! empty( $secret_key ) ) || ( ! empty( $wpforms_settings['recaptcha-site-key'] ) && ! empty( $wpforms_settings['recaptcha-secret-key'] ) ) ) { $form['settings']['recaptcha'] = '1'; } } $this->import_smtp( $pf_form_id, $form ); $this->add_form( $form, $unsupported, $upgrade_plain, $upgrade_omit ); } /** * Replace 3rd-party form provider tags/shortcodes with our own Smart Tags. * See: PirateForms_Util::get_magic_tags() for all PF tags. * * @since 1.6.6 * * @param string $text String to process the smart tag in. * @param array $fields List of fields for the form. * * @return string */ public function get_smarttags( $text, $fields ) { // phpcs:ignore Generic.Metrics.NestingLevel.MaxExceeded foreach ( self::$tags as $tag ) { $wpf_tag = ''; if ( $tag === '[email]' ) { foreach ( $fields as $field ) { if ( $field['type'] === 'email' ) { $wpf_tag = '{field_id="' . $field['id'] . '"}'; break; } } } $text = str_replace( $tag, $wpf_tag, $text ); } return $text; } /** * Import SMTP settings from Default form only. * * @since 1.6.6 * * @param int $pf_form_id PirateForms form ID. * @param array $form WPForms form array. */ protected function import_smtp( $pf_form_id, $form ) { // At this point we import only default form SMTP settings. if ( $pf_form_id !== 0 ) { return; } $pf_form = $this->get_form( 0 ); // Use only if enabled. if ( empty( $pf_form['pirateformsopt_use_smtp'] ) || $pf_form['pirateformsopt_use_smtp'] !== 'yes' ) { return; } // If a user has WP Mail SMTP already activated - do nothing as it's most likely already configured. if ( is_plugin_active( self::SLUG_SMTP_PLUGIN ) ) { return; } // Check that we successfully installed and activated the plugin. if ( ! $this->install_activate_smtp() ) { return; } /* * Finally, start the settings importing. */ // WP Mail SMTP 1.x and PHP 5.3+ are allowed. Older WPMS versions are ignored. if ( ! function_exists( 'wp_mail_smtp' ) ) { return; } // TODO: change to \WPMailSMTP\Options in future. $options = get_option( 'wp_mail_smtp', [] ); $options['mail']['from_email'] = $this->get_smarttags( $pf_form['pirateformsopt_email'], $form['fields'] ); $options['mail']['mailer'] = 'smtp'; $options['smtp']['host'] = $pf_form['pirateformsopt_smtp_host']; $options['smtp']['port'] = $pf_form['pirateformsopt_smtp_port']; $options['smtp']['encryption'] = empty( $pf_form['pirateformsopt_use_secure'] ) ? 'none' : $pf_form['pirateformsopt_use_secure']; $options['smtp']['auth'] = ! empty( $pf_form['pirateformsopt_use_smtp_authentication'] ) && $pf_form['pirateformsopt_use_smtp_authentication'] === 'yes'; $options['smtp']['user'] = $pf_form['pirateformsopt_smtp_username']; $options['smtp']['pass'] = $pf_form['pirateformsopt_smtp_password']; update_option( 'wp_mail_smtp', $options ); } /** * Do all the voodoo to install and activate the WP Mail SMTP plugin behind the scene. * No user interaction is needed. * * @since 1.6.6 * * @return bool */ protected function install_activate_smtp() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh, WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks /* * Check installation. * If installed but not activated - bail. * We don't want to break current site email deliverability. */ if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } // FALSE will bail the import. if ( array_key_exists( self::SLUG_SMTP_PLUGIN, get_plugins() ) ) { return false; } /* * Let's try to install. */ $url = add_query_arg( [ 'provider' => $this->slug, 'page' => 'wpforms-tools', 'view' => 'importer', ], admin_url( 'admin.php' ) ); $creds = request_filesystem_credentials( esc_url_raw( $url ), '', false, false ); // Check for file system permissions. if ( $creds === false ) { return false; } if ( ! WP_Filesystem( $creds ) ) { return false; } // Do not allow WordPress to search/download translations, as this will break JS output. remove_action( 'upgrader_process_complete', [ 'Language_Pack_Upgrader', 'async_upgrade' ], 20 ); // Create the plugin upgrader with our custom skin. $installer = new PluginSilentUpgrader( new WP_Ajax_Upgrader_Skin() ); // Error check. if ( ! method_exists( $installer, 'install' ) ) { return false; } $installer->install( self::URL_SMTP_ZIP ); // Flush the cache and return the newly installed plugin basename. wp_cache_flush(); if ( $installer->plugin_info() ) { $plugin_basename = $installer->plugin_info(); // Activate, do not redirect, run the plugin activation routine. $activated = activate_plugin( $plugin_basename ); if ( ! is_wp_error( $activated ) ) { return true; } } return false; } } Tools/Importers/NinjaForms.php 0000644 00000040167 15252506741 0012426 0 ustar 00 <?php // phpcs:disable Generic.Commenting.DocComment.MissingShort /** @noinspection PhpUndefinedClassInspection */ /** @noinspection PhpUndefinedFunctionInspection */ // phpcs:enable Generic.Commenting.DocComment.MissingShort namespace WPForms\Admin\Tools\Importers; use NF_Database_Models_Action; use NF_Database_Models_Field; use NF_Database_Models_Form; /** * Ninja Forms Importer class. * * @since 1.6.6 */ class NinjaForms extends Base { /** * Define required properties. * * @since 1.6.6 */ public function init() { $this->name = 'Ninja Forms'; $this->slug = 'ninja-forms'; $this->path = 'ninja-forms/ninja-forms.php'; } /** * Get ALL THE FORMS. * * @since 1.6.6 * * @return NF_Database_Models_Form[] */ public function get_forms() { $forms_final = []; if ( ! $this->is_active() ) { return $forms_final; } // phpcs:ignore WordPress.WP.Capabilities.Unknown, WPForms.Comments.PHPDocHooks.RequiredHookDocumentation, WPForms.PHP.ValidateHooks.InvalidHookName if ( ! current_user_can( apply_filters( 'ninja_forms_admin_all_forms_capabilities', 'manage_options' ) ) ) { return $forms_final; } $forms = Ninja_Forms()->form()->get_forms(); if ( ! empty( $forms ) ) { foreach ( $forms as $form ) { if ( ! $form instanceof NF_Database_Models_Form ) { continue; } $forms_final[ $form->get_id() ] = $form->get_setting( 'title' ); } } return $forms_final; } /** * Get a single form. * * @since 1.6.6 * * @param int $id Form ID. * * @return array */ public function get_form( $id ) { $form = []; $form['settings'] = Ninja_Forms()->form( $id )->get()->get_settings(); $fields = Ninja_Forms()->form( $id )->get_fields(); $actions = Ninja_Forms()->form( $id )->get_actions(); foreach ( $fields as $field ) { if ( ! $field instanceof NF_Database_Models_Field ) { continue; } $form['fields'][] = array_merge( [ 'id' => $field->get_id(), ], $field->get_settings() ); } foreach ( $actions as $action ) { if ( ! $action instanceof NF_Database_Models_Action ) { continue; } $form['actions'][] = $action->get_settings(); } return $form; } /** * Import a single form using AJAX. * * @since 1.6.6 */ public function import_form() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded, Generic.Metrics.NestingLevel.MaxExceeded // Run a security check. check_ajax_referer( 'wpforms-admin', 'nonce' ); // Check for permissions. if ( ! wpforms_current_user_can( 'create_forms' ) ) { wp_send_json_error(); } // Define some basic information. $analyze = isset( $_POST['analyze'] ); $nf_id = ! empty( $_POST['form_id'] ) ? (int) $_POST['form_id'] : 0; $nf_form = $this->get_form( $nf_id ); $nf_form_name = $nf_form['settings']['title']; $nf_recaptcha = false; $nf_recaptcha_type = 'v2'; $fields_pro_plain = [ 'phone', 'date' ]; $fields_pro_omit = [ 'html', 'divider' ]; $fields_unsupported = [ 'spam', 'starrating', 'listmultiselect', 'hidden', 'total', 'shipping', 'quantity', 'product' ]; $upgrade_plain = []; $upgrade_omit = []; $unsupported = []; $form = [ 'id' => '', 'field_id' => '', 'fields' => [], 'settings' => [ 'form_title' => $nf_form_name, 'form_desc' => '', 'submit_text' => esc_html__( 'Submit', 'wpforms-lite' ), 'submit_text_processing' => esc_html__( 'Sending', 'wpforms-lite' ), 'antispam_v3' => '1', 'notification_enable' => '1', 'notifications' => [ 1 => [ 'notification_name' => esc_html__( 'Notification 1', 'wpforms-lite' ), 'email' => '{admin_email}', 'subject' => sprintf( /* translators: %s - form name. */ esc_html__( 'New Entry: %s', 'wpforms-lite' ), $nf_form_name ), 'sender_name' => get_bloginfo( 'name' ), 'sender_address' => '{admin_email}', 'replyto' => '', 'message' => '{all_fields}', ], ], 'confirmations' => [ 1 => [ 'type' => 'message', 'message' => esc_html__( 'Thanks for contacting us! We will be in touch with you shortly.', 'wpforms-lite' ), 'message_scroll' => '1', ], ], 'import_form_id' => $nf_id, ], ]; // If the form does not contain fields, bail. if ( empty( $nf_form['fields'] ) ) { wp_send_json_success( [ 'error' => true, 'name' => sanitize_text_field( $nf_form_name ), 'msg' => esc_html__( 'No form fields found.', 'wpforms-lite' ), ] ); } // Convert fields. foreach ( $nf_form['fields'] as $nf_field ) { // Try to determine field label to use. $label = $this->get_field_label( $nf_field ); // Next, check if the field is unsupported. // If unsupported, make note and then continue to the next field. if ( in_array( $nf_field['type'], $fields_unsupported, true ) ) { $unsupported[] = $label; continue; } // Now check if this installation is Lite. // If it is Lite, and it's a field type not included, make a note then continue to the next // field. if ( in_array( $nf_field['type'], $fields_pro_plain, true ) && ! wpforms()->is_pro() ) { $upgrade_plain[] = $label; } if ( in_array( $nf_field['type'], $fields_pro_omit, true ) && ! wpforms()->is_pro() ) { $upgrade_omit[] = $label; continue; } // Determine the next field ID to assign. if ( empty( $form['fields'] ) ) { $field_id = 1; } else { $field_id = (int) max( array_keys( $form['fields'] ) ) + 1; } switch ( $nf_field['type'] ) { // Single line text, address, city, first name, last name, // zipcode, email, number, textarea fields. case 'textbox': case 'address': case 'city': case 'firstname': case 'lastname': case 'zip': case 'email': case 'number': case 'textarea': $type = 'text'; if ( $nf_field['type'] === 'email' ) { $type = 'email'; } elseif ( $nf_field['type'] === 'number' ) { $type = 'number'; } elseif ( $nf_field['type'] === 'textarea' ) { $type = 'textarea'; } $form['fields'][ $field_id ] = [ 'id' => $field_id, 'type' => $type, 'label' => $label, 'description' => ! empty( $nf_field['desc_text'] ) ? $nf_field['desc_text'] : '', 'size' => 'medium', 'required' => ! empty( $nf_field['required'] ) ? '1' : '', 'placeholder' => ! empty( $nf_field['placeholder'] ) ? $nf_field['placeholder'] : '', 'default_value' => ! empty( $nf_field['default'] ) ? $nf_field['default'] : '', 'nf_key' => $nf_field['key'], ]; break; // Single checkbox field. case 'checkbox': $form['fields'][ $field_id ] = [ 'id' => $field_id, 'type' => 'checkbox', 'label' => esc_html__( 'Single Checkbox Field', 'wpforms-lite' ), 'choices' => [ 1 => [ 'label' => $label, 'value' => '', ], ], 'description' => ! empty( $nf_field['desc_text'] ) ? $nf_field['desc_text'] : '', 'size' => 'medium', 'required' => ! empty( $nf_field['required'] ) ? '1' : '', 'label_hide' => '1', 'nf_key' => $nf_field['key'], ]; break; // Multi-check field, radio, select, state, and country fields. case 'listcheckbox': case 'listradio': case 'listselect': case 'liststate': case 'listcountry': $type = 'select'; if ( $nf_field['type'] === 'listcheckbox' ) { $type = 'checkbox'; } elseif ( $nf_field['type'] === 'listradio' ) { $type = 'radio'; } $choices = []; if ( $nf_field['type'] === 'listcountry' ) { $countries = wpforms_countries(); foreach ( $countries as $key => $country ) { $choices[] = [ 'label' => $country, 'value' => $key, 'default' => isset( $nf_field['default'] ) && $nf_field['default'] === $key ? '1' : '', ]; } } else { foreach ( $nf_field['options'] as $option ) { $choices[] = [ 'label' => $option['label'], 'value' => $option['value'], ]; } } $form['fields'][ $field_id ] = [ 'id' => $field_id, 'type' => $type, 'label' => $label, 'choices' => $choices, 'description' => ! empty( $nf_field['desc_text'] ) ? $nf_field['desc_text'] : '', 'size' => 'medium', 'required' => ! empty( $nf_field['required'] ) ? '1' : '', 'nf_key' => $nf_field['key'], ]; break; // HTML field. case 'html': $form['fields'][ $field_id ] = [ 'id' => $field_id, 'type' => 'html', 'code' => ! empty( $nf_field['default'] ) ? $nf_field['default'] : '', 'label_disable' => '1', 'nf_key' => $nf_field['key'], ]; break; // Divider field. case 'hr': $form['fields'][ $field_id ] = [ 'id' => $field_id, 'type' => 'divider', 'label' => '', 'description' => '', 'label_disable' => '1', 'nf_key' => $nf_field['key'], ]; break; // Phone number field. case 'phone': $type = wpforms()->is_pro() ? 'phone' : 'text'; $form['fields'][ $field_id ] = [ 'id' => $field_id, 'type' => $type, 'label' => $label, 'format' => ! empty( $nf_field['mask'] ) && $nf_field['mask'] === '(999) 999-9999' ? 'us' : 'international', 'description' => ! empty( $nf_field['desc_text'] ) ? $nf_field['desc_text'] : '', 'size' => 'medium', 'required' => ! empty( $nf_field['required'] ) ? '1' : '', 'placeholder' => ! empty( $nf_field['placeholder'] ) ? $nf_field['placeholder'] : '', 'default_value' => ! empty( $nf_field['default'] ) ? $nf_field['default'] : '', 'nf_key' => $nf_field['key'], ]; break; // Date field. case 'date': $type = wpforms()->is_pro() ? 'date-time' : 'text'; $form['fields'][ $field_id ] = [ 'id' => $field_id, 'type' => $type, 'label' => $label, 'description' => ! empty( $nf_field['desc_text'] ) ? $nf_field['desc_text'] : '', 'format' => 'date', 'size' => 'medium', 'required' => ! empty( $nf_field['required'] ) ? '1' : '', 'date_placeholder' => '', 'date_format' => 'm/d/Y', 'date_type' => 'datepicker', 'time_format' => 'g:i A', 'time_interval' => 30, 'nf_key' => $nf_field['key'], ]; break; // ReCAPTCHA field. case 'recaptcha': $nf_recaptcha = true; if ( $nf_field['size'] === 'invisible' ) { $nf_recaptcha_type = 'invisible'; } } } // If we are only analyzing the form, we can stop here and return the // details about this form. if ( $analyze ) { wp_send_json_success( [ 'name' => $nf_form_name, 'upgrade_plain' => $upgrade_plain, 'upgrade_omit' => $upgrade_omit, ] ); } // Settings. // Confirmation message. foreach ( $nf_form['actions'] as $action ) { if ( $action['type'] === 'successmessage' ) { $form['settings']['confirmations'][1]['message'] = $this->get_smarttags( $action['message'], $form['fields'] ); } } // ReCAPTCHA. if ( $nf_recaptcha ) { // If the user has already defined v2 reCAPTCHA keys in the WPForms // settings, use those. $site_key = wpforms_setting( 'recaptcha-site-key', '' ); $secret_key = wpforms_setting( 'recaptcha-secret-key', '' ); // Try to abstract keys from NF. if ( empty( $site_key ) || empty( $secret_key ) ) { $nf_settings = get_option( 'ninja_forms_settings' ); if ( ! empty( $nf_settings['recaptcha_site_key'] ) && ! empty( $nf_settings['recaptcha_secret_key'] ) ) { $wpforms_settings = get_option( 'wpforms_settings', [] ); $wpforms_settings['recaptcha-site-key'] = $nf_settings['recaptcha_site_key']; $wpforms_settings['recaptcha-secret-key'] = $nf_settings['recaptcha_secret_key']; $wpforms_settings['recaptcha-type'] = $nf_recaptcha_type; update_option( 'wpforms_settings', $wpforms_settings ); } } if ( ! empty( $site_key ) && ! empty( $secret_key ) ) { $form['settings']['recaptcha'] = '1'; } } // Setup email notifications. $action_count = 1; $action_defaults = [ 'notification_name' => esc_html__( 'Notification', 'wpforms-lite' ) . " $action_count", 'email' => '{admin_email}', 'subject' => sprintf( /* translators: %s - form name. */ esc_html__( 'New Entry: %s', 'wpforms-lite' ), $nf_form_name ), 'sender_name' => get_bloginfo( 'name' ), 'sender_address' => '{admin_email}', 'replyto' => '', 'message' => '{all_fields}', ]; foreach ( $nf_form['actions'] as $action ) { if ( $action['type'] !== 'email' ) { continue; } $action_defaults['notification_name'] = esc_html__( 'Notification', 'wpforms-lite' ) . " $action_count"; $form['settings']['notifications'][ $action_count ] = $action_defaults; if ( ! empty( $action['label'] ) ) { $form['settings']['notifications'][ $action_count ]['notification_name'] = $action['label']; } if ( ! empty( $action['to'] ) ) { $form['settings']['notifications'][ $action_count ]['email'] = $this->get_smarttags( $action['to'], $form['fields'] ); } if ( ! empty( $action['reply_to'] ) ) { $form['settings']['notifications'][ $action_count ]['replyto'] = $this->get_smarttags( $action['reply_to'], $form['fields'] ); } if ( ! empty( $action['email_subject'] ) ) { $form['settings']['notifications'][ $action_count ]['subject'] = $this->get_smarttags( $action['email_subject'], $form['fields'] ); } if ( ! empty( $action['email_message'] ) ) { $form['settings']['notifications'][ $action_count ]['message'] = $this->get_smarttags( $action['email_message'], $form['fields'] ); } if ( ! empty( $action['from_name'] ) ) { $form['settings']['notifications'][ $action_count ]['sender_name'] = $this->get_smarttags( $action['from_name'], $form['fields'] ); } if ( ! empty( $action['from_address'] ) ) { $form['settings']['notifications'][ $action_count ]['sender_address'] = $this->get_smarttags( $action['from_address'], $form['fields'] ); } ++$action_count; } $this->add_form( $form, $unsupported, $upgrade_plain, $upgrade_omit ); } /** * Get the field label. * * @since 1.6.6 * * @param array $field Field data. * * @return string */ public function get_field_label( $field ) { if ( ! empty( $field['label'] ) ) { $label = sanitize_text_field( $field['label'] ); } else { $label = sprintf( /* translators: %s - field type. */ esc_html__( '%s Field', 'wpforms-lite' ), ucfirst( $field['type'] ) ); } return trim( $label ); } /** * Replace 3rd-party form provider tags/shortcodes with our own Smart Tags. * * @since 1.6.6 * * @param string $text Text to look for Smart Tags in. * @param array $fields List of fields to process Smart Tags in. * * @return string */ public function get_smarttags( $text, $fields ) { preg_match_all( '/\{(.+?)\}/', $text, $tags ); if ( empty( $tags[1] ) ) { return $text; } foreach ( $tags[1] as $tag ) { $tag_formatted = str_replace( 'field:', '', $tag ); foreach ( $fields as $field ) { if ( ! empty( $field['nf_key'] ) && $field['nf_key'] === $tag_formatted ) { $text = str_replace( '{' . $tag . '}', '{field_id="' . $field['id'] . '"}', $text ); } } if ( in_array( $tag, [ 'wp:admin_email', 'system:admin_email' ], true ) ) { $text = str_replace( [ '{wp:admin_email}', '{system:admin_email}' ], '{admin_email}', $text ); } if ( $tag === 'all_fields_table' || $tag === 'fields_table' ) { $text = str_replace( '{' . $tag . '}', '{all_fields}', $text ); } } return $text; } } Tools/Importers/ImporterInterface.php 0000644 00000001603 15252506741 0013772 0 ustar 00 <?php namespace WPForms\Admin\Tools\Importers; /** * Interface WPForms_Importer_Interface to handle common methods for all importers. * * @since 1.6.6 */ interface ImporterInterface { /** * Define required properties. * * @since 1.6.6 */ public function init(); /** * Get ALL THE FORMS. * * @since 1.6.6 */ public function get_forms(); /** * Get a single form. * * @since 1.6.6 * * @param int $id Form ID. */ public function get_form( $id ); /** * Import a single form using AJAX. * * @since 1.6.6 */ public function import_form(); /** * Replace 3rd-party form provider tags/shortcodes with our own Smart Tags. * * @since 1.6.6 * * @param string $text Text to look for Smart Tags in. * @param array $fields List of fields to process Smart Tags in. * * @return string */ public function get_smarttags( $text, $fields ); } Tools/Importers/ContactForm7.php 0000644 00000046150 15252506741 0012664 0 ustar 00 <?php // phpcs:ignore Generic.Commenting.DocComment.MissingShort /** @noinspection PhpUndefinedClassInspection */ namespace WPForms\Admin\Tools\Importers; use WPCF7_ContactForm; use WPCF7_FormTag; /** * Contact Form 7 Importer class. * * @since 1.6.6 */ class ContactForm7 extends Base { /** * Define required properties. * * @since 1.6.6 */ public function init() { $this->name = 'Contact Form 7'; $this->slug = 'contact-form-7'; $this->path = 'contact-form-7/wp-contact-form-7.php'; } /** * Get ALL THE FORMS. * * @since 1.6.6 */ public function get_forms() { $forms_final = []; if ( ! $this->is_active() ) { return $forms_final; } // phpcs:ignore WordPress.WP.Capabilities.Unknown if ( ! current_user_can( 'wpcf7_read_contact_forms' ) ) { return $forms_final; } $forms = WPCF7_ContactForm::find( [ 'posts_per_page' => -1 ] ); if ( empty( $forms ) ) { return $forms_final; } foreach ( $forms as $form ) { if ( ! empty( $form ) && ( $form instanceof WPCF7_ContactForm ) ) { $forms_final[ $form->id() ] = $form->title(); } } return $forms_final; } /** * Get a single form. * * @since 1.6.6 * * @param int $id Form ID. * * @return WPCF7_ContactForm|bool */ public function get_form( $id ) { $form = WPCF7_ContactForm::find( [ 'posts_per_page' => 1, 'p' => $id, ] ); if ( ! empty( $form[0] ) && ( $form[0] instanceof WPCF7_ContactForm ) ) { return $form[0]; } return false; } /** * Import a single form using AJAX. * * @since 1.6.6 */ public function import_form() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded, Generic.Metrics.NestingLevel.MaxExceeded // Run a security check. check_ajax_referer( 'wpforms-admin', 'nonce' ); // Check for permissions. if ( ! wpforms_current_user_can( 'create_forms' ) ) { wp_send_json_error(); } // Define some basic information. $analyze = isset( $_POST['analyze'] ); $cf7_id = ! empty( $_POST['form_id'] ) ? (int) $_POST['form_id'] : 0; $cf7_form = $this->get_form( $cf7_id ); if ( ! $cf7_form ) { wp_send_json_error( [ 'error' => true, 'name' => esc_html__( 'Unknown Form', 'wpforms-lite' ), 'msg' => esc_html__( 'The form you are trying to import does not exist.', 'wpforms-lite' ), ] ); } $cf7_form_name = $cf7_form->title(); $cf7_fields = $cf7_form->scan_form_tags(); $cf7_properties = $cf7_form->get_properties(); $cf7_recaptcha = false; $fields_pro_plain = [ 'url', 'tel', 'date' ]; $fields_pro_omit = [ 'file' ]; $fields_unsupported = [ 'quiz', 'hidden' ]; $upgrade_plain = []; $upgrade_omit = []; $unsupported = []; $form = [ 'id' => '', 'field_id' => '', 'fields' => [], 'settings' => [ 'form_title' => $cf7_form_name, 'form_desc' => '', 'submit_text' => esc_html__( 'Submit', 'wpforms-lite' ), 'submit_text_processing' => esc_html__( 'Sending', 'wpforms-lite' ), 'antispam_v3' => '1', 'notification_enable' => '1', 'notifications' => [ 1 => [ 'notification_name' => esc_html__( 'Notification 1', 'wpforms-lite' ), 'email' => '{admin_email}', 'subject' => sprintf( /* translators: %s - form name. */ esc_html__( 'New Entry: %s', 'wpforms-lite' ), $cf7_form_name ), 'sender_name' => get_bloginfo( 'name' ), 'sender_address' => '{admin_email}', 'replyto' => '', 'message' => '{all_fields}', ], ], 'confirmations' => [ 1 => [ 'type' => 'message', 'message' => esc_html__( 'Thanks for contacting us! We will be in touch with you shortly.', 'wpforms-lite' ), 'message_scroll' => '1', ], ], 'import_form_id' => $cf7_id, ], ]; // If the form does not contain fields, bail. if ( empty( $cf7_fields ) ) { wp_send_json_success( [ 'error' => true, 'name' => sanitize_text_field( $cf7_form_name ), 'msg' => esc_html__( 'No form fields found.', 'wpforms-lite' ), ] ); } // Convert fields. foreach ( $cf7_fields as $cf7_field ) { if ( ! $cf7_field instanceof WPCF7_FormTag ) { continue; } // Try to determine field label to use. $label = $this->get_field_label( $cf7_properties['form'], $cf7_field->type, $cf7_field->name ); // Next, check if the field is unsupported. // If supported, make note and then continue to the next field. if ( in_array( $cf7_field->basetype, $fields_unsupported, true ) ) { $unsupported[] = $label; continue; } // Now check if this installation is Lite. // If it is Lite, and it's a field type not included, make a note then continue to the next field. if ( in_array( $cf7_field->basetype, $fields_pro_plain, true ) && ! wpforms()->is_pro() ) { $upgrade_plain[] = $label; } if ( in_array( $cf7_field->basetype, $fields_pro_omit, true ) && ! wpforms()->is_pro() ) { $upgrade_omit[] = $label; continue; } // Determine the next field ID to assign. if ( empty( $form['fields'] ) ) { $field_id = 1; } else { $field_id = (int) max( array_keys( $form['fields'] ) ) + 1; } switch ( $cf7_field->basetype ) { // Plain text, email, URL, number, and textarea fields. case 'text': case 'email': case 'url': case 'number': case 'textarea': $type = $cf7_field->basetype; if ( $type === 'url' && ! wpforms()->is_pro() ) { $type = 'text'; } $form['fields'][ $field_id ] = [ 'id' => $field_id, 'type' => $type, 'label' => $label, 'size' => 'medium', 'required' => $cf7_field->is_required() ? '1' : '', 'placeholder' => $this->get_field_placeholder_default( $cf7_field ), 'default_value' => $this->get_field_placeholder_default( $cf7_field, 'default' ), 'cf7_name' => $cf7_field->name, ]; break; // Phone number field. case 'tel': $form['fields'][ $field_id ] = [ 'id' => $field_id, 'type' => 'phone', 'label' => $label, 'format' => 'international', 'size' => 'medium', 'required' => $cf7_field->is_required() ? '1' : '', 'placeholder' => $this->get_field_placeholder_default( $cf7_field ), 'default_value' => $this->get_field_placeholder_default( $cf7_field, 'default' ), 'cf7_name' => $cf7_field->name, ]; break; // Date field. case 'date': $type = wpforms()->is_pro() ? 'date-time' : 'text'; $form['fields'][ $field_id ] = [ 'id' => $field_id, 'type' => $type, 'label' => $label, 'format' => 'date', 'size' => 'medium', 'required' => $cf7_field->is_required() ? '1' : '', 'date_placeholder' => '', 'date_format' => 'm/d/Y', 'date_type' => 'datepicker', 'time_format' => 'g:i A', 'time_interval' => 30, 'cf7_name' => $cf7_field->name, ]; break; // Select, radio, and checkbox fields. case 'select': case 'radio': case 'checkbox': $choices = []; $options = (array) $cf7_field->labels; foreach ( $options as $option ) { $choices[] = [ 'label' => $option, 'value' => '', ]; } $form['fields'][ $field_id ] = [ 'id' => $field_id, 'type' => $cf7_field->basetype, 'label' => $label, 'choices' => $choices, 'size' => 'medium', 'required' => $cf7_field->is_required() ? '1' : '', 'cf7_name' => $cf7_field->name, ]; if ( $cf7_field->basetype === 'select' && $cf7_field->has_option( 'include_blank' ) ) { $form['fields'][ $field_id ]['placeholder'] = '---'; } break; // File upload field. case 'file': $extensions = ''; $max_size = ''; $file_types = $cf7_field->get_option( 'filetypes' ); $limit = $cf7_field->get_option( 'limit' ); if ( ! empty( $file_types[0] ) ) { $extensions = implode( ',', explode( '|', strtolower( preg_replace( '/[^A-Za-z0-9|]/', '', strtolower( $file_types[0] ) ) ) ) ); } if ( ! empty( $limit[0] ) ) { $limit = $limit[0]; $mb = ( strpos( $limit, 'm' ) !== false ); $kb = ( strpos( $limit, 'kb' ) !== false ); $limit = (int) preg_replace( '/[^0-9]/', '', $limit ); if ( $mb ) { $max_size = $limit; } elseif ( $kb ) { $max_size = round( $limit / 1024, 1 ); } else { $max_size = round( $limit / 1048576, 1 ); } } $form['fields'][ $field_id ] = [ 'id' => $field_id, 'type' => 'file-upload', 'label' => $label, 'size' => 'medium', 'extensions' => $extensions, 'max_size' => $max_size, 'required' => $cf7_field->is_required() ? '1' : '', 'cf7_name' => $cf7_field->name, ]; break; // Acceptance field. case 'acceptance': $form['fields'][ $field_id ] = [ 'id' => $field_id, 'type' => 'checkbox', 'label' => esc_html__( 'Acceptance Field', 'wpforms-lite' ), 'choices' => [ 1 => [ 'label' => $label, 'value' => '', ], ], 'size' => 'medium', 'required' => '1', 'label_hide' => '1', 'cf7_name' => $cf7_field->name, ]; break; // ReCAPTCHA field. case 'recaptcha': $cf7_recaptcha = true; } } // If we are only analyzing the form, we can stop here and return the // details about this form. if ( $analyze ) { wp_send_json_success( [ 'name' => $cf7_form_name, 'upgrade_plain' => $upgrade_plain, 'upgrade_omit' => $upgrade_omit, ] ); } // Settings. // Confirmation message. if ( ! empty( $cf7_properties['messages']['mail_sent_ok'] ) ) { $form['settings']['confirmation_message'] = $cf7_properties['messages']['mail_sent_ok']; } // ReCAPTCHA. if ( $cf7_recaptcha ) { // If the user has already defined v2 reCAPTCHA keys in the WPForms // settings, use those. $site_key = wpforms_setting( 'recaptcha-site-key', '' ); $secret_key = wpforms_setting( 'recaptcha-secret-key', '' ); $type = wpforms_setting( 'recaptcha-type', 'v2' ); // Try to abstract keys from CF7. if ( empty( $site_key ) || empty( $secret_key ) ) { $cf7_settings = get_option( 'wpcf7' ); if ( ! empty( $cf7_settings['recaptcha'] ) && is_array( $cf7_settings['recaptcha'] ) ) { foreach ( $cf7_settings['recaptcha'] as $key => $val ) { if ( ! empty( $key ) && ! empty( $val ) ) { $site_key = $key; $secret_key = $val; } } $wpforms_settings = get_option( 'wpforms_settings', [] ); $wpforms_settings['recaptcha-site-key'] = $site_key; $wpforms_settings['recaptcha-secret-key'] = $secret_key; update_option( 'wpforms_settings', $wpforms_settings ); } } // Don't enable reCAPTCHA if user had configured invisible reCAPTCHA. if ( $type === 'v2' && ! empty( $site_key ) && ! empty( $secret_key ) ) { $form['settings']['recaptcha'] = '1'; } } // Setup email notifications. if ( ! empty( $cf7_properties['mail']['subject'] ) ) { $form['settings']['notifications'][1]['subject'] = $this->get_smarttags( $cf7_properties['mail']['subject'], $form['fields'] ); } if ( ! empty( $cf7_properties['mail']['recipient'] ) ) { $form['settings']['notifications'][1]['email'] = $this->get_smarttags( $cf7_properties['mail']['recipient'], $form['fields'] ); } if ( ! empty( $cf7_properties['mail']['body'] ) ) { $form['settings']['notifications'][1]['message'] = $this->get_smarttags( $cf7_properties['mail']['body'], $form['fields'] ); } if ( ! empty( $cf7_properties['mail']['additional_headers'] ) ) { $form['settings']['notifications'][1]['replyto'] = $this->get_replyto( $cf7_properties['mail']['additional_headers'], $form['fields'] ); } if ( ! empty( $cf7_properties['mail']['sender'] ) ) { $sender = $this->get_sender_details( $cf7_properties['mail']['sender'], $form['fields'] ); if ( $sender ) { $form['settings']['notifications'][1]['sender_name'] = $sender['name']; $form['settings']['notifications'][1]['sender_address'] = $sender['address']; } } if ( ! empty( $cf7_properties['mail_2'] ) && (int) $cf7_properties['mail_2']['active'] === 1 ) { // Check if a secondary notification is enabled, if so set defaults // and set it up. $form['settings']['notifications'][2] = [ 'notification_name' => esc_html__( 'Notification 2', 'wpforms-lite' ), 'email' => '{admin_email}', 'subject' => sprintf( /* translators: %s - form name. */ esc_html__( 'New Entry: %s', 'wpforms-lite' ), $cf7_form_name ), 'sender_name' => get_bloginfo( 'name' ), 'sender_address' => '{admin_email}', 'replyto' => '', 'message' => '{all_fields}', ]; if ( ! empty( $cf7_properties['mail_2']['subject'] ) ) { $form['settings']['notifications'][2]['subject'] = $this->get_smarttags( $cf7_properties['mail_2']['subject'], $form['fields'] ); } if ( ! empty( $cf7_properties['mail_2']['recipient'] ) ) { $form['settings']['notifications'][2]['email'] = $this->get_smarttags( $cf7_properties['mail_2']['recipient'], $form['fields'] ); } if ( ! empty( $cf7_properties['mail_2']['body'] ) ) { $form['settings']['notifications'][2]['message'] = $this->get_smarttags( $cf7_properties['mail_2']['body'], $form['fields'] ); } if ( ! empty( $cf7_properties['mail_2']['additional_headers'] ) ) { $form['settings']['notifications'][2]['replyto'] = $this->get_replyto( $cf7_properties['mail_2']['additional_headers'], $form['fields'] ); } if ( ! empty( $cf7_properties['mail_2']['sender'] ) ) { $sender = $this->get_sender_details( $cf7_properties['mail_2']['sender'], $form['fields'] ); if ( $sender ) { $form['settings']['notifications'][2]['sender_name'] = $sender['name']; $form['settings']['notifications'][2]['sender_address'] = $sender['address']; } } } $this->add_form( $form, $unsupported, $upgrade_plain, $upgrade_omit ); } /** * Lookup and return the placeholder or default value. * * @since 1.6.6 * * @param object $field Field object. * @param string $type Type of the field. * * @return string */ public function get_field_placeholder_default( $field, $type = 'placeholder' ) { $placeholder = ''; $default_value = (string) reset( $field->values ); if ( $field->has_option( 'placeholder' ) || $field->has_option( 'watermark' ) ) { $placeholder = $default_value; $default_value = ''; } if ( $type === 'placeholder' ) { return $placeholder; } return $default_value; } /** * Get the field label. * * @since 1.6.6 * * @param string $form Form data and settings. * @param string $type Field type. * @param string $name Field name. * * @return string */ public function get_field_label( $form, $type, $name = '' ) { preg_match_all( '/<label>([ \w\S\r\n\t]+?)<\/label>/', $form, $matches ); foreach ( $matches[1] as $match ) { $match = trim( str_replace( "\n", '', $match ) ); preg_match( '/\[(?:' . preg_quote( $type, '/' ) . ') ' . $name . '(?:[ ](.*?))?(?:[\r\n\t ](\/))?\]/', $match, $input_match ); if ( ! empty( $input_match[0] ) ) { return strip_shortcodes( sanitize_text_field( str_replace( $input_match[0], '', $match ) ) ); } } $label = sprintf( /* translators: %1$s - field type, %2$s - field name if available. */ esc_html__( '%1$s Field %2$s', 'wpforms-lite' ), ucfirst( $type ), ! empty( $name ) ? "($name)" : '' ); return trim( $label ); } /** * Replace 3rd-party form provider tags/shortcodes with our own Smart Tags. * * @since 1.6.6 * * @param string $text Text to look for Smart Tags in. * @param array $fields List of fields to process Smart Tags in. * * @return string */ public function get_smarttags( $text, $fields ) { preg_match_all( '/\[(.+?)\]/', $text, $tags ); if ( empty( $tags[1] ) ) { return $text; } // Process form-tags and mail-tags. foreach ( $tags[1] as $tag ) { foreach ( $fields as $field ) { if ( ! empty( $field['cf7_name'] ) && $field['cf7_name'] === $tag ) { $text = str_replace( '[' . $tag . ']', '{field_id="' . $field['id'] . '"}', $text ); } } } /* * Process CF7 tags that we can map with WPForms alternatives. * Replace those CF7 that are used in Notifications by default and that we can't leave empty. * We are not replacing certain special CF7 tags: [_user_url], [_post_name], [_time], [_user_agent]. * Without them some logic may be broken, and for user it will be harder to stop missing strings. * With them - they can see strange text and will be able to understand, based on the tag name, * which value is expected there. */ return str_replace( [ '[_site_title]', '[_site_description]', '[_site_url]', '[_user_display_name]', '[_user_nickname]', '[_user_last_name]', '[_user_first_name]', '[_user_email]', '[_user_login]', '[_site_admin_email]', '[_post_author_email]', '[_post_author]', '[_url]', '[_post_url]', '[_post_title]', '[_post_id]', '[_serial_number]', '[_date]', '[_remote_ip]', ], [ get_bloginfo( 'name' ), get_bloginfo( 'description' ), get_bloginfo( 'url' ), '{user_full_name}', '{user_display}', '{user_last_name}', '{user_first_name}', '{user_email}', '{user_display}', '{admin_email}', '{author_email}', '{author_display}', '{page_url}', '{page_url}', '{page_title}', '{page_id}', '{entry_id}', '{date format="m/d/Y"}', '{user_ip}', ], $text ); } /** * Find Reply-To in headers if provided. * * @since 1.6.6 * * @param string $headers CF7 email headers. * @param array $fields List of fields. * * @return string */ public function get_replyto( $headers, $fields ) { // phpcs:ignore Generic.Metrics.NestingLevel.MaxExceeded if ( strpos( $headers, 'Reply-To:' ) !== false ) { preg_match( '/Reply-To: \[(.+?)\]/', $headers, $tag ); if ( ! empty( $tag[1] ) ) { foreach ( $fields as $field ) { if ( ! empty( $field['cf7_name'] ) && $field['cf7_name'] === $tag[1] ) { return '{field_id="' . $field['id'] . '"}'; } } } } return ''; } /** * Sender information. * * @since 1.6.6 * * @param string $sender Sender strings in "Name <email@example.com>" format. * @param array $fields List of fields. * * @return bool|array */ public function get_sender_details( $sender, $fields ) { preg_match( '/(.+?)\<(.+?)\>/', $sender, $tag ); if ( ! empty( $tag[1] ) && ! empty( $tag[2] ) ) { return [ 'name' => $this->get_smarttags( $tag[1], $fields ), 'address' => $this->get_smarttags( $tag[2], $fields ), ]; } return false; } } Tools/Importers/Base.php 0000644 00000006724 15252506741 0011233 0 ustar 00 <?php namespace WPForms\Admin\Tools\Importers; /** * Base Importer class. * * @since 1.6.6 */ abstract class Base implements ImporterInterface { /** * Importer name. * * @since 1.6.6 * * @var string */ public $name; /** * Importer name in slug format. * * @since 1.6.6 * * @var string */ public $slug; /** * Importer plugin path. * * @since 1.6.6 * * @var string */ public $path; /** * Primary class constructor. * * @since 1.6.6 */ public function __construct() { $this->init(); // Import a specific form with AJAX. add_action( "wp_ajax_wpforms_import_form_{$this->slug}", [ $this, 'import_form' ] ); } /** * Add to list of registered importers. * * @since 1.6.6 * * @param array $importers List of supported importers. * * @return array */ public function register( $importers = [] ) { $importers[ $this->slug ] = [ 'name' => $this->name, 'slug' => $this->slug, 'path' => $this->path, 'installed' => file_exists( trailingslashit( WP_PLUGIN_DIR ) . $this->path ), 'active' => $this->is_active(), ]; return $importers; } /** * If the importer source is available. * * @since 1.6.6 * * @return bool */ protected function is_active() { return is_plugin_active( $this->path ); } /** * Add the new form to the database and return AJAX data. * * @since 1.6.6 * * @param array $form Form to import. * @param array $unsupported List of unsupported fields. * @param array $upgrade_plain List of fields, that are supported inside the paid WPForms, but not in Lite. * @param array $upgrade_omit No field alternative in WPForms. */ public function add_form( $form, $unsupported = [], $upgrade_plain = [], $upgrade_omit = [] ) { // Create empty form so we have an ID to work with. $form_id = wp_insert_post( [ 'post_status' => 'publish', 'post_type' => 'wpforms', ] ); if ( empty( $form_id ) || is_wp_error( $form_id ) ) { wp_send_json_success( [ 'error' => true, 'name' => sanitize_text_field( $form['settings']['form_title'] ), 'msg' => esc_html__( 'There was an error while creating a new form.', 'wpforms-lite' ), ] ); } $form['id'] = $form_id; $form['field_id'] = count( $form['fields'] ) + 1; // Update the form with all our compiled data. wpforms()->obj( 'form' )->update( $form_id, $form ); // Make note that this form has been imported. $this->track_import( $form['settings']['import_form_id'], $form_id ); // Build and send final AJAX response! wp_send_json_success( [ 'name' => $form['settings']['form_title'], 'edit' => esc_url_raw( admin_url( 'admin.php?page=wpforms-builder&view=fields&form_id=' . $form_id ) ), 'preview' => wpforms_get_form_preview_url( $form_id ), 'unsupported' => $unsupported, 'upgrade_plain' => $upgrade_plain, 'upgrade_omit' => $upgrade_omit, ] ); } /** * After a form has been successfully imported we track it, so that in the * future we can alert users if they try to import a form that has already * been imported. * * @since 1.6.6 * * @param int $source_id Imported plugin form ID. * @param int $wpforms_id WPForms form ID. */ public function track_import( $source_id, $wpforms_id ) { $imported = get_option( 'wpforms_imported', [] ); $imported[ $this->slug ][ $wpforms_id ] = $source_id; update_option( 'wpforms_imported', $imported, false ); } } Tools/Export/Views/ExportViewsInterface.php 0000644 00000001126 15252506741 0015062 0 ustar 00 <?php namespace WPForms\Admin\Tools\Export\Views; /** * Export Views Interface. * * @since 1.10.1 */ interface ExportViewsInterface { /** * Initialize class. * * @since 1.10.1 */ public function init(): void; /** * Check if the current user has the capability to view the page. * * @since 1.10.1 * * @return bool */ public function current_user_can(): bool; /** * Page content. * * @since 1.10.1 */ public function display(): void; /** * Get the Tab label. * * @since 1.10.1 * * @return string */ public function get_tab_label(): string; } Tools/Export/Views/Templates/Page.php 0000644 00000016746 15252506741 0013572 0 ustar 00 <?php namespace WPForms\Admin\Tools\Export\Views\Templates; use WPForms\Admin\Tools\Export\Views\ExportViewsInterface; use WPForms\Admin\Tools\Tools; use WPForms\Helpers\Form; /** * Export Templates Page class. * * Handles the Export Templates tab functionality. * * @since 1.10.1 */ class Page implements ExportViewsInterface { /** * View slug. * * @since 1.10.1 * * @var string */ protected $slug = 'templates'; /** * Available forms. * * @since 1.10.1 * * @var array */ private $forms = []; /** * Template code if generated. * * @since 1.10.1 * * @var string */ private $template = ''; /** * Initialize class. * * @since 1.10.1 */ public function init(): void { $this->hooks(); } /** * Register hooks. * * @since 1.10.1 */ private function hooks(): void { add_action( 'wpforms_tools_init', [ $this, 'process' ] ); } /** * Get the Tab label. * * @since 1.10.1 * * @return string */ public function get_tab_label(): string { return __( 'Export Templates', 'wpforms-lite' ); } /** * Check if the current user has the capability to view the page. * * @since 1.10.1 * * @return bool */ public function current_user_can(): bool { return wpforms_current_user_can( 'edit_forms' ); } /** * Export template process. * * @since 1.10.1 */ public function process(): void { // phpcs:disable WordPress.Security.NonceVerification if ( empty( $_POST['action'] ) || $_POST['action'] !== 'export_template' || ! isset( $_POST['submit-export'] ) || ! $this->verify_nonce() ) { return; } if ( empty( $_POST['form'] ) ) { return; } // phpcs:enable WordPress.Security.NonceVerification $this->process_template(); } /** * Page content. * * @since 1.10.1 */ public function display(): void { $this->forms = Form::get_all(); if ( empty( $this->forms ) ) { echo wpforms_render( 'admin/empty-states/no-forms' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped return; } ?> <div class="wpforms-setting-row tools"> <h4 id="template-export"><?php esc_html_e( 'Export a Form Template', 'wpforms-lite' ); ?></h4> <?php if ( $this->template ) { $this->display_template_result(); } ?> <p><?php esc_html_e( 'Select a form to generate PHP code that can be used to register a custom form template.', 'wpforms-lite' ); ?></p> <form method="post" action="<?php echo esc_attr( $this->get_link() ); ?>"> <?php $this->forms_select_html(); ?> <input type="hidden" name="action" value="export_template"> <?php wp_nonce_field( 'wpforms_export_template_nonce', 'wpforms-tools-export-template-nonce' ); ?> <button name="submit-export" class="wpforms-btn wpforms-btn-md wpforms-btn-orange" id="wpforms-export-template" aria-disabled="true"> <?php esc_html_e( 'Export Template', 'wpforms-lite' ); ?> </button> </form> </div> <?php } /** * Display template result. * * @since 1.10.1 * * @noinspection HtmlUnknownTarget */ private function display_template_result(): void { $doc_link = sprintf( wp_kses( /* translators: %s - WPForms.com docs URL. */ __( 'For more information <a href="%s" target="_blank" rel="noopener noreferrer">see our documentation</a>.', 'wpforms-lite' ), [ 'a' => [ 'href' => [], 'target' => [], 'rel' => [], ], ] ), 'https://wpforms.com/docs/how-to-create-a-custom-form-template/' ); ?> <p><?php esc_html_e( 'The following code can be used to register your custom form template. Copy and paste the following code to your theme\'s functions.php file or include it within an external file.', 'wpforms-lite' ); ?></p> <p><?php echo $doc_link; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></p> <textarea class="info-area" readonly><?php echo esc_textarea( $this->template ); ?></textarea> <?php } /** * Get a link to the view page. * * @since 1.10.1 * * @return string */ private function get_link(): string { return add_query_arg( [ 'page' => Tools::SLUG, 'view' => 'export', 'tab' => $this->slug, ], admin_url( 'admin.php' ) ); } /** * Forms selector HTML. * * @since 1.10.1 */ private function forms_select_html(): void { ?> <span class="choicesjs-select-wrap"> <select id="wpforms-tools-form-template" class="choicesjs-select" name="form" data-search="<?php echo esc_attr( wpforms_choices_js_is_search_enabled( $this->forms ) ); ?>"> <option value=""><?php esc_attr_e( 'Select a Template', 'wpforms-lite' ); ?></option> <?php foreach ( $this->forms as $form ) : ?> <option value="<?php echo absint( $form->ID ); ?>"><?php echo esc_html( $form->post_title ); ?></option> <?php endforeach; ?> </select> </span> <?php } /** * Verify nonce field. * * @since 1.10.1 * * @return bool */ private function verify_nonce(): bool { $nonce = isset( $_POST['wpforms-tools-export-template-nonce'] ) ? sanitize_key( $_POST['wpforms-tools-export-template-nonce'] ) : ''; return (bool) wp_verify_nonce( $nonce, 'wpforms_export_template_nonce' ); } /** * Export template processing. * * @since 1.10.1 */ private function process_template(): void { // Nonce is checked in the caller: process() method. // phpcs:ignore WordPress.Security.NonceVerification.Missing $form_id = isset( $_POST['form'] ) ? absint( $_POST['form'] ) : 0; $form_obj = wpforms()->obj( 'form' ); if ( ! $form_obj || ! $form_id ) { return; } $form_data = $form_obj->get( $form_id, [ 'content_only' => true ] ); // Define basic data with strict validation. $name = sanitize_text_field( $form_data['settings']['form_title'] ?? '' ); $desc = sanitize_text_field( $form_data['settings']['form_desc'] ?? '' ); $slug = sanitize_key( str_replace( [ ' ', '-' ], '_', trim( $name ) ) ); if ( ! $slug ) { // Slug is always empty when the $form_data is not valid. return; } $class = 'WPForms_Template_' . $slug; $data = $this->get_template_data( $slug, $form_data ); // Build the final template string. $this->template = <<<EOT if ( class_exists( 'WPForms_Template', false ) ) : /** * {$name} * Template for WPForms. */ class $class extends WPForms_Template { /** * Primary class constructor. * * @since 1.0.0 */ public function init() { // Template name \$this->name = '$name'; // Template slug \$this->slug = '$slug'; // Template description \$this->description = '$desc'; // Template field and settings \$this->data = $data; } } new $class(); endif; EOT; } /** * Get template data. * * @since 1.10.1 * * @param string $slug Template slug. * @param array|mixed $form_data Form data. * * @return string */ private function get_template_data( string $slug, $form_data ): string { // Format template field and settings data. $data = []; $data['meta']['template'] = $slug; $data['fields'] = isset( $form_data['fields'] ) && is_array( $form_data['fields'] ) ? wpforms_array_remove_empty_strings( $form_data['fields'] ) : []; $data['settings'] = isset( $form_data['settings'] ) && is_array( $form_data['settings'] ) ? wpforms_array_remove_empty_strings( $form_data['settings'] ) : []; $template_data = (string) var_export( $data, true ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export $template_data = str_replace( ' ', "\t", $template_data ); return preg_replace( '/([\t\r\n]+?)array/', 'array', $template_data ); } } Tools/Export/Views/Forms/Page.php 0000644 00000011225 15252506741 0012705 0 ustar 00 <?php namespace WPForms\Admin\Tools\Export\Views\Forms; use WPForms\Admin\Tools\Export\Views\ExportViewsInterface; use WPForms\Admin\Tools\Tools; use WPForms\Helpers\Form; /** * Export Forms Page class. * * Handles the Export Forms tab functionality. * * @since 1.10.1 */ class Page implements ExportViewsInterface { /** * View slug. * * @since 1.10.1 * * @var string */ protected $slug = 'forms'; /** * Available forms. * * @since 1.10.1 * * @var array */ private $forms = []; /** * Initialize class. * * @since 1.10.1 */ public function init(): void { $this->hooks(); } /** * Register hooks. * * @since 1.10.1 */ private function hooks(): void { add_action( 'wpforms_tools_init', [ $this, 'process' ] ); } /** * Get the Tab label. * * @since 1.10.1 * * @return string */ public function get_tab_label(): string { return __( 'Export Forms', 'wpforms-lite' ); } /** * Check if the current user has the capability to view the page. * * @since 1.10.1 * * @return bool */ public function current_user_can(): bool { return wpforms_current_user_can( 'edit_forms' ); } /** * Export process. * * @since 1.10.1 */ public function process(): void { // phpcs:disable WordPress.Security.NonceVerification if ( empty( $_POST['action'] ) || $_POST['action'] !== 'export_form' || ! isset( $_POST['submit-export'] ) || ! $this->verify_nonce() ) { return; } if ( empty( $_POST['forms'] ) ) { return; } // phpcs:enable WordPress.Security.NonceVerification $this->process_export(); } /** * Page content. * * @since 1.10.1 */ public function display(): void { $this->forms = Form::get_all(); if ( empty( $this->forms ) ) { echo wpforms_render( 'admin/empty-states/no-forms' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped return; } ?> <div class="wpforms-setting-row tools"> <h4 id="form-export"><?php esc_html_e( 'Export Forms', 'wpforms-lite' ); ?></h4> <p><?php esc_html_e( 'Use form export files to create a backup of your forms or to import forms to another site.', 'wpforms-lite' ); ?></p> <form method="post" action="<?php echo esc_attr( $this->get_link() ); ?>"> <?php $this->forms_select_html(); ?> <input type="hidden" name="action" value="export_form"> <?php wp_nonce_field( 'wpforms_export_forms_nonce', 'wpforms-tools-export-forms-nonce' ); ?> <button name="submit-export" class="wpforms-btn wpforms-btn-md wpforms-btn-orange" id="wpforms-export-form" aria-disabled="true"> <?php esc_html_e( 'Export Forms', 'wpforms-lite' ); ?> </button> </form> </div> <?php } /** * Get a link to the view page. * * @since 1.10.1 * * @return string */ private function get_link(): string { return add_query_arg( [ 'page' => Tools::SLUG, 'view' => 'export', 'tab' => $this->slug, ], admin_url( 'admin.php' ) ); } /** * Forms selector HTML. * * @since 1.10.1 */ private function forms_select_html(): void { ?> <span class="choicesjs-select-wrap"> <select id="wpforms-tools-form-export" class="choicesjs-select" name="forms[]" multiple size="1" data-search="<?php echo esc_attr( wpforms_choices_js_is_search_enabled( $this->forms ) ); ?>"> <option value=""><?php esc_attr_e( 'Select Form(s)', 'wpforms-lite' ); ?></option> <?php foreach ( $this->forms as $form ) : ?> <option value="<?php echo absint( $form->ID ); ?>"><?php echo esc_html( $form->post_title ); ?></option> <?php endforeach; ?> </select> </span> <?php } /** * Verify nonce field. * * @since 1.10.1 * * @return bool */ private function verify_nonce(): bool { $nonce = isset( $_POST['wpforms-tools-export-forms-nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['wpforms-tools-export-forms-nonce'] ) ) : ''; return (bool) wp_verify_nonce( $nonce, 'wpforms_export_forms_nonce' ); } /** * Export processing. * * @since 1.10.1 */ private function process_export(): void { $export = []; $forms = get_posts( [ 'post_type' => 'wpforms', 'nopaging' => true, 'post__in' => isset( $_POST['forms'] ) ? array_map( 'intval', $_POST['forms'] ) : [], // phpcs:ignore WordPress.Security.NonceVerification ] ); foreach ( $forms as $form ) { $export[] = wpforms_decode( $form->post_content ); } ignore_user_abort( true ); wpforms_set_time_limit(); nocache_headers(); header( 'Content-Type: application/json; charset=utf-8' ); header( 'Content-Disposition: attachment; filename=wpforms-form-export-' . current_time( 'm-d-Y' ) . '.json' ); header( 'Expires: 0' ); echo wp_json_encode( $export ); exit; } } Tools/Importers.php 0000644 00000002477 15252506741 0010362 0 ustar 00 <?php namespace WPForms\Admin\Tools; use WPForms\Admin\Tools\Importers\ContactForm7; use WPForms\Admin\Tools\Importers\NinjaForms; use WPForms\Admin\Tools\Importers\PirateForms; /** * Load the different form importers. * * @since 1.6.6 */ class Importers { /** * Available importers. * * @since 1.6.6 * * @var array */ private $importers = []; /** * Load default form importers. * * @since 1.6.6 */ public function load() { if ( empty( $this->importers ) ) { $this->importers = [ 'contact-form-7' => new ContactForm7(), 'ninja-forms' => new NinjaForms(), 'pirate-forms' => new PirateForms(), ]; } } /** * Load default form importers. * * @since 1.6.6 * * @return array */ public function get_importers() { $this->load(); $importers = []; foreach ( $this->importers as $importer ) { $importers = $importer->register( $importers ); } return apply_filters( 'wpforms_importers', $importers ); } /** * Get a importer forms. * * @since 1.6.6 * * @param string $provider Provider. * * @return array */ public function get_importer_forms( $provider ) { if ( isset( $this->importers[ $provider ] ) ) { return apply_filters( "wpforms_importer_forms_{$provider}", $this->importers[ $provider ]->get_forms() ); } return []; } } Tools/Tools.php 0000644 00000007474 15252506741 0007500 0 ustar 00 <?php namespace WPForms\Admin\Tools; /** * Main Tools class. * * @since 1.6.6 */ class Tools { /** * Tools page slug. * * @since 1.6.6 */ const SLUG = 'wpforms-tools'; /** * Available pages. * * @since 1.6.6 * * @var array */ private $views = []; /** * The current view. * * @since 1.6.6 * * @var null|\WPForms\Admin\Tools\Views\View */ private $view; /** * The active view slug. * * @since 1.6.6 * * @var string */ private $active_view_slug; /** * Initialize class. * * @since 1.6.6 */ public function init() { if ( ! $this->is_tools_page() ) { return; } $this->init_view(); $this->hooks(); } /** * Check if we're on tools page. * * @since 1.6.6 * * @return bool */ private function is_tools_page() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $page = isset( $_GET['page'] ) ? sanitize_key( $_GET['page'] ) : ''; // Only load if we are actually on the settings page. return $page === self::SLUG; } /** * Init current view. * * @since 1.6.6 */ private function init_view() { $view_ids = array_keys( $this->get_views() ); // Determine the current active settings tab. // phpcs:ignore WordPress.Security.NonceVerification.Recommended $this->active_view_slug = ! empty( $_GET['view'] ) ? sanitize_key( $_GET['view'] ) : 'import'; // If the user tries to load an invalid view - fallback to the first available. if ( ! in_array( $this->active_view_slug, $view_ids, true ) && ! has_action( 'wpforms_tools_display_tab_' . $this->active_view_slug ) ) { $this->active_view_slug = reset( $view_ids ); } if ( isset( $this->views[ $this->active_view_slug ] ) ) { $this->view = $this->views[ $this->active_view_slug ]; $this->view->init(); } } /** * Get Views. * * @since 1.6.6 * * @return array */ public function get_views() { if ( empty( $this->views ) ) { $this->views = [ 'import' => new Views\Import(), 'importer' => new Views\Importer(), 'export' => new Views\Export(), 'entry-automation' => new Views\EntryAutomation(), 'ai-mcp' => new Views\AiMcp(), 'system' => new Views\System(), 'action-scheduler' => new Views\ActionScheduler(), 'logs' => new Views\Logs(), 'wpcode' => new Views\CodeSnippets(), ]; } $this->views = apply_filters( 'wpforms_tools_views', $this->views ); return array_filter( $this->views, static function ( $view ) { return $view->check_capability(); } ); } /** * Register hooks. * * @since 1.6.6 */ public function hooks() { add_action( 'wpforms_admin_page', [ $this, 'output' ] ); // Hook for addons. do_action( 'wpforms_tools_init' ); } /** * Build the output for the Tools admin page. * * @since 1.6.6 */ public function output() { ?> <div id="wpforms-tools" class="wrap wpforms-admin-wrap wpforms-tools-tab-<?php echo esc_attr( $this->active_view_slug ); ?>"> <?php if ( $this->view && $this->view->show_nav() ) { echo '<ul class="wpforms-admin-tabs">'; foreach ( $this->views as $slug => $view ) { if ( $view->hide_from_nav() || ! $view->check_capability() ) { continue; } echo '<li>'; printf( '<a href="%1$s" class="%2$s">%3$s</a>', esc_url( $view->get_link() ), sanitize_html_class( $this->active_view_slug === $slug ? 'active' : '' ), esc_html( $view->get_label() ) ); echo '</li>'; } echo '</ul>'; } ?> <h1 class="wpforms-h1-placeholder"></h1> <div class="wpforms-admin-content wpforms-admin-settings"> <?php if ( $this->view ) { $this->view->display(); } else { do_action( 'wpforms_tools_display_tab_' . $this->active_view_slug ); } ?> </div> </div> <?php } } FlyoutMenu.php 0000644 00000010172 15252506741 0007374 0 ustar 00 <?php namespace WPMailSMTP\Admin; /** * Admin Flyout Menu. * * @since 3.0.0 */ class FlyoutMenu { /** * Hooks. * * @since 3.0.0 */ public function hooks() { /** * Filter for enabling/disabling the quick links (flyout menu). * * @since 3.0.0 * * @param bool $enabled Whether quick links are enabled. */ if ( apply_filters( 'wp_mail_smtp_admin_flyout_menu', true ) ) { add_action( 'admin_footer', [ $this, 'output' ] ); } } /** * Output menu. * * @since 3.0.0 */ public function output() { // Bail if we're not on a plugin admin page. if ( ! wp_mail_smtp()->get_admin()->is_admin_page() ) { return; } printf( '<div id="wp-mail-smtp-flyout"> <div id="wp-mail-smtp-flyout-items">%1$s</div> <a href="#" class="wp-mail-smtp-flyout-button wp-mail-smtp-flyout-head"> <div class="wp-mail-smtp-flyout-label">%2$s</div> <figure><img src="%3$s" alt="%2$s"/></figure> </a> </div>', $this->get_items_html(), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped esc_html__( 'See Quick Links', 'wp-mail-smtp' ), esc_url( wp_mail_smtp()->assets_url . '/images/flyout-menu/mascot.svg' ) ); } /** * Generate menu items HTML. * * @since 3.0.0 * * @return string Menu items HTML. */ private function get_items_html() { $items = array_reverse( $this->menu_items() ); $items_html = ''; foreach ( $items as $item_key => $item ) { $items_html .= sprintf( '<a href="%1$s" target="_blank" rel="noopener noreferrer" class="wp-mail-smtp-flyout-button wp-mail-smtp-flyout-item wp-mail-smtp-flyout-item-%2$d"%5$s%6$s> <div class="wp-mail-smtp-flyout-label">%3$s</div> <img src="%4$s" alt="%3$s"> </a>', esc_url( $item['url'] ), (int) $item_key, esc_html( $item['title'] ), esc_url( $item['icon'] ), ! empty( $item['bgcolor'] ) ? ' style="background-color: ' . esc_attr( $item['bgcolor'] ) . '"' : '', ! empty( $item['hover_bgcolor'] ) ? ' onMouseOver="this.style.backgroundColor=\'' . esc_attr( $item['hover_bgcolor'] ) . '\'" onMouseOut="this.style.backgroundColor=\'' . esc_attr( $item['bgcolor'] ) . '\'"' : '' ); } return $items_html; } /** * Menu items data. * * @since 3.0.0 * * @return array Menu items data. */ private function menu_items() { $icons_url = wp_mail_smtp()->assets_url . '/images/flyout-menu'; $items = [ [ 'title' => esc_html__( 'Upgrade to WP Mail SMTP Pro', 'wp-mail-smtp' ), 'url' => wp_mail_smtp()->get_upgrade_link( [ 'medium' => 'quick-link-menu' ] ), 'icon' => $icons_url . '/star.svg', 'bgcolor' => '#E27730', 'hover_bgcolor' => '#B85A1B', ], [ 'title' => esc_html__( 'Support & Docs', 'wp-mail-smtp' ), // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound 'url' => esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/', [ 'medium' => 'quick-link-menu', 'content' => 'Support' ] ) ), 'icon' => $icons_url . '/life-ring.svg', ], [ 'title' => esc_html__( 'Follow on Facebook', 'wp-mail-smtp' ), 'url' => 'https://www.facebook.com/wpmailsmtp', 'icon' => $icons_url . '/facebook.svg', ], [ 'title' => esc_html__( 'Suggest a Feature', 'wp-mail-smtp' ), // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound 'url' => esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/suggest-a-feature/', [ 'medium' => 'quick-link-menu', 'content' => 'Feature' ] ) ), 'icon' => $icons_url . '/lightbulb.svg', ], ]; if ( wp_mail_smtp()->is_pro() ) { array_shift( $items ); } /** * Filters quick links items. * * @since 3.0.0 * * @param array $items { * Quick links items. * * @type string $title Item title. * @type string $url Item link. * @type string $icon Item icon url. * @type string $bgcolor Item background color (optional). * @type string $hover_bgcolor Item background color on hover (optional). * } */ return apply_filters( 'wp_mail_smtp_admin_flyout_menu_menu_items', $items ); } } PluginsCategory.php 0000644 00000012612 15252506741 0010405 0 ustar 00 <?php namespace WPForms\Admin; /** * Adds a "WPForms" category tab to the WordPress Plugins screen. * * Uses the `plugins_list` filter to register an extra group containing * every WPForms-owned plugin (the main plugin and all `wpforms-*` addons), * and the `plugins_list_status_text` filter (introduced in WordPress 7.0) * to provide a friendly label for the new tab. * * On WordPress versions older than 7.0, the feature is disabled so * customers keep relying on the existing search / filter mechanism. * * @since 1.10.0.5 */ class PluginsCategory { /** * Status slug used as a key in `$plugins` and as `?plugin_status=` value. * * @since 1.10.0.5 */ private const STATUS_KEY = 'wpforms'; /** * Slug prefix shared by the main plugin and all WPForms addons. * * @since 1.10.0.5 */ private const SLUG_PREFIX = 'wpforms'; /** * Init. * * @since 1.10.0.5 */ public function init(): void { if ( ! $this->is_supported() ) { return; } $this->hooks(); } /** * Whether the current request supports the new plugins category tab. * * Registers on the `plugins.php` screen and on the AJAX * `search-plugins` handler invoked by the live-search input, * so the filter stays scoped to WPForms when the user clears * the search box (the 'X' icon) and the table is re-rendered * via admin-ajax. * * @since 1.10.0.5 * * @return bool */ private function is_supported(): bool { global $pagenow; $is_plugins_screen = ! empty( $pagenow ) && $pagenow === 'plugins.php'; // The 'X' clear icon on the live-search input fires the // `search-plugins` AJAX action, which re-runs `plugins_list` // from admin-ajax.php. The nonce is verified inside the core // handler before our filter runs. // phpcs:disable WordPress.Security.NonceVerification.Missing $is_plugins_search_ajax = wp_doing_ajax() && isset( $_POST['action'], $_POST['pagenow'] ) && sanitize_key( $_POST['action'] ) === 'search-plugins' && sanitize_key( $_POST['pagenow'] ) === 'plugins'; // phpcs:enable WordPress.Security.NonceVerification.Missing if ( ! $is_plugins_screen && ! $is_plugins_search_ajax ) { return false; } return is_wp_version_compatible( '7.0' ); } /** * Register hooks. * * @since 1.10.0.5 */ private function hooks(): void { add_filter( 'plugins_list', [ $this, 'add_category' ] ); add_filter( 'plugins_list_status_text', [ $this, 'category_label' ], 10, 3 ); } /** * Append a WPForms group to the plugins list used by the list table. * * @since 1.10.0.5 * * @param array|mixed $plugins Plugins grouped by status (`all`, `active`, ...). * * @return array */ public function add_category( $plugins ): array { $plugins = (array) $plugins; if ( empty( $plugins['all'] ) || ! is_array( $plugins['all'] ) ) { return $plugins; } $wpforms_plugins = []; foreach ( $plugins['all'] as $file => $plugin_data ) { if ( ! is_array( $plugin_data ) ) { continue; } if ( $this->is_wpforms_plugin( (string) $file, $plugin_data ) ) { $wpforms_plugins[ $file ] = $plugin_data; } } if ( ! empty( $wpforms_plugins ) ) { $plugins[ self::STATUS_KEY ] = $wpforms_plugins; } return $plugins; } /** * Provide the human-readable label for the WPForms category tab. * * The list table escapes the returned string and appends the count * span, so the value must be plain text without HTML. * * @since 1.10.0.5 * * @param string|mixed $text Status text. Default empty string. * @param int|mixed $count Number of plugins in the category. * @param string|mixed $type The status slug being filtered. * * @return string * @noinspection PhpUnusedParameterInspection */ public function category_label( $text, $count, $type ): string { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed if ( $type !== self::STATUS_KEY ) { return is_string( $text ) ? $text : ''; } // "WPForms" is a brand name, but keep the call translatable so locales // can adjust casing if needed. The %s count is appended by core. return __( 'WPForms', 'wpforms-lite' ); } /** * Determine whether a plugin should be listed under the WPForms category. * * The default rule matches by folder slug: the main plugin (`wpforms`) * and any `wpforms-*` addon. Third parties can override via the * exposed filter without touching this class. * * @since 1.10.0.5 * * @param string $file Plugin file path (e.g. `wpforms-stripe/wpforms-stripe.php`). * @param array $plugin_data Plugin metadata as returned by `get_plugins()`. * * @return bool */ private function is_wpforms_plugin( string $file, array $plugin_data ): bool { $slug = explode( '/', $file )[0]; $is_ours = $slug === self::SLUG_PREFIX || strpos( $slug, self::SLUG_PREFIX . '-' ) === 0; /** * Filters whether a plugin should be listed under the WPForms category tab. * * @since 1.10.0.5 * * @param bool $is_ours Whether the plugin is recognised as a WPForms plugin. * @param string $file Plugin file path (e.g. `wpforms-stripe/wpforms-stripe.php`). * @param array $plugin_data Plugin metadata as returned by `get_plugins()`. * * @return bool Whether the plugin should appear under the WPForms tab. */ return (bool) apply_filters( 'wpforms_admin_plugins_category_is_wpforms_plugin', $is_ours, $file, $plugin_data ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName } } Notifications/Notifications.php 0000644 00000041614 15252506741 0012714 0 ustar 00 <?php namespace WPForms\Admin\Notifications; /** * Notifications. * * @since 1.7.5 */ class Notifications { /** * Source of notifications content. * * @since 1.7.5 * * @var string */ const SOURCE_URL = 'https://wpformsapi.com/feeds/v1/notifications'; /** * Array of license types, that are considered being Elite level. * * @since 1.7.5 * * @var array */ const LICENSES_ELITE = [ 'agency', 'ultimate', 'elite' ]; /** * Option value. * * @since 1.7.5 * * @var bool|array */ public $option = false; /** * Current license type. * * @since 1.7.5 * * @var string */ private $license_type; /** * Initialize class. * * @since 1.7.5 */ public function init() { $this->hooks(); } /** * Register hooks. * * @since 1.7.5 */ public function hooks() { add_action( 'wpforms_admin_notifications_update', [ $this, 'update' ] ); if ( ! wpforms_is_admin_ajax() && ! is_admin() ) { return; } add_action( 'wpforms_overview_enqueue', [ $this, 'enqueues' ] ); add_action( 'wpforms_admin_overview_before_table', [ $this, 'output' ] ); add_action( 'deactivate_plugin', [ $this, 'delete' ], 10, 2 ); add_action( 'wp_ajax_wpforms_notification_dismiss', [ $this, 'dismiss' ] ); } /** * Check if user has access and is enabled. * * @since 1.7.5 * @since 1.8.2 Added AS task support. * * @return bool */ public function has_access() { $has_access = ! wpforms_setting( 'hide-announcements', false ); if ( ! wp_doing_cron() && ! wpforms_doing_wp_cli() ) { $has_access = $has_access && wpforms_current_user_can( 'view_forms' ); } /** * Allow modifying state if a user has access. * * @since 1.6.0 * * @param bool $access True if user has access. */ return (bool) apply_filters( 'wpforms_admin_notifications_has_access', $has_access ); } /** * Get option value. * * @since 1.7.5 * * @param bool $cache Reference property cache if available. * * @return array */ public function get_option( $cache = true ) { if ( $this->option && $cache ) { return $this->option; } $option = (array) get_option( 'wpforms_notifications', [] ); $this->option = [ 'update' => ! empty( $option['update'] ) ? (int) $option['update'] : 0, 'feed' => ! empty( $option['feed'] ) ? (array) $option['feed'] : [], 'events' => ! empty( $option['events'] ) ? (array) $option['events'] : [], 'dismissed' => ! empty( $option['dismissed'] ) ? (array) $option['dismissed'] : [], ]; return $this->option; } /** * Fetch notifications from feed. * * @since 1.7.5 * * @return array */ public function fetch_feed() { $response = wp_remote_get( self::SOURCE_URL, [ 'timeout' => 10, 'user-agent' => wpforms_get_default_user_agent(), ] ); if ( is_wp_error( $response ) ) { return []; } $body = wp_remote_retrieve_body( $response ); if ( empty( $body ) ) { return []; } return $this->verify( json_decode( $body, true ) ); } /** * Verify notification data before it is saved. * * @since 1.7.5 * * @param array $notifications Array of notifications items to verify. * * @return array */ public function verify( $notifications ) { $data = []; if ( ! is_array( $notifications ) || empty( $notifications ) ) { return $data; } foreach ( $notifications as $notification ) { // Ignore if one of the conditional checks is true: // // 1. notification message is empty. // 2. license type does not match. // 3. notification is expired. // 4. notification has already been dismissed. // 5. notification existed before installing WPForms. // (Prevents bombarding the user with notifications after activation). if ( empty( $notification['content'] ) || ! $this->is_license_type_match( $notification ) || $this->is_expired( $notification ) || $this->is_dismissed( $notification ) || $this->is_existed( $notification ) ) { continue; } $data[] = $notification; } return $data; } /** * Verify saved notification data for active notifications. * * @since 1.7.5 * * @param array $notifications Array of notifications items to verify. * * @return array */ public function verify_active( $notifications ) { if ( ! is_array( $notifications ) || empty( $notifications ) ) { return []; } $current_timestamp = time(); // Remove notifications that are not active. foreach ( $notifications as $key => $notification ) { if ( ( ! empty( $notification['start'] ) && $current_timestamp < strtotime( $notification['start'] ) ) || ( ! empty( $notification['end'] ) && $current_timestamp > strtotime( $notification['end'] ) ) ) { unset( $notifications[ $key ] ); } } return $notifications; } /** * Get notification data. * * @since 1.7.5 * * @return array */ public function get() { if ( ! $this->has_access() ) { return []; } $option = $this->get_option(); // Update notifications using async task. if ( empty( $option['update'] ) || time() > $option['update'] + DAY_IN_SECONDS ) { $tasks = wpforms()->obj( 'tasks' ); if ( ! $tasks->is_scheduled( 'wpforms_admin_notifications_update' ) !== false ) { $tasks ->create( 'wpforms_admin_notifications_update' ) ->async() ->params() ->register(); } } $feed = ! empty( $option['feed'] ) ? $this->verify_active( $option['feed'] ) : []; $events = ! empty( $option['events'] ) ? $this->verify_active( $option['events'] ) : []; return array_merge( $feed, $events ); } /** * Get notification count. * * @since 1.7.5 * * @return int */ public function get_count() { return count( $this->get() ); } /** * Add a new Event Driven notification. * * @since 1.7.5 * * @param array $notification Notification data. */ public function add( $notification ) { if ( ! $this->is_valid( $notification ) ) { return; } $option = $this->get_option(); // Notification ID already exists. if ( ! empty( $option['events'][ $notification['id'] ] ) ) { return; } update_option( 'wpforms_notifications', [ 'update' => $option['update'], 'feed' => $option['feed'], 'events' => array_merge( $notification, $option['events'] ), 'dismissed' => $option['dismissed'], ] ); } /** * Determine if notification data is valid. * * @since 1.7.5 * * @param array $notification Notification data. * * @return bool */ public function is_valid( $notification ) { if ( empty( $notification['id'] ) ) { return false; } return ! empty( $this->verify( [ $notification ] ) ); } /** * Determine if notification has already been dismissed. * * @since 1.7.5 * * @param array $notification Notification data. * * @return bool */ private function is_dismissed( $notification ) { $option = $this->get_option(); // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict return ! empty( $option['dismissed'] ) && in_array( $notification['id'], $option['dismissed'] ); } /** * Determine if license type is match. * * @since 1.7.5 * * @param array $notification Notification data. * * @return bool */ private function is_license_type_match( $notification ) { // A specific license type is not required. if ( empty( $notification['type'] ) ) { return true; } return in_array( $this->get_license_type(), (array) $notification['type'], true ); } /** * Determine if notification is expired. * * @since 1.7.5 * * @param array $notification Notification data. * * @return bool */ private function is_expired( $notification ) { return ! empty( $notification['end'] ) && time() > strtotime( $notification['end'] ); } /** * Determine if notification existed before installing WPForms. * * @since 1.7.5 * * @param array $notification Notification data. * * @return bool */ private function is_existed( $notification ) { $activated = wpforms_get_activated_timestamp(); return ! empty( $activated ) && ! empty( $notification['start'] ) && $activated > strtotime( $notification['start'] ); } /** * Update notification data from feed. * * @since 1.7.5 * @since 1.7.8 Added `wp_cache_flush()` call when the option has been updated. * @since 1.8.2 Don't fire the update action when it disabled or was fired recently. */ public function update() { if ( ! $this->has_access() ) { return; } $option = $this->get_option(); // Double-check the last update time to prevent multiple requests. if ( ! empty( $option['update'] ) && time() < $option['update'] + DAY_IN_SECONDS ) { return; } $data = [ 'feed' => $this->fetch_feed(), 'events' => $option['events'], 'dismissed' => $option['dismissed'], ]; // phpcs:disable WPForms.PHP.ValidateHooks.InvalidHookName /** * Allow changing notification data before it will be updated in database. * * @since 1.7.5 * * @param array $data New notification data. */ $data = (array) apply_filters( 'wpforms_admin_notifications_update_data', $data ); // phpcs:enable WPForms.PHP.ValidateHooks.InvalidHookName $data['update'] = time(); update_option( 'wpforms_notifications', $data ); } /** * Remove notification data from database before a plugin is deactivated. * * @since 1.7.5 * * @param string $plugin Path to the plugin file relative to the plugins directory. * @param bool $network_deactivating Whether the plugin is deactivated for all sites in the network * or just the current site. Multisite only. Default false. */ public function delete( $plugin, $network_deactivating ) { $wpforms_plugins = [ 'wpforms-lite/wpforms.php', 'wpforms/wpforms.php', ]; if ( ! in_array( $plugin, $wpforms_plugins, true ) ) { return; } delete_option( 'wpforms_notifications' ); } /** * Enqueue assets on Form Overview admin page. * * @since 1.7.5 */ public function enqueues() { if ( ! $this->get_count() ) { return; } $min = wpforms_get_min_suffix(); wp_enqueue_style( 'wpforms-admin-notifications', WPFORMS_PLUGIN_URL . "assets/css/admin-notifications{$min}.css", [ 'wpforms-lity' ], WPFORMS_VERSION ); wp_enqueue_script( 'wpforms-admin-notifications', WPFORMS_PLUGIN_URL . "assets/js/admin/admin-notifications{$min}.js", [ 'jquery', 'wpforms-lity' ], WPFORMS_VERSION, true ); // Lity. wp_enqueue_style( 'wpforms-lity', WPFORMS_PLUGIN_URL . 'assets/lib/lity/lity.min.css', [], WPFORMS_VERSION ); wp_enqueue_script( 'wpforms-lity', WPFORMS_PLUGIN_URL . 'assets/lib/lity/lity.min.js', [ 'jquery' ], WPFORMS_VERSION, true ); } /** * Output notifications on Form Overview admin area. * * @since 1.7.5 */ public function output() { // Leave early if there are no forms. if ( ! wpforms()->obj( 'form' )->forms_exist() ) { return; } $notifications = $this->get(); if ( empty( $notifications ) ) { return; } $notifications_html = ''; $current_class = ' current'; $content_allowed_tags = $this->get_allowed_tags(); foreach ( $notifications as $notification ) { // Prepare required arguments. $notification = wp_parse_args( $notification, [ 'id' => 0, 'title' => '', 'content' => '', 'video' => '', ] ); $title = $this->get_component_data( $notification['title'] ); $content = $this->get_component_data( $notification['content'] ); if ( ! $title && ! $content ) { continue; } // Notification HTML. $notifications_html .= sprintf( '<div class="wpforms-notifications-message%5$s" data-message-id="%4$s"> <h3 class="wpforms-notifications-title">%1$s%6$s</h3> <div class="wpforms-notifications-content">%2$s</div> %3$s </div>', esc_html( $title ), wp_kses( wpautop( $content ), $content_allowed_tags ), $this->get_notification_buttons_html( $notification ), esc_attr( $notification['id'] ), esc_attr( $current_class ), $this->get_video_badge_html( $this->get_component_data( $notification['video'] ) ) ); // Only first notification is current. $current_class = ''; } // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wpforms_render( 'admin/notifications', [ 'notifications' => [ 'count' => count( $notifications ), 'html' => $notifications_html, ], ], true ); } /** * Get the allowed HTML tags and their attributes. * * @since 1.8.8 * * @return array */ public function get_allowed_tags(): array { return [ 'br' => [], 'em' => [], 'strong' => [], 'span' => [ 'style' => [], ], 'p' => [ 'id' => [], 'class' => [], ], 'a' => [ 'href' => [], 'target' => [], 'rel' => [], ], ]; } /** * Retrieve notification's buttons HTML. * * @since 1.7.5 * * @param array $notification Notification data. * * @return string */ private function get_notification_buttons_html( $notification ) { $html = ''; if ( empty( $notification['btns'] ) || ! is_array( $notification['btns'] ) ) { return $html; } foreach ( $notification['btns'] as $btn_type => $btn ) { $btn = $this->get_component_data( $btn ); if ( ! $btn ) { continue; } $url = $this->prepare_btn_url( $btn ); $target = ! empty( $btn['target'] ) ? $btn['target'] : '_blank'; $target = ! empty( $url ) && strpos( $url, home_url() ) === 0 ? '_self' : $target; $html .= sprintf( '<a href="%1$s" class="button button-%2$s"%3$s>%4$s</a>', esc_url( $url ), $btn_type === 'main' ? 'primary' : 'secondary', $target === '_blank' ? ' target="_blank" rel="noopener noreferrer"' : '', ! empty( $btn['text'] ) ? esc_html( $btn['text'] ) : '' ); } return ! empty( $html ) ? sprintf( '<div class="wpforms-notifications-buttons">%s</div>', $html ) : ''; } /** * Retrieve notification's component data by a license type. * * @since 1.7.5 * * @param mixed $data Component data. * * @return false|mixed */ private function get_component_data( $data ) { if ( empty( $data['license'] ) ) { return $data; } $license_type = $this->get_license_type(); if ( in_array( $license_type, self::LICENSES_ELITE, true ) ) { $license_type = 'elite'; } return ! empty( $data['license'][ $license_type ] ) ? $data['license'][ $license_type ] : false; } /** * Retrieve the current installation license type (always lowercase). * * @since 1.7.5 * * @return string */ private function get_license_type() { if ( $this->license_type ) { return $this->license_type; } $this->license_type = wpforms_get_license_type(); if ( ! $this->license_type ) { $this->license_type = 'lite'; } return $this->license_type; } /** * Dismiss notification via AJAX. * * @since 1.7.5 */ public function dismiss() { // Check for required param, security and access. if ( empty( $_POST['id'] ) || ! check_ajax_referer( 'wpforms-admin', 'nonce', false ) || ! $this->has_access() ) { wp_send_json_error(); } $id = sanitize_key( $_POST['id'] ); $type = is_numeric( $id ) ? 'feed' : 'events'; $option = $this->get_option(); $option['dismissed'][] = $id; $option['dismissed'] = array_unique( $option['dismissed'] ); // Remove notification. if ( is_array( $option[ $type ] ) && ! empty( $option[ $type ] ) ) { foreach ( $option[ $type ] as $key => $notification ) { if ( (string) $notification['id'] === (string) $id ) { unset( $option[ $type ][ $key ] ); break; } } } update_option( 'wpforms_notifications', $option ); wp_send_json_success(); } /** * Prepare button URL. * * @since 1.7.5 * * @param array $btn Button data. * * @return string */ private function prepare_btn_url( $btn ) { if ( empty( $btn['url'] ) ) { return ''; } $replace_tags = [ '{admin_url}' => admin_url(), '{license_key}' => wpforms_get_license_key(), ]; return str_replace( array_keys( $replace_tags ), array_values( $replace_tags ), $btn['url'] ); } /** * Get the notification's video badge HTML. * * @since 1.7.5 * * @param string $video_url Valid video URL. * * @return string */ private function get_video_badge_html( $video_url ) { $video_url = wp_http_validate_url( $video_url ); if ( empty( $video_url ) ) { return ''; } $data_attr_lity = wp_is_mobile() ? '' : 'data-lity'; return sprintf( '<a class="wpforms-notifications-badge" href="%1$s" %2$s> <svg fill="none" viewBox="0 0 15 13" aria-hidden="true"> <path fill="#fff" d="M4 2.5h7v8H4z"/> <path fill="#D63638" d="M14.2 10.5v-8c0-.4-.2-.8-.5-1.1-.3-.3-.7-.5-1.1-.5H2.2c-.5 0-.8.2-1.1.5-.4.3-.5.7-.5 1.1v8c0 .4.2.8.5 1.1.3.3.6.5 1 .5h10.5c.4 0 .8-.2 1.1-.5.3-.3.5-.7.5-1.1Zm-8.8-.8V3.3l4.8 3.2-4.8 3.2Z"/> </svg> %3$s </a>', esc_url( $video_url ), esc_attr( $data_attr_lity ), esc_html__( 'Watch Video', 'wpforms-lite' ) ); } } Notifications/EventDriven.php 0000644 00000053256 15252506741 0012341 0 ustar 00 <?php namespace WPForms\Admin\Notifications; use WPForms\Migrations\Base as MigrationsBase; /** * Class EventDriven. * * @since 1.7.5 * @since 1.9.1 Not used anymore, but still exists for using it in the future. */ class EventDriven { /** * WPForms version when the Event Driven feature has been introduced. * * @since 1.7.5 * * @var string */ public const FEATURE_INTRODUCED = '1.7.5'; /** * Expected date format for notifications. * * @since 1.7.5 * * @var string */ private const DATE_FORMAT = 'Y-m-d H:i:s'; /** * Common UTM parameters. * * @since 1.7.5 * * @var array */ private const UTM_PARAMS = [ 'utm_source' => 'WordPress', 'utm_medium' => 'Event Notification', ]; /** * Common targets for date logic. * * Available items: * - upgraded (upgraded to the latest version) * - activated * - forms_first_created * - X.X.X.X (upgraded to a specific version) * - pro (activated/installed) * - lite (activated/installed) * * @since 1.7.5 * * @var array */ private const DATE_LOGIC = [ 'upgraded', 'activated', 'forms_first_created' ]; /** * Timestamps. * * @since 1.7.5 * * @var array */ private $timestamps = []; /** * Initialize class. * * @since 1.7.5 */ public function init(): void { if ( ! $this->allow_load() ) { return; } $this->hooks(); } /** * Indicate if this is allowed to load. * * @since 1.7.5 * * @return bool */ private function allow_load(): bool { $notifications_obj = wpforms()->obj( 'notifications' ); $has_access = $notifications_obj && $notifications_obj->has_access(); return $has_access || wp_doing_cron(); } /** * Hooks. * * @since 1.7.5 */ private function hooks(): void { add_filter( 'wpforms_admin_notifications_update_data', [ $this, 'update_events' ] ); } /** * Add Event Driven notifications before saving them in a database. * * @since 1.7.5 * * @param array|mixed $data Notification data. * * @return array */ public function update_events( $data ): array { $data = (array) $data; $updated = []; /** * Allow developers to turn on debug mode: store all notifications and then show all of them. * * @since 1.7.5 * * @param bool $is_debug True if it's a debug mode. Default: false. */ $is_debug = (bool) apply_filters( 'wpforms_admin_notifications_event_driven_update_events_debug', false ); $wpforms_notifications = wpforms()->obj( 'notifications' ); foreach ( $this->get_notifications() as $slug => $notification ) { $is_processed = ! empty( $data['events'][ $slug ]['start'] ); $is_conditional_ok = ! ( isset( $notification['condition'] ) && $notification['condition'] === false ); // If it's a debug mode, OR valid notification has been already processed - skip running logic checks and save it. if ( $is_debug || ( $is_processed && $is_conditional_ok && $wpforms_notifications && $wpforms_notifications->is_valid( $data['events'][ $slug ] ) ) ) { unset( $notification['date_logic'], $notification['offset'], $notification['condition'] ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date $notification['start'] = $is_debug ? date( self::DATE_FORMAT ) : $data['events'][ $slug ]['start']; $updated[ $slug ] = $notification; continue; } // Ignore if a condition is not passed conditional checks. if ( ! $is_conditional_ok ) { continue; } $timestamp = $this->get_timestamp_by_date_logic( $this->prepare_date_logic( $notification ) ); if ( empty( $timestamp ) ) { continue; } // Probably, notification should be visible after some time. $offset = empty( $notification['offset'] ) ? 0 : absint( $notification['offset'] ); // Set a start date when the notification will be shown. // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date $notification['start'] = date( self::DATE_FORMAT, $timestamp + $offset ); // Ignore if notification data is not valid. if ( ! $wpforms_notifications->is_valid( $notification ) ) { continue; } // Remove unnecessary values, mark the notification as active, and save it. unset( $notification['date_logic'], $notification['offset'], $notification['condition'] ); $updated[ $slug ] = $notification; } $data['events'] = $updated; return $data; } /** * Prepare and retrieve date logic. * * @since 1.7.5 * * @param array|mixed $notification Notification data. * * @return array */ private function prepare_date_logic( $notification ): array { $date_logic = empty( $notification['date_logic'] ) || ! is_array( $notification['date_logic'] ) ? self::DATE_LOGIC : $notification['date_logic']; return array_filter( array_filter( $date_logic, 'is_string' ) ); } /** * Retrieve a notification timestamp based on date logic. * * @since 1.7.5 * * @param array $args Date logic. * * @return int */ private function get_timestamp_by_date_logic( array $args ): int { foreach ( $args as $target ) { if ( ! empty( $this->timestamps[ $target ] ) ) { return $this->timestamps[ $target ]; } $timestamp = (int) call_user_func( $this->get_timestamp_callback( $target ), $target ); if ( ! empty( $timestamp ) ) { $this->timestamps[ $target ] = $timestamp; return $timestamp; } } return 0; } /** * Retrieve a callback that determines the necessary timestamp. * * @since 1.7.5 * * @param string $target Date logic target. * * @return callable */ private function get_timestamp_callback( string $target ) { $raw_target = $target; // As $target should be a part of name for callback method, // this regular expression allows lowercase characters, numbers, and underscore. $target = strtolower( preg_replace( '/[^a-z0-9_]/', '', $target ) ); // Basic callback. $callback = [ $this, 'get_timestamp_' . $target ]; // Determine if a special version number is passed. // Uses the regular expression to check a SemVer string. // @link https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string. if ( preg_match( '/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\.([1-9\d*]))?(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/', $raw_target ) ) { $callback = [ $this, 'get_timestamp_upgraded' ]; } // If a callback is callable, return it. Otherwise, return fallback. return is_callable( $callback ) ? $callback : '__return_zero'; } /** * Retrieve a timestamp when WPForms was upgraded. * * @since 1.7.5 * * @param string $version WPForms version. * * @return int|false Unix timestamp. False on failure. */ private function get_timestamp_upgraded( string $version ) { if ( $version === 'upgraded' ) { $version = WPFORMS_VERSION; } $timestamp = wpforms_get_upgraded_timestamp( $version ); if ( $timestamp === false ) { return false; } // Return a current timestamp if no luck to return a migration's timestamp. return $timestamp <= 0 ? time() : $timestamp; } /** * Retrieve a timestamp when WPForms was first installed/activated. * * @since 1.7.5 * * @return int|false Unix timestamp. False on failure. * @noinspection PhpUnusedPrivateMethodInspection */ private function get_timestamp_activated() { return wpforms_get_activated_timestamp(); } /** * Retrieve a timestamp when Lite was first installed. * * @since 1.7.5 * * @return int|false Unix timestamp. False on failure. * @noinspection PhpUnusedPrivateMethodInspection */ private function get_timestamp_lite() { $activated = (array) get_option( 'wpforms_activated', [] ); return ! empty( $activated['lite'] ) ? absint( $activated['lite'] ) : false; } /** * Retrieve a timestamp when Pro was first installed. * * @since 1.7.5 * * @return int|false Unix timestamp. False on failure. * @noinspection PhpUnusedPrivateMethodInspection */ private function get_timestamp_pro() { $activated = (array) get_option( 'wpforms_activated', [] ); return ! empty( $activated['pro'] ) ? absint( $activated['pro'] ) : false; } /** * Retrieve a timestamp when a first form was created. * * @since 1.7.5 * * @return int|false Unix timestamp. False on failure. * @noinspection PhpUnusedPrivateMethodInspection */ private function get_timestamp_forms_first_created() { $timestamp = get_option( 'wpforms_forms_first_created' ); return ! empty( $timestamp ) ? absint( $timestamp ) : false; } /** * Retrieve a number of entries. * * @since 1.7.5 * * @return int */ private function get_entry_count(): int { static $count; if ( is_int( $count ) ) { return $count; } global $wpdb; $count = 0; $entry_handler = wpforms()->obj( 'entry' ); $entry_meta_handler = wpforms()->obj( 'entry_meta' ); if ( ! $entry_handler || ! $entry_meta_handler ) { return $count; } $query = "SELECT COUNT( $entry_handler->primary_key ) FROM $entry_handler->table_name WHERE $entry_handler->primary_key NOT IN ( SELECT entry_id FROM $entry_meta_handler->table_name WHERE type = 'backup_id' );"; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared $count = (int) $wpdb->get_var( $query ); return $count; } /** * Retrieve forms. * * @since 1.7.5 * * @param int $posts_per_page The number of forms to return. * * @return array * @noinspection PhpSameParameterValueInspection */ private function get_forms( int $posts_per_page ): array { $form_obj = wpforms()->obj( 'form' ); $forms = $form_obj ? $form_obj->get( '', [ 'posts_per_page' => $posts_per_page, 'nopaging' => false, 'update_post_meta_cache' => false, 'update_post_term_cache' => false, 'cap' => false, ] ) : null; return ! empty( $forms ) ? (array) $forms : []; } /** * Determine if the user has at least 1 form. * * @since 1.7.5 * * @return bool */ private function has_form(): bool { return ! empty( $this->get_forms( 1 ) ); } /** * Determine if it is a new user. * * @since 1.7.5 * * @return bool */ private function is_new_user(): bool { // Check if this is an update or first install. return ! get_option( MigrationsBase::PREVIOUS_CORE_VERSION_OPTION_NAME ); } /** * Determine if it's an English site. * * @since 1.7.5 * * @return bool * @noinspection PhpUnusedPrivateMethodInspection */ private function is_english_site(): bool { static $result; if ( is_bool( $result ) ) { return $result; } $locales = array_unique( array_map( [ $this, 'language_to_iso' ], [ get_locale(), get_user_locale() ] ) ); $result = count( $locales ) === 1 && $locales[0] === 'en'; return $result; } /** * Convert language to ISO. * * @since 1.7.5 * * @param string|mixed $lang Language value. * * @return string */ private function language_to_iso( $lang ): string { $lang = (string) $lang; return $lang === '' ? $lang : explode( '_', $lang )[0]; } /** * Retrieve a modified URL query string. * * @since 1.7.5 * * @param array $args An associative array of query variables. * @param string $url A URL to act upon. * * @return string */ private function add_query_arg( array $args, string $url ): string { return add_query_arg( array_merge( $this->get_utm_params(), array_map( 'rawurlencode', $args ) ), $url ); } /** * Retrieve UTM parameters for Event Driven notifications links. * * @since 1.7.5 * * @return array */ private function get_utm_params(): array { static $utm_params; if ( ! $utm_params ) { $utm_params = [ 'utm_source' => self::UTM_PARAMS['utm_source'], 'utm_medium' => rawurlencode( self::UTM_PARAMS['utm_medium'] ), 'utm_campaign' => wpforms()->is_pro() ? 'plugin' : 'liteplugin', ]; } return $utm_params; } /** * Retrieve Event Driven notifications. * * @since 1.7.5 * * @return array */ private function get_notifications(): array { return [ 'welcome-message' => [ 'id' => 'welcome-message', 'title' => esc_html__( 'Welcome to WPForms!', 'wpforms-lite' ), 'content' => sprintf( /* translators: %s - number of templates. */ esc_html__( 'We’re grateful that you chose WPForms for your website! Now that you’ve installed the plugin, you’re less than 5 minutes away from publishing your first form. To make it easy, we’ve got %s form templates to get you started!', 'wpforms-lite' ), '2100+' ), 'btns' => [ 'main' => [ 'url' => admin_url( 'admin.php?page=wpforms-builder' ), 'text' => esc_html__( 'Start Building', 'wpforms-lite' ), ], 'alt' => [ 'url' => $this->add_query_arg( [ 'utm_content' => 'Welcome Read the Guide' ], 'https://wpforms.com/docs/creating-first-form/' ), 'text' => esc_html__( 'Read the Guide', 'wpforms-lite' ), ], ], 'type' => [ 'lite', 'basic', 'plus', 'pro', 'agency', 'elite', 'ultimate', ], // Immediately after activation (new users only, not upgrades). 'condition' => $this->is_new_user(), ], 'wp-mail-smtp-education' => [ 'id' => 'wp-mail-smtp-education', 'title' => esc_html__( 'Don’t Miss Your Form Notification Emails!', 'wpforms-lite' ), 'content' => esc_html__( 'Did you know that many WordPress sites are not properly configured to send emails? With the free WP Mail SMTP plugin, you can easily optimize your site to send emails, avoid the spam folder, and make sure your emails land in the recipient’s inbox every time.', 'wpforms-lite' ), 'btns' => [ 'main' => [ 'url' => admin_url( 'admin.php?page=wpforms-smtp' ), 'text' => esc_html__( 'Install Now', 'wpforms-lite' ), ], 'alt' => [ 'url' => $this->add_query_arg( [ 'utm_content' => 'WP Mail SMTP Learn More' ], 'https://wpforms.com/docs/how-to-set-up-smtp-using-the-wp-mail-smtp-plugin/' ), 'text' => esc_html__( 'Learn More', 'wpforms-lite' ), ], ], // 3 days after activation/upgrade. 'offset' => 3 * DAY_IN_SECONDS, 'condition' => ! function_exists( 'wp_mail_smtp' ), ], 'join-vip-circle' => [ 'id' => 'join-vip-circle', 'title' => esc_html__( 'Want to Be a VIP? Join Now!', 'wpforms-lite' ), 'content' => esc_html__( 'Running a WordPress site can be challenging. But help is just around the corner! Our Facebook group contains tons of tips and help to get your business growing! When you join our VIP Circle, you’ll get instant access to tips, tricks, and answers from a community of loyal WPForms users. Best of all, membership is 100% free!', 'wpforms-lite' ), 'btns' => [ 'main' => [ 'url' => 'https://www.facebook.com/groups/wpformsvip/', 'text' => esc_html__( 'Join Now', 'wpforms-lite' ), ], ], // 30 days after activation/upgrade. 'offset' => 30 * DAY_IN_SECONDS, ], 'survey-reports' => [ 'id' => 'survey-reports', 'title' => esc_html__( 'Want to Know What Your Customers Really Think?', 'wpforms-lite' ), 'content' => esc_html__( 'Nothing beats real feedback from your customers and visitors. That’s why many small businesses love our awesome Surveys and Polls addon. Instantly unlock full survey reporting right in your WordPress dashboard. And don’t forget: building a survey is easy with our pre-made templates, so you could get started within a few minutes!', 'wpforms-lite' ), 'btns' => [ 'main' => [ 'license' => [ 'lite' => [ 'url' => wpforms_admin_upgrade_link( 'Event Notification', 'Surveys and Polls Upgrade Lite' ), 'text' => esc_html__( 'Upgrade Now', 'wpforms-lite' ), ], 'basic' => [ 'url' => $this->add_query_arg( [ 'utm_content' => 'Surveys and Polls Upgrade Basic' ], 'https://wpforms.com/account/licenses/' ), 'text' => esc_html__( 'Upgrade Now', 'wpforms-lite' ), ], 'plus' => [ 'url' => $this->add_query_arg( [ 'utm_content' => 'Surveys and Polls Upgrade Basic' ], 'https://wpforms.com/account/licenses/' ), 'text' => esc_html__( 'Upgrade Now', 'wpforms-lite' ), ], 'pro' => [ 'url' => admin_url( 'admin.php?page=wpforms-addons' ), 'text' => esc_html__( 'Install Now', 'wpforms-lite' ), ], 'elite' => [ 'url' => admin_url( 'admin.php?page=wpforms-addons' ), 'text' => esc_html__( 'Install Now', 'wpforms-lite' ), ], ], ], 'alt' => [ 'url' => $this->add_query_arg( [ 'utm_content' => 'Surveys and Polls Learn More' ], 'https://wpforms.com/docs/how-to-install-and-use-the-surveys-and-polls-addon/' ), 'text' => esc_html__( 'Learn More', 'wpforms-lite' ), ], ], // 45 days after activation/upgrade. 'offset' => 45 * DAY_IN_SECONDS, 'condition' => ! defined( 'WPFORMS_SURVEYS_POLLS_VERSION' ), ], 'form-abandonment' => [ 'id' => 'form-abandonment', 'title' => esc_html__( 'Get More Leads From Your Forms!', 'wpforms-lite' ), 'content' => esc_html__( 'Are your forms converting fewer visitors than you hoped? Often, visitors quit forms partway through. That can prevent you from getting all the leads you deserve to capture. With our Form Abandonment addon, you can capture partial entries even if your visitor didn’t hit Submit! From there, it’s easy to follow up with leads and turn them into loyal customers.', 'wpforms-lite' ), 'btns' => [ 'main' => [ 'license' => [ 'lite' => [ 'url' => wpforms_admin_upgrade_link( 'Event Notification', 'Form Abandonment Upgrade Lite' ), 'text' => esc_html__( 'Upgrade Now', 'wpforms-lite' ), ], 'basic' => [ 'url' => $this->add_query_arg( [ 'utm_content' => 'Form Abandonment Upgrade Basic' ], 'https://wpforms.com/account/licenses/' ), 'text' => esc_html__( 'Upgrade Now', 'wpforms-lite' ), ], 'plus' => [ 'url' => $this->add_query_arg( [ 'utm_content' => 'Form Abandonment Upgrade Basic' ], 'https://wpforms.com/account/licenses/' ), 'text' => esc_html__( 'Upgrade Now', 'wpforms-lite' ), ], 'pro' => [ 'url' => admin_url( 'admin.php?page=wpforms-addons' ), 'text' => esc_html__( 'Install Now', 'wpforms-lite' ), ], 'elite' => [ 'url' => admin_url( 'admin.php?page=wpforms-addons' ), 'text' => esc_html__( 'Install Now', 'wpforms-lite' ), ], ], ], 'alt' => [ 'url' => $this->add_query_arg( [ 'utm_content' => 'Form Abandonment Learn More' ], 'https://wpforms.com/docs/how-to-install-and-use-form-abandonment-with-wpforms/' ), 'text' => esc_html__( 'Learn More', 'wpforms-lite' ), ], ], // 60 days after activation/upgrade. 'offset' => 60 * DAY_IN_SECONDS, 'condition' => ! defined( 'WPFORMS_FORM_ABANDONMENT_VERSION' ), ], 'ideas' => [ 'id' => 'ideas', 'title' => esc_html__( 'What’s Your Dream WPForms Feature?', 'wpforms-lite' ), 'content' => esc_html__( 'If you could add just one feature to WPForms, what would it be? We want to know! Our team is busy surveying valued customers like you as we plan the year ahead. We’d love to know which features would take your business to the next level! Do you have a second to share your idea with us?', 'wpforms-lite' ), 'btns' => [ 'main' => [ 'url' => 'https://wpforms.com/share-your-idea/', 'text' => esc_html__( 'Share Your Idea', 'wpforms-lite' ), ], ], // 120 days after activation/upgrade. 'offset' => 120 * DAY_IN_SECONDS, 'condition' => $this->has_form(), ], 'user-insights' => [ 'id' => 'user-insights', 'title' => esc_html__( 'Congratulations! You Just Got Your 100th Form Entry!', 'wpforms-lite' ), 'content' => esc_html__( 'You just hit 100 entries… and this is just the beginning! Now it’s time to dig into the data and figure out what makes your visitors tick. The User Journey addon shows you what your visitors looked at before submitting your form. Now you can easily find which areas of your site are triggering form conversions.', 'wpforms-lite' ), 'btns' => [ 'main' => [ 'license' => [ 'lite' => [ 'url' => wpforms_admin_upgrade_link( 'Event Notification', 'User Journey Upgrade Lite' ), 'text' => esc_html__( 'Upgrade Now', 'wpforms-lite' ), ], 'basic' => [ 'url' => $this->add_query_arg( [ 'utm_content' => 'User Journey Upgrade Basic' ], 'https://wpforms.com/account/licenses/' ), 'text' => esc_html__( 'Upgrade Now', 'wpforms-lite' ), ], 'plus' => [ 'url' => $this->add_query_arg( [ 'utm_content' => 'User Journey Upgrade Basic' ], 'https://wpforms.com/account/licenses/' ), 'text' => esc_html__( 'Upgrade Now', 'wpforms-lite' ), ], 'pro' => [ 'url' => admin_url( 'admin.php?page=wpforms-addons' ), 'text' => esc_html__( 'Install Now', 'wpforms-lite' ), ], 'elite' => [ 'url' => admin_url( 'admin.php?page=wpforms-addons' ), 'text' => esc_html__( 'Install Now', 'wpforms-lite' ), ], ], ], ], 'condition' => ! defined( 'WPFORMS_USER_JOURNEY_VERSION' ) && $this->get_entry_count() >= 100, ], ]; } } Notice.php 0000644 00000023542 15252506741 0006513 0 ustar 00 <?php namespace WPForms\Admin; /** * Dismissible admin notices. * * @since 1.6.7.1 * * @example Dismissible - global: * \WPForms\Admin\Notice::error( * 'Fatal error!', * [ * 'dismiss' => \WPForms\Admin\Notice::DISMISS_GLOBAL, * 'slug' => 'fatal_error_3678975', * ] * ); * * @example Dismissible - per user: * \WPForms\Admin\Notice::warning( * 'Do something please.', * [ * 'dismiss' => \WPForms\Admin\Notice::DISMISS_USER, * 'slug' => 'do_something_1238943', * ] * ); * * @example Dismissible - global, add custom class to output and disable auto paragraph in text: * \WPForms\Admin\Notice::error( * 'Fatal error!', * [ * 'dismiss' => \WPForms\Admin\Notice::DISMISS_GLOBAL, * 'slug' => 'fatal_error_348975', * 'autop' => false, * 'class' => 'some-additional-class', * ] * ); * * @example Not dismissible: * \WPForms\Admin\Notice::success( 'Everything is good!' ); */ class Notice { /** * Not dismissible. * * Constant attended to use as the value of the $args['dismiss'] argument. * DISMISS_NONE means that the notice is not dismissible. * * @since 1.6.7.1 */ const DISMISS_NONE = 0; /** * Dismissible global. * * Constant attended to use as the value of the $args['dismiss'] argument. * DISMISS_GLOBAL means that the notice will have the dismiss button, and after clicking this button, the notice will be dismissed for all users. * * @since 1.6.7.1 */ const DISMISS_GLOBAL = 1; /** * Dismissible per user. * * Constant attended to use as the value of the $args['dismiss'] argument. * DISMISS_USER means that the notice will have the dismiss button, and after clicking this button, the notice will be dismissed only for the current user.. * * @since 1.6.7.1 */ const DISMISS_USER = 2; /** * Order of notices by type. * * @since 1.9.1 */ const ORDER = [ 'error', 'warning', 'info', 'success', ]; /** * Added notices. * * @since 1.6.7.1 * * @var array */ private $notices = []; /** * Init. * * @since 1.6.7.1 */ public function init() { $this->hooks(); } /** * Hooks. * * @since 1.6.7.1 */ public function hooks() { add_action( 'admin_notices', [ $this, 'display' ], PHP_INT_MAX ); add_action( 'wp_ajax_wpforms_notice_dismiss', [ $this, 'dismiss_ajax' ] ); } /** * Enqueue assets. * * @since 1.6.7.1 */ private function enqueues() { $min = wpforms_get_min_suffix(); wp_enqueue_script( 'wpforms-admin-notices', WPFORMS_PLUGIN_URL . "assets/js/admin/notices{$min}.js", [ 'jquery' ], WPFORMS_VERSION, true ); wp_localize_script( 'wpforms-admin-notices', 'wpforms_admin_notices', [ 'ajax_url' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'wpforms-admin' ), ] ); } /** * Display the notices. * * @since 1.6.7.1 */ public function display() { $dismissed_notices = get_user_meta( get_current_user_id(), 'wpforms_admin_notices', true ); $dismissed_notices = is_array( $dismissed_notices ) ? $dismissed_notices : []; $dismissed_notices = array_merge( $dismissed_notices, (array) get_option( 'wpforms_admin_notices', [] ) ); $dismissed_notices = array_filter( wp_list_pluck( $dismissed_notices, 'dismissed' ) ); $this->notices = array_diff_key( $this->notices, $dismissed_notices ); $output = implode( '', array_column( $this->sort_notices(), 'data' ) ); echo $output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped // Enqueue script only when it's needed. if ( strpos( $output, 'is-dismissible' ) !== false ) { $this->enqueues(); } } /** * Sort notices by type. * * @since 1.9.1 * * @return array Notices. */ private function sort_notices(): array { $ordered_notices = []; foreach ( self::ORDER as $order ) { foreach ( $this->notices as $key => $notice ) { if ( $notice['type'] === $order ) { $ordered_notices[ $key ] = $notice; unset( $this->notices[ $key ] ); } } } // Notices that are not in the self::ORDER array merged to the end of array. return array_merge( $ordered_notices, $this->notices ); } /** * Add notice to the registry. * * @since 1.6.7.1 * * @param string $message Message to display. * @param string $type Type of the notice. Can be [ '' (default) | 'info' | 'error' | 'success' | 'warning' ]. * @param array $args The array of additional arguments. Please see the $defaults array below. */ public static function add( $message, $type = '', $args = [] ) { static $uniq_id = 0; $defaults = [ 'dismiss' => self::DISMISS_NONE, // Dismissible level: one of the self::DISMISS_* const. By default notice is not dismissible. 'slug' => '', // Slug. Should be unique if dismissible is not equal self::DISMISS_NONE. 'autop' => true, // `false` if not needed to pass message through wpautop(). 'class' => '', // Additional CSS class. ]; $args = wp_parse_args( $args, $defaults ); $dismissible = (int) $args['dismiss']; $dismissible = $dismissible > self::DISMISS_USER ? self::DISMISS_USER : $dismissible; $class = $dismissible > self::DISMISS_NONE ? ' is-dismissible' : ''; $global = ( $dismissible === self::DISMISS_GLOBAL ) ? 'global-' : ''; $slug = sanitize_key( $args['slug'] ); ++$uniq_id; $uniq_id += ( $uniq_id === (int) $slug ) ? 1 : 0; $notice = [ 'type' => $type, ]; $id = 'wpforms-notice-' . $global; $id .= empty( $slug ) ? $uniq_id : $slug; $type = ! empty( $type ) ? 'notice-' . esc_attr( sanitize_key( $type ) ) : ''; $class = empty( $args['class'] ) ? $class : $class . ' ' . esc_attr( sanitize_key( $args['class'] ) ); $message = $args['autop'] ? wpautop( $message ) : $message; $notice['data'] = sprintf( '<div class="notice wpforms-notice %s%s" id="%s">%s</div>', esc_attr( $type ), esc_attr( $class ), esc_attr( $id ), $message ); if ( empty( $slug ) ) { wpforms()->obj( 'notice' )->notices[] = $notice; } else { wpforms()->obj( 'notice' )->notices[ $slug ] = $notice; } } /** * Add info notice. * * @since 1.6.7.1 * * @param string $message Message to display. * @param array $args Array of additional arguments. Details in the self::add() method. */ public static function info( $message, $args = [] ) { self::add( $message, 'info', $args ); } /** * Add error notice. * * @since 1.6.7.1 * * @param string $message Message to display. * @param array $args Array of additional arguments. Details in the self::add() method. */ public static function error( $message, $args = [] ) { self::add( $message, 'error', $args ); } /** * Add success notice. * * @since 1.6.7.1 * * @param string $message Message to display. * @param array $args Array of additional arguments. Details in the self::add() method. */ public static function success( $message, $args = [] ) { self::add( $message, 'success', $args ); } /** * Add warning notice. * * @since 1.6.7.1 * * @param string $message Message to display. * @param array $args Array of additional arguments. Details in the self::add() method. */ public static function warning( $message, $args = [] ) { self::add( $message, 'warning', $args ); } /** * AJAX routine that updates dismissed notices meta data. * * @since 1.6.7.1 */ public function dismiss_ajax() { // Run a security check. check_ajax_referer( 'wpforms-admin', 'nonce' ); // Sanitize POST data. $post = array_map( 'sanitize_key', wp_unslash( $_POST ) ); // Update notices meta data. if ( strpos( $post['id'], 'global-' ) !== false ) { // Check for permissions. if ( ! wpforms_current_user_can() ) { wp_send_json_error(); } $notices = $this->dismiss_global( $post['id'] ); $level = self::DISMISS_GLOBAL; } else { $notices = $this->dismiss_user( $post['id'] ); $level = self::DISMISS_USER; } /** * Allows developers to apply additional logic to the dismissing notice process. * Executes after updating option or user meta (according to the notice level). * * @since 1.6.7.1 * * @param string $notice_id Notice ID (slug). * @param integer $level Notice level. * @param array $notices Dismissed notices. */ do_action( 'wpforms_admin_notice_dismiss_ajax', $post['id'], $level, $notices ); if ( ! wpforms_debug() ) { wp_send_json_success(); } wp_send_json_success( [ 'id' => $post['id'], 'time' => time(), 'level' => $level, 'notices' => $notices, ] ); } /** * AJAX sub-routine that updates dismissed notices option. * * @since 1.6.7.1 * * @param string $id Notice Id. * * @return array Notices. */ private function dismiss_global( $id ) { $id = str_replace( 'global-', '', $id ); $notices = get_option( 'wpforms_admin_notices', [] ); $notices[ $id ] = [ 'time' => time(), 'dismissed' => true, ]; update_option( 'wpforms_admin_notices', $notices, true ); return $notices; } /** * AJAX sub-routine that updates dismissed notices user meta. * * @since 1.6.7.1 * * @param string $id Notice Id. * * @return array Notices. */ private function dismiss_user( $id ) { $user_id = get_current_user_id(); $notices = get_user_meta( $user_id, 'wpforms_admin_notices', true ); $notices = ! is_array( $notices ) ? [] : $notices; $notices[ $id ] = [ 'time' => time(), 'dismissed' => true, ]; update_user_meta( $user_id, 'wpforms_admin_notices', $notices ); return $notices; } } Addons/AddonsCache.php 0000644 00000005624 15252506741 0010637 0 ustar 00 <?php namespace WPForms\Admin\Addons; use WPForms\Helpers\CacheBase; /** * Addons cache handler. * * @since 1.6.6 */ class AddonsCache extends CacheBase { /** * Remote source URL. * * @since 1.8.9 * * @var string */ const REMOTE_SOURCE = 'https://wpformsapi.com/feeds/v1/addons/'; /** * Determine if the class is allowed to load. * * @since 1.6.8 * * @return bool */ protected function allow_load() { if ( wp_doing_cron() || wpforms_doing_wp_cli() ) { return true; } $has_permissions = wpforms_current_user_can( [ 'create_forms', 'edit_forms' ] ); $allowed_requests = wpforms_is_admin_ajax() || wpforms_is_admin_page() || wpforms_is_admin_page( 'builder' ); return $has_permissions && $allowed_requests; } /** * Provide settings. * * @since 1.6.6 * * @return array Settings array. */ protected function setup() { return [ // Remote source URL. 'remote_source' => $this->get_remote_source(), // Addons cache file name. 'cache_file' => 'addons.json', /** * Time-to-live of the addons cache file in seconds. * * This applies to `uploads/wpforms/cache/addons.json` file. * * @since 1.6.8 * * @param integer $cache_ttl Cache time-to-live, in seconds. * Default value: WEEK_IN_SECONDS. */ 'cache_ttl' => (int) apply_filters( 'wpforms_admin_addons_cache_ttl', WEEK_IN_SECONDS ), // Scheduled update action. 'update_action' => 'wpforms_admin_addons_cache_update', ]; } /** * Get remote source URL. * * @since 1.8.9 * * @return string */ protected function get_remote_source(): string { return defined( 'WPFORMS_ADDONS_REMOTE_SOURCE' ) ? WPFORMS_ADDONS_REMOTE_SOURCE : self::REMOTE_SOURCE; } /** * Prepare addons data to store in a local cache - * generate addons icon image file name for further use. * * @since 1.6.6 * * @param array $data Raw addons data. * * @return array Prepared data for caching (with icons). */ protected function prepare_cache_data( $data ): array { if ( empty( $data ) || ! is_array( $data ) ) { return []; } $addons_cache = []; foreach ( $data as $addon ) { // Addon icon. $addon['icon'] = str_replace( 'wpforms-', 'addon-icon-', $addon['slug'] ) . '.png'; // Special case when plugin addon renamed, for instance: // Sendinblue to Brevo, or ConvertKit to Kit, // but we keep the old slug for compatibility. foreach ( [ 'wpforms-sendinblue' => [ 'old' => 'sendinblue', 'new' => 'brevo', ], 'wpforms-convertkit' => [ 'old' => 'convertkit', 'new' => 'kit', ], ] as $slug => $renamed ) { if ( $addon['slug'] === $slug ) { $addon['icon'] = str_replace( $renamed['old'], $renamed['new'], $addon['icon'] ); } } // Use slug as a key for further usage. $addons_cache[ $addon['slug'] ] = $addon; } return $addons_cache; } } Addons/Addons.php 0000644 00000033625 15252506741 0007715 0 ustar 00 <?php namespace WPForms\Admin\Addons; use WPForms\SetupWizard\RestApi; /** * Addons data handler. * * @since 1.6.6 */ class Addons { /** * Basic license. * * @since 1.8.2 */ const BASIC = 'basic'; /** * Plus license. * * @since 1.8.2 */ const PLUS = 'plus'; /** * Pro license. * * @since 1.8.2 */ const PRO = 'pro'; /** * Elite license. * * @since 1.8.2 */ const ELITE = 'elite'; /** * Agency license. * * @since 1.8.2 */ const AGENCY = 'agency'; /** * Ultimate license. * * @since 1.8.2 */ const ULTIMATE = 'ultimate'; /** * Addons cache object. * * @since 1.6.6 * * @var AddonsCache */ private $cache; /** * All Addons data. * * @since 1.6.6 * * @var array */ private $addons; /** * WPForms addons text domains. * * @since 1.9.2 * * @var array */ private $addons_text_domains = []; /** * WPForms addons titles. * * @since 1.9.2 * * @var array */ private $addons_titles = []; /** * Determine if the class is allowed to load. * * @since 1.6.6 * * @return bool */ public function allow_load() { // The Setup Wizard installation request authenticates later, inside the REST // permission callback, so no user is set this early, and the capability // gate below would always reject it. The endpoint guards the installation // itself (session token plus manage_options), so let the data load here. if ( RestApi::is_install_request() ) { return true; } $has_permissions = wpforms_current_user_can( [ 'create_forms', 'edit_forms' ] ); if ( ! $has_permissions ) { return false; } global $pagenow; $allowed_pages = in_array( $pagenow ?? '', [ 'plugins.php', 'update-core.php', 'plugin-install.php' ], true ); $allowed_ajax = $pagenow === 'admin-ajax.php' && isset( $_POST['action'] ) && $_POST['action'] === 'update-plugin'; // phpcs:ignore WordPress.Security.NonceVerification.Missing return $allowed_pages || $allowed_ajax || wpforms_is_admin_ajax() || wpforms_is_admin_page() || wpforms_is_admin_page( 'builder' ); } /** * Initialize class. * * @since 1.6.6 */ public function init() { if ( ! $this->allow_load() ) { return; } $this->cache = wpforms()->obj( 'addons_cache' ); global $pagenow; // Force update addons cache if we are on the update-core.php page. // This is necessary to update addons data while checking for all available updates. if ( $pagenow === 'update-core.php' ) { $this->cache->update( true ); } $this->addons = $this->cache->get(); $this->populate_addons_data(); $this->hooks(); } /** * Hooks. * * @since 1.6.6 */ protected function hooks() { global $pagenow; /** * Fire before admin addons init. * * @since 1.6.7 */ do_action( 'wpforms_admin_addons_init' ); // Filter Gettext only on Plugin list and Updates pages. if ( $pagenow === 'update-core.php' || $pagenow === 'plugins.php' ) { add_action( 'gettext', [ $this, 'filter_gettext' ], 10, 3 ); } } /** * Get all addons data as array. * * @since 1.6.6 * * @param bool $force_cache_update Determine if we need to update cache. Default is `false`. * * @return array */ public function get_all( bool $force_cache_update = false ) { if ( ! $this->allow_load() ) { return []; } if ( $force_cache_update ) { $this->cache->update( true ); $this->addons = $this->cache->get(); } // WPForms 1.8.7 core includes Custom Captcha. // The Custom Captcha addon will only work on WPForms 1.8.6 and earlier versions. unset( $this->addons['wpforms-captcha'] ); return $this->get_sorted_addons(); } /** * Get sorted addons data. * Recommended addons will be displayed first, * then new addons, then featured addons, * and then all other addons. * * @since 1.8.9 * * @return array */ private function get_sorted_addons(): array { if ( empty( $this->addons ) ) { return []; } $recommended = array_filter( $this->addons, static function ( $addon ) { return ! empty( $addon['recommended'] ); } ); $new = array_filter( $this->addons, static function ( $addon ) { return ! empty( $addon['new'] ); } ); $featured = array_filter( $this->addons, static function ( $addon ) { return ! empty( $addon['featured'] ); } ); return array_merge( $recommended, $new, $featured, $this->addons ); } /** * Get filtered addons data. * * Usage: * ->get_filtered( $this->addons, [ 'category' => 'payments' ] ) - addons for the payments panel. * ->get_filtered( $this->addons, [ 'license' => 'elite' ] ) - addons available for 'elite' license. * * @since 1.6.6 * * @param array $addons Raw addons data. * @param array $args Arguments array. * * @return array Addons data filtered according to given arguments. */ private function get_filtered( array $addons, array $args ): array { $args = wp_parse_args( $args, [ 'category' => '', 'license' => '', ] ); $args = array_map( 'strtolower', $args ); $filtered_addons = []; foreach ( $addons as $addon ) { foreach ( $args as $arg_key => $arg_value ) { $addon_value = wpforms_array_get_by_path( $addon, $arg_key, '' ); if ( is_array( $addon_value ) && // We cannot use preg_quote here, as $arg_value could contain regex like 'crm|email-marketing|integration'. preg_grep( '/^' . $arg_value . '$/', $addon_value ) ) { $filtered_addons[] = $addon; } } } return $filtered_addons; } /** * Get available addons data by category. * * @since 1.6.6 * * @param string $category Addon category. * * @return array. */ public function get_by_category( string $category ) { return $this->get_by_path( 'category', $category ); } /** * Get available addons data by path. * * @since 1.9.8.6 * * @param string $path Path in addons multidimensional array. * May be 'category' or 'form_builder.category' or 'settings_integrations.category', etc. * @param string $value Addons multidimensional array value we are looking for in the path. * * @return array */ public function get_by_path( string $path, $value ): array { return $this->get_filtered( $this->get_available(), [ $path => $value ] ); } /** * Get available addons data by license. * * @since 1.6.6 * * @param string $license Addon license. * * @return array. * @noinspection PhpUnused */ public function get_by_license( string $license ) { return $this->get_filtered( $this->get_available(), [ 'license' => $license ] ); } /** * Get available addons data by slugs. * * @since 1.6.8 * * @param array|mixed $slugs Addon slugs. * * @return array */ public function get_by_slugs( $slugs ) { if ( empty( $slugs ) || ! is_array( $slugs ) ) { return []; } $result_addons = []; foreach ( $slugs as $slug ) { $addon = $this->get_addon( $slug ); if ( ! empty( $addon ) ) { $result_addons[] = $addon; } } return $result_addons; } /** * Get available addon data by slug. * * @since 1.6.6 * * @param string|bool $slug Addon slug can be both "wpforms-drip" and "drip". * * @return array Single addon data. Empty array if addon is not found. */ public function get_addon( $slug ) { $slug = (string) $slug; $slug = 'wpforms-' . str_replace( 'wpforms-', '', sanitize_key( $slug ) ); $addon = $this->get_available()[ $slug ] ?? []; // In case if addon is "not available" let's try to get and prepare addon data from all addons. if ( empty( $addon ) ) { $addon = ! empty( $this->addons[ $slug ] ) ? $this->prepare_addon_data( $this->addons[ $slug ] ) : []; } return $addon; } /** * Check if addon is active. * * @since 1.8.9 * * @param string $slug Addon slug. * * @return bool */ public function is_active( string $slug ): bool { $addon = $this->get_addon( $slug ); return isset( $addon['status'] ) && $addon['status'] === 'active'; } /** * Return the download URL for an addon. * * Lite has no license and therefore no addon download URLs, so the base * implementation always returns an empty string. The Pro subclass overrides this * with the license-keyed URL resolved from the WPForms.com API. * * @since 2.0.0 * * @param string $slug Addon slug. * * @return string */ public function get_url( string $slug ): string { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found return ''; } /** * Get license level of the addon. * * @since 1.6.6 * * @param array|string $addon Addon data array OR addon slug. * * @return string License level: pro | elite. */ private function get_license_level( $addon ) { if ( empty( $addon ) ) { return ''; } $levels = [ self::BASIC, self::PLUS, self::PRO, self::ELITE, self::AGENCY, self::ULTIMATE ]; $license = ''; $addon_license = $this->get_addon_license( $addon ); foreach ( $levels as $level ) { if ( in_array( $level, $addon_license, true ) ) { $license = $level; break; } } if ( empty( $license ) ) { return ''; } return in_array( $license, [ self::BASIC, self::PLUS, self::PRO ], true ) ? self::PRO : self::ELITE; } /** * Get addon license. * * @since 1.8.2 * * @param array|string $addon Addon data array OR addon slug. * * @return array */ private function get_addon_license( $addon ) { $addon = is_string( $addon ) ? $this->get_addon( $addon ) : $addon; return $this->default_data( $addon, 'license', [] ); } /** * Determine if a user's license level has access. * * @since 1.6.6 * * @param array|string $addon Addon data array OR addon slug. * * @return bool */ protected function has_access( $addon ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found return false; } /** * Return array of addons available to display. All data is prepared and normalized. * "Available to display" means that addon needs to be displayed as an education item (addon is not installed or not activated). * * @since 1.6.6 * * @return array */ public function get_available() { static $available_addons = []; if ( $available_addons ) { return $available_addons; } if ( empty( $this->addons ) || ! is_array( $this->addons ) ) { return []; } $available_addons = array_map( [ $this, 'prepare_addon_data' ], $this->addons ); $available_addons = array_filter( $available_addons, static function ( $addon ) { return isset( $addon['status'], $addon['plugin_allow'] ) && ( $addon['status'] !== 'active' || ! $addon['plugin_allow'] ); } ); return $available_addons; } /** * Prepare addon data. * * @since 1.6.6 * * @param array|mixed $addon Addon data. * * @return array Extended addon data. */ protected function prepare_addon_data( $addon ) { if ( empty( $addon ) ) { return []; } $addon['title'] = $this->default_data( $addon, 'title', '' ); $addon['slug'] = $this->default_data( $addon, 'slug', '' ); // We need the cleared name of the addon, without the 'addon' suffix, for further use. $addon['name'] = preg_replace( '/ addon$/i', '', $addon['title'] ); $addon['modal_name'] = sprintf( /* translators: %s - addon name. */ esc_html__( '%s addon', 'wpforms-lite' ), $addon['name'] ); $addon['clear_slug'] = str_replace( 'wpforms-', '', $addon['slug'] ); $addon['utm_content'] = ucwords( str_replace( '-', ' ', $addon['clear_slug'] ) ); $addon['license'] = $this->default_data( $addon, 'license', [] ); $addon['license_level'] = $this->get_license_level( $addon ); $addon['icon'] = $this->default_data( $addon, 'icon', '' ); $addon['path'] = sprintf( '%1$s/%1$s.php', $addon['slug'] ); $addon['video'] = $this->default_data( $addon, 'video', '' ); $addon['plugin_allow'] = $this->has_access( $addon ); $addon['status'] = 'missing'; $addon['action'] = 'upgrade'; $addon['page_url'] = $this->default_data( $addon, 'url', '' ); $addon['doc_url'] = $this->default_data( $addon, 'doc', '' ); $addon['url'] = ''; static $nonce = ''; $nonce = empty( $nonce ) ? wp_create_nonce( 'wpforms-admin' ) : $nonce; $addon['nonce'] = $nonce; return $addon; } /** * Get default data. * * @since 1.8.2 * * @param array|mixed $addon Addon data. * @param string $key Key. * @param mixed $default_data Default data. * * @return array|string|mixed */ private function default_data( $addon, string $key, $default_data ) { if ( is_string( $default_data ) ) { return ! empty( $addon[ $key ] ) ? $addon[ $key ] : $default_data; } if ( is_array( $default_data ) ) { return ! empty( $addon[ $key ] ) ? (array) $addon[ $key ] : $default_data; } return $addon[ $key ] ?? ''; } /** * Populate addons data. * * @since 1.9.2 * * @return void */ private function populate_addons_data() { foreach ( $this->addons as $addon ) { $this->addons_text_domains[] = $addon['slug']; $this->addons_titles[] = 'WPForms ' . str_replace( ' Addon', '', $addon['title'] ); } } /** * Filter Gettext. * * This filter allows us to prevent empty translations from being returned * on the `plugins` page for addon name and description. * * @since 1.9.2 * * @param string|mixed $translation Translated text. * @param string|mixed $text Text to translate. * @param string|mixed $domain Text domain. * * @return string Translated text. */ public function filter_gettext( $translation, $text, $domain ): string { $translation = (string) $translation; $text = (string) $text; $domain = (string) $domain; if ( ! in_array( $domain, $this->addons_text_domains, true ) ) { return $translation; } // Prevent empty translations from being returned and don't translate addon names. if ( ! trim( $translation ) || in_array( $text, $this->addons_titles, true ) ) { $translation = $text; } return $translation; } } FormEmbedWizard.php 0000644 00000040506 15252506741 0010312 0 ustar 00 <?php namespace WPForms\Admin; use WPForms\Admin\Builder\PreviewDropdownEducationItems; use WPForms_Builder; use WP_Post; /** * Embed Form in a Page wizard. * * @since 1.6.2 */ class FormEmbedWizard { /** * Max search results count of 'Select Page' dropdown. * * @since 1.7.9 * * @var int */ const MAX_SEARCH_RESULTS_DROPDOWN_PAGES_COUNT = 20; /** * Post statuses of pages in 'Select Page' dropdown. * * @since 1.7.9 * * @var string[] */ const POST_STATUSES_OF_DROPDOWN_PAGES = [ 'publish', 'pending' ]; /** * Initialize class. * * @since 1.6.2 */ public function init() { // Form Embed Wizard should load only in the Form Builder and on the Edit/Add Page screen. if ( ! wpforms_is_admin_page( 'builder' ) && ! wpforms_is_admin_ajax() && ! $this->is_form_embed_page() ) { return; } $this->hooks(); } /** * Register hooks. * * @since 1.6.2 * @since 1.7.9 Add hook for searching pages in embed wizard via AJAX. */ public function hooks() { add_action( 'admin_enqueue_scripts', [ $this, 'enqueues' ] ); add_action( 'admin_footer', [ $this, 'output' ] ); add_filter( 'default_title', [ $this, 'embed_page_title' ], 10, 2 ); add_filter( 'default_content', [ $this, 'embed_page_content' ], 10, 2 ); add_action( 'wp_ajax_wpforms_admin_form_embed_wizard_embed_page_url', [ $this, 'get_embed_page_url_ajax' ] ); add_action( 'wp_ajax_wpforms_admin_form_embed_wizard_search_pages_choicesjs', [ $this, 'get_search_result_pages_ajax' ] ); } /** * Enqueue assets. * * @since 1.6.2 * @since 1.7.9 Add 'underscore' as dependency. */ public function enqueues() { $min = wpforms_get_min_suffix(); if ( $this->is_form_embed_page() && $this->get_meta() && ! $this->is_challenge_active() ) { wp_enqueue_style( 'wpforms-admin-form-embed-wizard', WPFORMS_PLUGIN_URL . "assets/css/form-embed-wizard{$min}.css", [], WPFORMS_VERSION ); wp_enqueue_style( 'tooltipster', WPFORMS_PLUGIN_URL . 'assets/lib/jquery.tooltipster/jquery.tooltipster.min.css', null, '4.2.6' ); wp_enqueue_script( 'tooltipster', WPFORMS_PLUGIN_URL . 'assets/lib/jquery.tooltipster/jquery.tooltipster.min.js', [ 'jquery' ], '4.2.6', true ); } // In the Form Builder the education cards delegate their "open preview" // and "jump to settings section" actions to the Preview dropdown module, // so we declare it as an explicit dependency there. $deps = [ 'jquery', 'underscore' ]; if ( wpforms_is_admin_page( 'builder' ) ) { $deps[] = 'wpforms-builder-preview-dropdown'; } wp_enqueue_script( 'wpforms-admin-form-embed-wizard', WPFORMS_PLUGIN_URL . "assets/js/admin/form-embed-wizard{$min}.js", $deps, WPFORMS_VERSION, false ); wp_localize_script( 'wpforms-admin-form-embed-wizard', 'wpforms_admin_form_embed_wizard', [ 'nonce' => wp_create_nonce( 'wpforms_admin_form_embed_wizard_nonce' ), 'is_edit_page' => (int) $this->is_form_embed_page( 'edit' ), 'i18n' => [ 'add_to_page' => __( 'Add to Page', 'wpforms-lite' ), 'create_page' => __( 'Create Page', 'wpforms-lite' ), ], ] ); } /** * Output HTML. * * @since 1.6.2 */ public function output() { // We don't need to output tooltip if Challenge is active. if ( $this->is_form_embed_page() && $this->is_challenge_active() ) { $this->delete_meta(); return; } // We don't need to output tooltip if it's not an embed flow. if ( $this->is_form_embed_page() && ! $this->get_meta() ) { return; } $template = $this->is_form_embed_page() ? 'admin/form-embed-wizard/tooltip' : 'admin/form-embed-wizard/popup'; $args = []; if ( ! $this->is_form_embed_page() ) { $args['user_can_edit_pages'] = current_user_can( 'edit_pages' ); $args['dropdown_pages'] = $this->get_select_dropdown_pages_html(); $args['education_items'] = $this->get_education_items(); $args['is_lite'] = ! wpforms()->is_pro(); } // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wpforms_render( $template, $args ); $this->delete_meta(); } /** * Check if Challenge is active. * * @since 1.6.4 * * @return boolean */ public function is_challenge_active() { static $challenge_active = null; if ( $challenge_active === null ) { $challenge = wpforms()->obj( 'challenge' ); $challenge_active = method_exists( $challenge, 'challenge_active' ) ? $challenge->challenge_active() : false; } return $challenge_active; } /** * Check if the current page is a form embed page. * * @since 1.6.2 * * @param string $type Type of the embed page to check. Can be '', 'add' or 'edit'. By default is empty string. * * @return boolean */ public function is_form_embed_page( $type = '' ) { global $pagenow; $type = $type === 'add' || $type === 'edit' ? $type : ''; if ( $pagenow !== 'post.php' && $pagenow !== 'post-new.php' ) { return false; } // phpcs:disable WordPress.Security.NonceVerification.Recommended $post_id = empty( $_GET['post'] ) ? 0 : (int) $_GET['post']; $post_type = empty( $_GET['post_type'] ) ? '' : sanitize_key( $_GET['post_type'] ); $action = empty( $_GET['action'] ) ? 'add' : sanitize_key( $_GET['action'] ); // phpcs:enable if ( $pagenow === 'post-new.php' && ( empty( $post_type ) || $post_type !== 'page' ) ) { return false; } if ( $pagenow === 'post.php' && ( empty( $post_id ) || get_post_type( $post_id ) !== 'page' ) ) { return false; } $meta = $this->get_meta(); $embed_page = ! empty( $meta['embed_page'] ) ? (int) $meta['embed_page'] : 0; if ( 'add' === $action && 0 === $embed_page && $type !== 'edit' ) { return true; } if ( ! empty( $post_id ) && $embed_page === $post_id && $type !== 'add' ) { return true; } return false; } /** * Set user's embed meta data. * * @since 1.6.2 * * @param array $data Data array to set. */ public function set_meta( $data ) { update_user_meta( get_current_user_id(), 'wpforms_admin_form_embed_wizard', $data ); } /** * Get user's embed meta data. * * @since 1.6.2 * * @return array User's embed meta data. */ public function get_meta() { return get_user_meta( get_current_user_id(), 'wpforms_admin_form_embed_wizard', true ); } /** * Delete user's embed meta data. * * @since 1.6.2 */ public function delete_meta() { delete_user_meta( get_current_user_id(), 'wpforms_admin_form_embed_wizard' ); } /** * Get embed page URL via AJAX. * * @since 1.6.2 */ public function get_embed_page_url_ajax() { // Run a security check. check_admin_referer( 'wpforms_admin_form_embed_wizard_nonce' ); // Check for permissions. if ( ! wpforms_current_user_can( 'edit_forms' ) ) { wp_send_json_error( esc_html__( 'You are not allowed to perform this action.', 'wpforms-lite' ) ); } $page_id = ! empty( $_POST['pageId'] ) ? absint( $_POST['pageId'] ) : 0; $meta = $this->prepare_meta_data( $page_id ); $this->set_meta( $meta ); // Update challenge option to properly continue challenge on the embed page. $this->update_challenge_option( $meta ); wp_send_json_success( $meta['url'] ); } /** * Prepare meta data for the embed page. * * @since 1.9.4 * * @param int $page_id Page ID. * * @return array */ private function prepare_meta_data( int $page_id ): array { if ( ! empty( $page_id ) ) { $url = get_edit_post_link( $page_id, '' ); $meta = [ 'embed_page' => $page_id, ]; } else { $url = add_query_arg( 'post_type', 'page', admin_url( 'post-new.php' ) ); $meta = [ 'embed_page' => 0, 'embed_page_title' => ! empty( $_POST['pageTitle'] ) ? sanitize_text_field( wp_unslash( $_POST['pageTitle'] ) ) : '', // phpcs:ignore WordPress.Security.NonceVerification.Missing ]; } $meta['form_id'] = ! empty( $_POST['formId'] ) ? absint( $_POST['formId'] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing $meta['url'] = $url; return $meta; } /** * Update challenge option to properly continue challenge on the embed page. * * @since 1.9.4 * * @param array $meta Meta data. */ private function update_challenge_option( array $meta ): void { if ( $this->is_challenge_active() ) { $challenge = wpforms()->obj( 'challenge' ); if ( $challenge && method_exists( $challenge, 'set_challenge_option' ) ) { $challenge->set_challenge_option( [ 'embed_page' => $meta['embed_page'] ] ); } } } /** * Set default title for the new page. * * @since 1.6.2 * * @param string $post_title Default post title. * @param \WP_Post $post Post object. * * @return string New default post title. */ public function embed_page_title( $post_title, $post ) { $meta = $this->get_meta(); $this->delete_meta(); return empty( $meta['embed_page_title'] ) ? $post_title : $meta['embed_page_title']; } /** * Embed the form to the new page. * * @since 1.6.2 * * @param string $post_content Default post content. * @param \WP_Post $post Post object. * * @return string Embedding string (shortcode or GB component code). */ public function embed_page_content( $post_content, $post ) { $meta = $this->get_meta(); $form_id = ! empty( $meta['form_id'] ) ? $meta['form_id'] : 0; $page_id = ! empty( $meta['embed_page'] ) ? $meta['embed_page'] : 0; if ( ! empty( $page_id ) || empty( $form_id ) ) { return $post_content; } if ( wpforms_is_gutenberg_active() ) { $pattern = '<!-- wp:wpforms/form-selector {"formId":"%d"} /-->'; } else { $pattern = '[wpforms id="%d" title="false" description="false"]'; } return sprintf( $pattern, absint( $form_id ) ); } /** * Get product education items shown in the embed wizard card grid. * * Reuses the canonical item list from `PreviewDropdownEducationItems` * and enriches each item with wizard-specific UTM content, demo URL, * card-body attributes, and (for Lite users) the upgrade URL. * * @since 1.10.1 * * @return array */ private function get_education_items(): array { $items = ( new PreviewDropdownEducationItems( $this->get_current_form() ) )->get_items(); if ( empty( $items ) ) { return []; } // UTM content mapping keyed by item slug. $utm_map = [ 'conversational-forms' => [ 'upgrade' => 'Conversational Forms - Upgrade to Pro Link', 'demo' => 'Conversational Forms - Demo Link', ], 'form-pages' => [ 'upgrade' => 'Form Pages - Upgrade to Pro Link', 'demo' => 'Form Pages - Demo Link', ], 'lead-forms' => [ 'upgrade' => 'Lead Forms - Upgrade to Pro Link', 'demo' => 'Lead Forms - Demo Link', ], ]; $is_lite = ! wpforms()->is_pro(); foreach ( $items as &$item ) { $slug_utm = $utm_map[ $item['slug'] ] ?? []; // Override UTM medium and content for the embed wizard context. $item['utm_medium'] = 'Builder Embed Modal'; $item['utm_content'] = $slug_utm['upgrade'] ?? $item['utm_content']; $item['demo_utm_content'] = $slug_utm['demo'] ?? ( $item['demo_utm_content'] ?? $item['utm_content'] ); $item['demo_url'] = PreviewDropdownEducationItems::get_demo_url( $item ); $item['button_attrs'] = $this->get_education_item_attrs( $item, $is_lite ); if ( $is_lite ) { $item['upgrade_url'] = wpforms_admin_upgrade_link( 'Builder Embed Modal', $item['utm_content'] ); } } unset( $item ); return $items; } /** * Build the CSS class and data attributes for an education card body. * * Mirrors the Preview dropdown's `print_preview_dropdown_item()` logic so * clicks on the card body are handled by the same JS flows already wired * to the Preview dropdown: education upgrade modal (Lite), Addons Required * modal (Pro without addon), or "active addon" preview handler (Pro with * addon installed and active). * * @since 1.10.1 * * @param array $item Education item. * @param bool $is_lite Whether the current user is on Lite. * * @return array { * Card body rendering data. * * @type string $class CSS class for the card body. * @type array $data Associative array of `data-*` attributes (name => value). * } */ private function get_education_item_attrs( array $item, bool $is_lite ): array { if ( $is_lite ) { // Lite user — card click opens the pricing page directly, // bypassing the standard education upgrade modal. return [ 'class' => 'wpforms-admin-form-embed-wizard-card-education-lite', 'data' => [ 'name' => $item['label'], 'utm-content' => $item['utm_content'], 'upgrade-url' => wpforms_admin_upgrade_link( 'Builder Embed Modal', $item['utm_content'] ), ], ]; } $addon_slug = $item['addon_slug'] ?? ''; if ( PreviewDropdownEducationItems::is_addon_active( $addon_slug ) ) { // Addon is installed and active — open preview URL in a new tab, // scoped to the addon section via the `section` query param. $preview_url = $item['preview_url'] ?? ''; return [ 'class' => 'wpforms-admin-form-embed-wizard-card-education-active', 'data' => [ 'section' => $item['section'] ?? '', 'toggle-id' => $item['toggle_id'] ?? '', 'preview-url' => $preview_url, ], ]; } // Pro user without the addon installed or active — open the // Addons Required modal (same handler as the Preview dropdown). return [ 'class' => 'wpforms-addons-required-trigger', 'data' => [ 'license' => 'pro', 'name' => $item['label'], 'utm-content' => $item['utm_content'], 'demo-url' => $item['demo_url'], 'addon-slug' => $addon_slug, ], ]; } /** * Resolve the current builder form from the Builder singleton. * * @since 1.10.1 * * @return WP_Post|null */ private function get_current_form(): ?WP_Post { if ( ! class_exists( WPForms_Builder::class ) ) { return null; } $form = WPForms_Builder::instance()->form ?? null; return $form instanceof WP_Post ? $form : null; } /** * Generate select with pages which are available to edit for current user. * * @since 1.6.6 * @since 1.7.9 Refactor to use ChoicesJS instead of `wp_dropdown_pages()`. * * @return string */ private function get_select_dropdown_pages_html() { $dropdown_pages = wpforms_search_posts( '', [ 'count' => self::MAX_SEARCH_RESULTS_DROPDOWN_PAGES_COUNT, 'post_status' => self::POST_STATUSES_OF_DROPDOWN_PAGES, ] ); if ( empty( $dropdown_pages ) ) { return ''; } $total_pages = 0; $wp_count_pages = (array) wp_count_posts( 'page' ); foreach ( $wp_count_pages as $page_status => $pages_count ) { if ( in_array( $page_status, self::POST_STATUSES_OF_DROPDOWN_PAGES, true ) ) { $total_pages += $pages_count; } } // Include so we can use `\wpforms_settings_select_callback()`. require_once WPFORMS_PLUGIN_DIR . 'includes/admin/settings-api.php'; $options = [ '' => esc_html__( 'Search for a page', 'wpforms-lite' ) ] + wp_list_pluck( $dropdown_pages, 'post_title', 'ID' ); return wpforms_settings_select_callback( [ 'id' => 'form-embed-wizard-choicesjs-select-pages', 'type' => 'select', 'choicesjs' => true, 'options' => $options, 'data' => [ 'use_ajax' => $total_pages > self::MAX_SEARCH_RESULTS_DROPDOWN_PAGES_COUNT, ], ] ); } /** * Get search result pages for ChoicesJS via AJAX. * * @since 1.7.9 */ public function get_search_result_pages_ajax() { // Run a security check. if ( ! check_ajax_referer( 'wpforms_admin_form_embed_wizard_nonce', false, false ) ) { wp_send_json_error( [ 'msg' => esc_html__( 'Your session expired. Please reload the builder.', 'wpforms-lite' ), ] ); } // Check for permissions. if ( ! wpforms_current_user_can( 'edit_forms' ) ) { wp_send_json_error( esc_html__( 'You are not allowed to perform this action.', 'wpforms-lite' ) ); } if ( ! array_key_exists( 'search', $_GET ) ) { wp_send_json_error( [ 'msg' => esc_html__( 'Incorrect usage of this operation.', 'wpforms-lite' ), ] ); } $result_pages = wpforms_search_pages_for_dropdown( sanitize_text_field( wp_unslash( $_GET['search'] ) ), [ 'count' => self::MAX_SEARCH_RESULTS_DROPDOWN_PAGES_COUNT, 'post_status' => self::POST_STATUSES_OF_DROPDOWN_PAGES, ] ); if ( empty( $result_pages ) ) { wp_send_json_error( [] ); } wp_send_json_success( $result_pages ); } } Pages/Community.php 0000644 00000014253 15252506741 0010314 0 ustar 00 <?php namespace WPForms\Admin\Pages; /** * Community Sub-page. * * @since 1.5.6 */ class Community { /** * Admin menu page slug. * * @since 1.5.6 * * @var string */ const SLUG = 'wpforms-community'; /** * Constructor. * * @since 1.5.6 */ public function __construct() { if ( \wpforms_current_user_can() ) { $this->hooks(); } } /** * Hooks. * * @since 1.5.6 */ public function hooks() { // Check what page we are on. // phpcs:ignore WordPress.Security.NonceVerification.Recommended $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : ''; // Only load if we are actually on the Community page. if ( self::SLUG !== $page ) { return; } add_action( 'wpforms_admin_page', [ $this, 'output' ] ); // Hook for addons. do_action( 'wpforms_admin_community_init' ); } /** * Page data. * * @since 1.5.6 */ public function get_blocks_data() { $type = wpforms()->is_pro() ? 'plugin' : 'liteplugin'; $data = []; $data['vip_circle'] = [ 'title' => esc_html__( 'WPForms VIP Circle Facebook Group', 'wpforms-lite' ), 'description' => esc_html__( 'Powered by the community, for the community. Anything and everything WPForms: Discussions. Questions. Tutorials. Insights and sneak peaks. Also, exclusive giveaways!', 'wpforms-lite' ), 'button_text' => esc_html__( 'Join WPForms VIP Circle', 'wpforms-lite' ), 'button_link' => 'https://www.facebook.com/groups/wpformsvip/', 'cover_bg_color' => '#E4F0F6', 'cover_img' => 'vip-circle.png', 'cover_img2x' => 'vip-circle@2x.png', ]; $data['announcements'] = [ 'title' => esc_html__( 'WPForms Announcements', 'wpforms-lite' ), 'description' => esc_html__( 'Check out the latest releases from WPForms. Our team is always innovating to bring you powerful features and functionality that are simple to use. Every release is designed with you in mind!', 'wpforms-lite' ), 'button_text' => esc_html__( 'View WPForms Announcements', 'wpforms-lite' ), 'button_link' => 'https://wpforms.com/blog/?utm_source=WordPress&utm_medium=Community&utm_campaign=' . esc_attr( $type ) . '&utm_content=Announcements', 'cover_bg_color' => '#EFF8E9', 'cover_img' => 'announcements.png', 'cover_img2x' => 'announcements@2x.png', ]; $data['youtube'] = [ 'title' => esc_html__( 'WPForms YouTube Channel', 'wpforms-lite' ), 'description' => esc_html__( 'Take a visual dive into everything WPForms has to offer. From simple contact forms to advanced payment forms and email marketing integrations, our extensive video collection covers it all.', 'wpforms-lite' ), 'button_text' => esc_html__( 'Visit WPForms YouTube Channel', 'wpforms-lite' ), 'button_link' => 'https://www.youtube.com/c/wpformsplugin', 'cover_bg_color' => '#FFE6E6', 'cover_img' => 'youtube.png', 'cover_img2x' => 'youtube@2x.png', ]; $data['dev_docs'] = [ 'title' => esc_html__( 'WPForms Developer Documentation', 'wpforms-lite' ), 'description' => esc_html__( 'Customize and extend WPForms with code. Our comprehensive developer resources include tutorials, snippets, and documentation on core actions, filters, functions, and more.', 'wpforms-lite' ), 'button_text' => esc_html__( 'View WPForms Dev Docs', 'wpforms-lite' ), 'button_link' => 'https://wpforms.com/developers/?utm_source=WordPress&utm_medium=Community&utm_campaign=' . esc_attr( $type ) . '&utm_content=Developers', 'cover_bg_color' => '#EBEBEB', 'cover_img' => 'dev-docs.png', 'cover_img2x' => 'dev-docs@2x.png', ]; $data['wpbeginner'] = [ 'title' => esc_html__( 'WPBeginner Engage Facebook Group', 'wpforms-lite' ), 'description' => esc_html__( 'Hang out with other WordPress experts and like minded website owners such as yourself! Hosted by WPBeginner, the largest free WordPress site for beginners.', 'wpforms-lite' ), 'button_text' => esc_html__( 'Join WPBeginner Engage', 'wpforms-lite' ), 'button_link' => 'https://www.facebook.com/groups/wpbeginner/', 'cover_bg_color' => '#FCEDE4', 'cover_img' => 'wpbeginner.png', 'cover_img2x' => 'wpbeginner@2x.png', ]; $data['suggest'] = [ 'title' => esc_html__( 'Suggest a Feature', 'wpforms-lite' ), 'description' => esc_html__( 'Do you have an idea or suggestion for WPForms? If you have thoughts on features, integrations, addons, or improvements - we want to hear it! We appreciate all feedback and insight from our users.', 'wpforms-lite' ), 'button_text' => esc_html__( 'Suggest a Feature', 'wpforms-lite' ), 'button_link' => 'https://wpforms.com/features/suggest/?utm_source=WordPress&utm_medium=Community&utm_campaign=' . esc_attr( $type ) . '&utm_content=Feature', 'cover_bg_color' => '#FFF9EF', 'cover_img' => 'suggest.png', 'cover_img2x' => 'suggest@2x.png', ]; return $data; } /** * Generate and output page HTML. * * @since 1.5.6 */ public function output() { ?> <div id="wpforms-admin-community" class="wrap wpforms-admin-wrap"> <h1 class="page-title"><?php esc_html_e( 'Community', 'wpforms-lite' ); ?></h1> <div class="items"> <?php $data = $this->get_blocks_data(); foreach ( $data as $item ) { printf( '<div class="item"> <a href="%6$s" target="_blank" rel="noopener noreferrer" class="item-cover" style="background-color: %s;" title="%4$s"><img class="item-img" src="%s" srcset="%s 2x" alt="%4$s"/></a> <h3 class="item-title">%s</h3> <p class="item-description">%s</p> <div class="item-footer"> <a class="wpforms-btn button-primary wpforms-btn-blue" href="%s" target="_blank" rel="noopener noreferrer">%s</a> </div> </div>', esc_attr( $item['cover_bg_color'] ), esc_url( WPFORMS_PLUGIN_URL . 'assets/images/community/' . $item['cover_img'] ), esc_url( WPFORMS_PLUGIN_URL . 'assets/images/community/' . $item['cover_img2x'] ), esc_html( $item['title'] ), esc_html( $item['description'] ), esc_url( $item['button_link'] ), esc_html( $item['button_text'] ) ); } ?> </div> </div> <?php } } Pages/SugarCalendar.php 0000644 00000026325 15252506741 0011046 0 ustar 00 <?php namespace WPForms\Admin\Pages; /** * Sugar Calendar Subpage. * * @since 1.9.8.6 */ class SugarCalendar extends Page { /** * Admin menu page slug. * * @since 1.9.8.6 * * @var string */ public const SLUG = 'wpforms-sugar-calendar'; /** * Configuration. * * @since 1.9.8.6 * * @var array */ protected $config = [ 'lite_plugin' => 'sugar-calendar-lite/sugar-calendar-lite.php', 'lite_wporg_url' => 'https://wordpress.org/plugins/sugar-calendar-lite/', 'lite_download_url' => 'https://downloads.wordpress.org/plugin/sugar-calendar-lite.zip', 'pro_plugin' => 'sugar-calendar/sugar-calendar.php', 'sugar-calendar_addon' => 'sugar-calendar/sugar-calendar.php', 'sugar-calendar_addon_page' => 'https://sugarcalendar.com/?utm_source=wpformsplugin&utm_medium=link&utm_campaign=sugar-calendar-page', 'sugar-calendar_onboarding' => 'post-new.php?post_type=sc_event', ]; /** * Hooks. * * @since 1.9.8.6 */ public function hooks(): void { if ( wp_doing_ajax() ) { add_filter( 'default_option_sugar_calendar_prevent_redirect', '__return_true' ); } parent::hooks(); } /** * Get the plugin name for use in IDs, CSS classes, and config keys. * * @since 1.9.8.6 * * @return string Plugin name. */ protected static function get_plugin_name(): string { return 'sugar-calendar'; } /** * Get heading title text. * * @since 1.9.8.6 * * @return string Heading title. */ protected function get_heading_title(): string { return esc_html__( 'Taking Bookings? Put Them on a Calendar', 'wpforms-lite' ); } /** * Get heading alt text for logo. * * @since 1.9.8.6 * * @return string Heading alt text. */ protected function get_heading_alt_text(): string { return esc_attr__( 'WPForms ♥ Sugar Calendar', 'wpforms-lite' ); } /** * Get heading description strings. * * @since 1.9.8.6 * * @return array Array of description strings. */ protected function get_heading_strings(): array { return [ esc_html__( 'WPForms collects the "yes." Sugar Calendar shows the "when and where."', 'wpforms-lite' ), esc_html__( 'Together, they turn bookings into events your visitors can browse, sync, and show up for.', 'wpforms-lite' ), esc_html__( 'Simple, elegant, and built for your workflow.', 'wpforms-lite' ), ]; } /** * Get screenshot features list. * * @since 1.9.8.6 * * @return array Array of feature strings. */ protected function get_screenshot_features(): array { return [ esc_html__( 'Display events on beautiful calendars visitors can browse and filter.', 'wpforms-lite' ), esc_html__( 'Sell tickets with Stripe or WooCommerce integration.', 'wpforms-lite' ), esc_html__( 'Visitors can add events to Google, Apple, or Outlook calendars with one click.', 'wpforms-lite' ), esc_html__( 'Set up recurring events: daily, weekly, monthly, or custom patterns.', 'wpforms-lite' ), ]; } /** * Get screenshot alt text. * * @since 1.9.8.6 * * @return string Alt text for screenshot image. */ protected function get_screenshot_alt_text(): string { return esc_attr__( 'Sugar Calendar screenshot', 'wpforms-lite' ); } /** * Generate and output step 'Result' section HTML. * * @since 1.9.8.6 * * @noinspection HtmlUnknownTarget */ protected function output_section_step_result(): void { $step = $this->get_data_step_result(); if ( empty( $step ) ) { return; } printf( '<section class="step step-result %1$s"> <aside class="num"> <img src="%2$s" alt="%3$s" /> <i class="loader hidden"></i> </aside> <div> <h2>%4$s</h2> <p>%5$s</p> <button class="button %6$s" data-url="%7$s">%8$s</button> </div> </section>', esc_attr( $step['section_class'] ), esc_url( WPFORMS_PLUGIN_URL . 'assets/images/' . $step['icon'] ), esc_attr__( 'Step 3', 'wpforms-lite' ), esc_html__( 'Display Events on Your Site', 'wpforms-lite' ), esc_html__( 'Use the Calendar block or shortcode [sc_events_calendar] to embed events anywhere on your site.', 'wpforms-lite' ), esc_attr( $step['button_class'] ), esc_url( $step['button_url'] ), esc_html( $step['button_text'] ) ); } /** * Generate and output footer section HTML. * * @since 1.9.8.6 */ protected function output_section_footer(): void { printf( '<section class="bottom"> <p>%s</p> </section>', esc_html__( 'From the same team trusted by over 6 million sites.', 'wpforms-lite' ) ); } /** * Step 'Result' data. * * @since 1.9.8.6 * * @return array Step data. */ protected function get_data_step_result(): array { $step = $this->get_default_step_data(); $plugin_license_level = $this->get_plugin_license_level(); if ( $plugin_license_level === 'lite' ) { $this->apply_lite_step_data( $step ); } elseif ( $plugin_license_level === 'pro' ) { $this->apply_pro_step_data( $step ); } return $step; } /** * Whether the plugin is finished setup or not. * * @since 1.9.8.6 */ protected function is_plugin_finished_setup(): bool { if ( ! $this->is_plugin_configured() ) { return false; } return $this->get_plugin_license_level() === 'pro'; } /** * Get default step data. * * @since 1.9.8.6 * * @return array Default step data. */ private function get_default_step_data(): array { return [ 'icon' => 'step-3.svg', 'section_class' => $this->output_data['plugin_setup'] ? '' : 'grey', 'button_text' => esc_html__( 'Learn More', 'wpforms-lite' ), 'button_class' => 'grey disabled', 'button_url' => '', ]; } /** * Get plugin license level. * * @since 1.9.8.6 * * @return string License level ('lite', 'pro') or false if not activated. */ private function get_plugin_license_level(): string { if ( ! function_exists( 'sugar_calendar' ) ) { return 'lite'; } $sugar_calendar = sugar_calendar(); return $sugar_calendar->__get( 'is_pro' ) ? 'pro' : 'lite'; } /** * Apply lite version step data. * * @since 1.9.8.6 * * @param array $step Step data array (passed by reference). */ private function apply_lite_step_data( array &$step ): void { $step['button_url'] = $this->config['sugar-calendar_addon_page']; $step['button_class'] = $this->output_data['plugin_setup'] ? 'button-primary' : 'grey disabled'; } /** * Apply pro version step data. * * @since 1.9.8.6 * * @param array $step Step data array (passed by reference). */ private function apply_pro_step_data( array &$step ): void { $addon_installed = array_key_exists( $this->config['sugar-calendar_addon'], $this->output_data['all_plugins'] ); $configured = $this->is_plugin_configured(); $step['button_text'] = $addon_installed && $configured ? esc_html__( 'Sugar Calendar Pro Installed & Activated', 'wpforms-lite' ) : esc_html__( 'Install Now', 'wpforms-lite' ); $step['button_class'] = $this->output_data['plugin_setup'] || ! $configured ? 'grey disabled' : 'button-primary'; $step['icon'] = $addon_installed && $configured ? 'step-complete.svg' : 'step-3.svg'; } /** * Whether a plugin is configured or not. * * @since 1.9.8.6 * * @return bool True if plugin is configured properly. */ protected function is_plugin_configured(): bool { if ( ! $this->is_plugin_activated() ) { return false; } $events = get_posts( [ 'post_type' => 'sc_event', 'post_status' => 'any', 'posts_per_page' => 1, 'fields' => 'ids', ] ); return ! empty( $events ); } /** * Whether a plugin is active or not. * * @since 1.9.8.6 * * @return bool True if the plugin is active. */ protected function is_plugin_activated(): bool { return ( ( function_exists( 'sugar_calendar' ) || class_exists( 'Sugar_Calendar\Plugin' ) ) && ( is_plugin_active( $this->config['lite_plugin'] ) || is_plugin_active( $this->config['pro_plugin'] ) ) ); } /** * Whether a plugin is available (class/function exists). * * @since 1.9.8.6 * * @return bool True if plugin is available. */ protected function is_plugin_available(): bool { return class_exists( 'Sugar_Calendar\Plugin' ) || function_exists( 'sugar_calendar' ); } /** * Whether pro version is active. * * @since 1.9.8.6 * * @return bool True if pro version is active. */ protected function is_pro_active(): bool { if ( ! function_exists( 'sugar_calendar' ) ) { return false; } return sugar_calendar()->is_pro(); } /** * Get the heading for the install step. * * @since 1.9.8.6 * * @return string Install step heading. */ protected function get_install_heading(): string { return esc_html__( 'Install and Activate Sugar Calendar', 'wpforms-lite' ); } /** * Get the description for the install step. * * @since 1.9.8.6 * * @return string Install step description. */ protected function get_install_description(): string { return esc_html__( 'Bring your forms to life. Install Sugar Calendar and start creating events.', 'wpforms-lite' ); } /** * Get the plugin title. * * @since 1.9.8.6 * * @return string Plugin title. */ protected function get_plugin_title(): string { return esc_html__( 'Sugar Calendar', 'wpforms-lite' ); } /** * Get the install button text. * * @since 1.9.8.6 * * @return string Install button text. */ protected function get_install_button_text(): string { return esc_html__( 'Install Sugar Calendar', 'wpforms-lite' ); } /** * Get the text when a plugin is installed and activated. * * @since 1.9.8.6 * * @return string Installed & activated text. */ protected function get_installed_activated_text(): string { return esc_html__( 'Sugar Calendar Installed & Activated', 'wpforms-lite' ); } /** * Get the activate button text. * * @since 1.9.8.6 * * @return string Activate button text. */ protected function get_activate_text(): string { return esc_html__( 'Activate Sugar Calendar', 'wpforms-lite' ); } /** * Get the heading for the setup step. * * @since 1.9.8.6 * * @return string Setup step heading. */ protected function get_setup_heading(): string { return esc_html__( 'Create Your First Event', 'wpforms-lite' ); } /** * Get the description for the setup step. * * @since 1.9.8.6 * * @return string Setup step description. */ protected function get_setup_description(): string { return esc_html__( 'Add your first booking or class to your calendar in seconds. Clean, simple, and built right into WordPress.', 'wpforms-lite' ); } /** * Get the setup button text. * * @since 1.9.8.6 * * @return string Setup button text. */ protected function get_setup_button_text(): string { return esc_html__( 'Add First Event', 'wpforms-lite' ); } /** * Get the text when setup is completed. * * @since 1.9.8.6 * * @return string Setup completed text. */ protected function get_setup_completed_text(): string { return esc_html__( 'Event Created', 'wpforms-lite' ); } /** * Get the text when a pro-version is installed and activated. * * @since 1.9.8.6 * * @return string Pro installed and activated text. */ protected function get_pro_installed_activated_text(): string { return esc_html__( 'Sugar Calendar Pro Installed & Activated', 'wpforms-lite' ); } } Pages/UncannyAutomator.php 0000644 00000026741 15252506741 0011644 0 ustar 00 <?php namespace WPForms\Admin\Pages; use Uncanny_Automator\Automator_Load; use Uncanny_Automator_Pro\Automator_Pro_Load; /** * Uncanny Automator Subpage. * * @since 1.9.8.6 */ class UncannyAutomator extends Page { /** * Admin menu page slug. * * @since 1.9.8.6 * * @var string */ public const SLUG = 'wpforms-uncanny-automator'; /** * Configuration. * * @since 1.9.8.6 * * @var array */ protected $config = [ 'lite_plugin' => 'uncanny-automator/uncanny-automator.php', 'lite_wporg_url' => 'https://wordpress.org/plugins/uncanny-automator/', 'lite_download_url' => 'https://downloads.wordpress.org/plugin/uncanny-automator.zip', 'pro_plugin' => 'uncanny-automator-pro/uncanny-automator-pro.php', 'uncanny-automator_addon' => 'uncanny-automator-pro/uncanny-automator-pro.php', 'uncanny-automator_addon_page' => 'https://automatorplugin.com/?utm_source=wpformsplugin&utm_medium=link&utm_campaign=uncanny-automator-page', 'uncanny-automator_onboarding' => 'post-new.php?post_type=uo-recipe', ]; /** * Get the plugin name for use in IDs, CSS classes, and config keys. * * @since 1.9.8.6 * * @return string Plugin name. */ protected static function get_plugin_name(): string { return 'uncanny-automator'; } /** * Get heading title text. * * @since 1.9.8.6 * * @return string Heading title. */ protected function get_heading_title(): string { return esc_html__( 'Let Your Site Handle the Busywork.', 'wpforms-lite' ); } /** * Get heading alt text for logo. * * @since 1.9.8.6 * * @return string Heading alt text. */ protected function get_heading_alt_text(): string { return esc_attr__( 'WPForms ♥ Uncanny Automator', 'wpforms-lite' ); } /** * Get heading description strings. * * @since 1.9.8.6 * * @return array Array of description strings. */ protected function get_heading_strings(): array { return [ esc_html__( 'Automate tasks, save time, and keep everything running smoothly. Uncanny Automator connects your favorite tools so your site works smarter. No code. No stress.', 'wpforms-lite' ), ]; } /** * Get screenshot features list. * * @since 1.9.8.6 * * @return array Array of feature strings. */ protected function get_screenshot_features(): array { return [ 'Connect 200+ plugins and apps automatically: social media, memberships, courses, WooCommerce, CRMs, team chat, and much more.', 'Create users, assign access, and enroll in courses with no manual work.', 'Build multi-step workflows with delays and conditional logic, no code required.', 'Unlimited automations with no per-task fees.', ]; } /** * Get screenshot alt text. * * @since 1.9.8.6 * * @return string Alt text for screenshot image. */ protected function get_screenshot_alt_text(): string { return esc_attr__( 'Uncanny Automator screenshot', 'wpforms-lite' ); } /** * Generate and output step 'Result' section HTML. * * @since 1.9.8.6 * * @noinspection HtmlUnknownTarget */ protected function output_section_step_result(): void { $step = $this->get_data_step_result(); if ( empty( $step ) ) { return; } printf( '<section class="step step-result %1$s"> <aside class="num"> <img src="%2$s" alt="%3$s" /> <i class="loader hidden"></i> </aside> <div> <h2>%4$s</h2> <p>%5$s</p> <button class="button %6$s" data-url="%7$s">%8$s</button> </div> </section>', esc_attr( $step['section_class'] ), esc_url( WPFORMS_PLUGIN_URL . 'assets/images/' . $step['icon'] ), esc_attr__( 'Step 3', 'wpforms-lite' ), esc_html__( 'Save and Test Your Automation', 'wpforms-lite' ), esc_html__( 'Click Save Recipe, run a test, and watch your workflow run on its own, no code needed.', 'wpforms-lite' ), esc_attr( $step['button_class'] ), esc_url( $step['button_url'] ), esc_html( $step['button_text'] ) ); } /** * Step 'Result' data. * * @since 1.9.8.6 * * @return array Step data. */ protected function get_data_step_result(): array { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh $step = []; $step['icon'] = 'step-3.svg'; $step['section_class'] = $this->output_data['plugin_setup'] ? '' : 'grey'; $step['button_text'] = esc_html__( 'Learn More', 'wpforms-lite' ); $step['button_class'] = 'grey disabled'; $step['button_url'] = ''; $plugin_license_level = $this->get_license_level(); switch ( $plugin_license_level ) { case 'lite': $step['button_url'] = $this->config['uncanny-automator_addon_page']; $step['button_class'] = $this->output_data['plugin_setup'] ? 'button-primary' : 'grey disabled'; break; case 'pro': $addon_installed = array_key_exists( $this->config['uncanny-automator_addon'], $this->output_data['all_plugins'] ); $step['button_text'] = $addon_installed ? esc_html__( 'Uncanny Automator Pro Installed & Activated', 'wpforms-lite' ) : esc_html__( 'Install Now', 'wpforms-lite' ); $step['button_class'] = $this->output_data['plugin_setup'] ? 'grey disabled' : 'button-primary'; $step['icon'] = $addon_installed ? 'step-complete.svg' : 'step-3.svg'; break; } return $step; } /** * Retrieve the license level of the plugin. * * @since 1.9.8.6 * * @return string The plugin license level ('lite' or 'pro'). */ protected function get_license_level(): string { $plugin_license_level = 'lite'; if ( isset( $this->output_data['plugin_activated'] ) ) { // Check if premium features are available. if ( defined( 'AUTOMATOR_PRO_PLUGIN_VERSION' ) || class_exists( Automator_Pro_Load::class ) ) { $plugin_license_level = 'pro'; } } return $plugin_license_level; } /** * Whether the plugin is finished setup or not. * * @since 1.9.8.6 */ protected function is_plugin_finished_setup(): bool { if ( ! $this->is_plugin_configured() ) { return false; } return $this->get_license_level() === 'pro'; } /** * Get the heading for the setup step. * * @since 1.9.8.6 * * @return string Setup step heading. */ protected function get_setup_heading(): string { return esc_html__( 'Create Your First Automation (Recipe)', 'wpforms-lite' ); } /** * Get the description for the setup step. * * @since 1.9.8.6 * * @return string Setup step description. */ protected function get_setup_description(): string { return esc_html__( 'Open the Automator menu, click Add New, choose your trigger (e.g. form submission), and define your action (e.g. send email, update CRM).', 'wpforms-lite' ); } /** * Whether a plugin is configured or not. * * @since 1.9.8.6 * * @return bool True if plugin is configured properly. */ protected function is_plugin_configured(): bool { if ( ! $this->is_plugin_activated() ) { return false; } // Check if Uncanny Automator has been configured with basic settings. // The plugin is considered configured if there are recipes created. global $wpdb; // Check for Uncanny Automator posts (recipes). // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $recipes = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type = %s AND post_status != %s", 'uo-recipe', 'trash' ) ); if ( (int) $recipes > 0 ) { return true; } // Check for basic Automator settings. $automator_settings = get_option( 'uap_automator_settings' ); return ! empty( $automator_settings ); } /** * Whether a plugin is active or not. * * @since 1.9.8.6 * * @return bool True if the plugin is active. */ protected function is_plugin_activated(): bool { return ( ( defined( 'AUTOMATOR_PLUGIN_VERSION' ) || class_exists( Automator_Load::class ) ) && ( is_plugin_active( $this->config['lite_plugin'] ) || is_plugin_active( $this->config['pro_plugin'] ) ) ); } /** * Whether a plugin is available (class/function exists). * * @since 1.9.8.6 * * @return bool True if a plugin is available. */ protected function is_plugin_available(): bool { return function_exists( 'Automator' ) || defined( 'AUTOMATOR_VERSION' ); } /** * Whether a pro-version is active. * * @since 1.9.8.6 * * @return bool True if a pro-version is active. */ protected function is_pro_active(): bool { return class_exists( 'Uncanny_Automator_Pro\Plugin' ) || defined( 'AUTOMATOR_PRO_VERSION' ); } /** * Get the heading for the installation step. * * @since 1.9.8.6 * * @return string Install step heading. */ protected function get_install_heading(): string { return esc_html__( 'Install and Activate Uncanny Automator', 'wpforms-lite' ); } /** * Get the description for the installation step. * * @since 1.9.8.6 * * @return string Install step description. */ protected function get_install_description(): string { return esc_html__( 'Connect Automator and start building automations that save hours every week.', 'wpforms-lite' ); } /** * Get the plugin title. * * @since 1.9.8.6 * * @return string Plugin title. */ protected function get_plugin_title(): string { return esc_html__( 'Uncanny Automator', 'wpforms-lite' ); } /** * Get the installation button text. * * @since 1.9.8.6 * * @return string Install button text. */ protected function get_install_button_text(): string { return esc_html__( 'Install Uncanny Automator', 'wpforms-lite' ); } /** * Get the text when a plugin is installed and activated. * * @since 1.9.8.6 * * @return string Installed & activated text. */ protected function get_installed_activated_text(): string { return esc_html__( 'Uncanny Automator Installed & Activated', 'wpforms-lite' ); } /** * Get the activate button text. * * @since 1.9.8.6 * * @return string Activate button text. */ protected function get_activate_text(): string { return esc_html__( 'Activate Uncanny Automator', 'wpforms-lite' ); } /** * Get the setup button text. * * @since 1.9.8.6 * * @return string Setup button text. */ protected function get_setup_button_text(): string { return esc_html__( 'Create Your First Recipe', 'wpforms-lite' ); } /** * Get the text when setup is completed. * * @since 1.9.8.6 * * @return string Setup completed text. */ protected function get_setup_completed_text(): string { return esc_html__( 'Recipe Created', 'wpforms-lite' ); } /** * Get the text when a pro-version is installed and activated. * * @since 1.9.8.6 * * @return string Pro installed and activated text. */ protected function get_pro_installed_activated_text(): string { return esc_html__( 'Uncanny Automator Pro Installed & Activated', 'wpforms-lite' ); } /** * Set the source of the plugin installation. * * @since 1.9.8.6 * * @param string $plugin_basename The basename of the plugin. */ public function plugin_activated( string $plugin_basename ): void { if ( $plugin_basename !== $this->config['lite_plugin'] ) { return; } $source = wpforms()->is_pro() ? 'WPForms' : 'WPForms Lite'; /** * Rewrite the get_plugin_name() default value. * * Use `uncannyautomator` instead of `uncanny-automator`. * This is necessary for maintaining consistency with the integration and the plugin itself. * * See: src/Integrations/UncannyAutomator/UncannyAutomator.php update_source() method. */ update_option( 'uncannyautomator_source', $source, false ); update_option( 'uncannyautomator_date', time(), false ); } } Pages/Duplicator.php 0000644 00000030072 15252506741 0010433 0 ustar 00 <?php namespace WPForms\Admin\Pages; /** * Duplicator Subpage. * * @since 1.9.8.6 */ class Duplicator extends Page { /** * Admin menu page slug. * * @since 1.9.8.6 * * @var string */ public const SLUG = 'wpforms-duplicator'; /** * Configuration. * * @since 1.9.8.6 * * @var array */ protected $config = [ 'lite_plugin' => 'duplicator/duplicator.php', 'lite_wporg_url' => 'https://wordpress.org/plugins/duplicator/', 'lite_download_url' => 'https://downloads.wordpress.org/plugin/duplicator.zip', 'pro_plugin' => 'duplicator-pro/duplicator-pro.php', 'duplicator_addon' => 'duplicator-pro/duplicator-pro.php', 'duplicator_addon_page' => 'https://duplicator.com/?utm_source=wpformsplugin&utm_medium=link&utm_campaign=duplicator-page', 'duplicator_onboarding' => 'admin.php?page=duplicator', ]; /** * Constructor. * * @since 1.9.8.6 */ public function __construct() { // Set the correct onboarding page based on the active version. if ( $this->is_pro_active() ) { $this->config['duplicator_onboarding'] = 'admin.php?page=duplicator-pro'; } parent::__construct(); } /** * Get the plugin name for use in IDs, CSS classes, and config keys. * * @since 1.9.8.6 * * @return string Plugin name. */ protected static function get_plugin_name(): string { return 'duplicator'; } /** * Get heading title text. * * @since 1.9.8.6 * * @return string Heading title. */ protected function get_heading_title(): string { return esc_html__( 'WPForms Collects It. Duplicator Protects It.', 'wpforms-lite' ); } /** * Get heading alt text for logo. * * @since 1.9.8.6 * * @return string Heading alt text. */ protected function get_heading_alt_text(): string { return esc_attr__( 'WPForms ♥ Duplicator', 'wpforms-lite' ); } /** * Get heading description strings. * * @since 1.9.8.6 * * @return array Array of description strings. */ protected function get_heading_strings(): array { return [ esc_html__( 'Every form entry lives in your database. One bad update, one crash, and it\'s gone. Duplicator backs up your entire site automatically so you can restore everything with one click.', 'wpforms-lite' ), esc_html__( 'Trusted by over 1.5 million websites.', 'wpforms-lite' ), ]; } /** * Get screenshot features list. * * @since 1.9.8.6 * * @return array Array of feature strings. */ protected function get_screenshot_features(): array { return [ 'Back up your entire site automatically: forms, entries, everything.', 'Restore your site with one click if anything goes wrong.', 'Store backups safely in Google Drive, Dropbox, or Amazon S3.', 'Schedule daily backups so you never have to think about it.', ]; } /** * Get screenshot alt text. * * @since 1.9.8.6 * * @return string Alt text for screenshot image. */ protected function get_screenshot_alt_text(): string { return esc_attr__( 'Duplicator screenshot', 'wpforms-lite' ); } /** * Generate and output step 'Result' section HTML. * * @since 1.9.8.6 * * @noinspection HtmlUnknownTarget */ protected function output_section_step_result(): void { $step = $this->get_data_step_result(); if ( empty( $step ) ) { return; } printf( '<section class="step step-result %1$s"> <aside class="num"> <img src="%2$s" alt="%3$s" /> </aside> <div> <h2>%4$s</h2> <p>%5$s</p> <button class="button %6$s" data-url="%7$s">%8$s</button> </div> </section>', esc_attr( $step['section_class'] ), esc_url( WPFORMS_PLUGIN_URL . 'assets/images/' . $step['icon'] ), esc_attr__( 'Step 3', 'wpforms-lite' ), esc_html__( 'Set Up Scheduled Cloud Backups', 'wpforms-lite' ), esc_html__( 'Keep your data safe forever with automatic daily backups to Google Drive, Dropbox, or Amazon S3.', 'wpforms-lite' ), esc_attr( $step['button_class'] ), esc_url( admin_url( $this->is_pro_active() ? 'admin.php?page=duplicator-pro-schedules' : 'admin.php?page=duplicator-schedules' ) ), esc_html( $step['button_text'] ) ); } /** * Whether the plugin is finished setup or not. * * @since 1.9.8.6 */ protected function is_plugin_finished_setup(): bool { if ( ! $this->is_plugin_configured() ) { return false; } $count = $this->get_package_count(); $schedule_count = 0; if ( $count && class_exists( '\Duplicator\Models\ScheduleEntity' ) && $this->is_pro_active() ) { $schedule_count = \Duplicator\Models\ScheduleEntity::count(); // phpcs:ignore WPForms.PHP.BackSlash.RemoveBackslash, WPForms.PHP.BackSlash.UseShortSyntax } return $count && $schedule_count; } /** * Generate and output footer section HTML. * * @since 1.9.8.6 */ protected function output_section_footer(): void { printf( '<section class="bottom"> <p>%s</p> </section>', esc_html__( 'Because the data you collect with WPForms is too valuable to lose.', 'wpforms-lite' ) ); } /** * Step 'Result' data. * * @since 1.9.8.6 * * @return array Step data. */ protected function get_data_step_result(): array { $count = $this->get_package_count(); $data = [ 'section_class' => $count ? '' : 'grey', 'button_class' => ! $count ? 'grey disabled' : 'button-primary', 'icon' => 'step-3.svg', 'button_text' => esc_html__( 'Set Up Cloud Backups', 'wpforms-lite' ), ]; if ( $count && class_exists( '\Duplicator\Models\ScheduleEntity' ) && $this->is_pro_active() ) { $schedule_count = \Duplicator\Models\ScheduleEntity::count(); // phpcs:ignore WPForms.PHP.BackSlash.RemoveBackslash, WPForms.PHP.BackSlash.UseShortSyntax $data['section_class'] = ''; $data['button_class'] = 'button-primary'; if ( $schedule_count ) { $data['icon'] = 'step-complete.svg'; $data['button_class'] = 'grey disabled'; $data['button_text'] = esc_html__( 'Cloud Backups Set Up', 'wpforms-lite' ); } } return $data; } /** * Whether a plugin is configured or not. * * @since 1.9.8.6 * * @return bool True if plugin is configured properly. */ protected function is_plugin_configured(): bool { if ( ! $this->is_plugin_activated() ) { return false; } $count = $this->get_package_count(); return $count > 0; } /** * Get the number of packages in the database. * * @since 1.9.8.6 * * @return int Number of packages. */ protected function get_package_count(): int { /** * Check if the plugin is available. * Since we are using a direct query to the database to get the number of records instead of built-in methods, * there is a chance of getting a non-zero value even if the plugin is turned off. */ if ( ! $this->is_plugin_available() ) { return 0; } // Check if Duplicator has been configured with basic settings. global $wpdb; // Check for the Duplicator packages table. $packages_table = $this->is_pro_active() ? $wpdb->prefix . 'duplicator_backups' : $wpdb->prefix . 'duplicator_packages'; // Use object caching to minimize direct DB queries here, as there is no core API // to check custom plugin table existence or its contents. $blog_id = function_exists( 'get_current_blog_id' ) ? get_current_blog_id() : 0; $table_exists_cache_key = "wpforms_dup_table_exists_{$blog_id}"; $package_count_cache_key = "wpforms_dup_package_count_{$blog_id}"; $table_exists = wp_cache_get( $table_exists_cache_key, 'wpforms' ); if ( $table_exists === false ) { // PHPCS: We must use a direct DB query because no WP API exists for custom tables. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $table_exists = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $packages_table ) ) ); wp_cache_set( $table_exists_cache_key, $table_exists, 'wpforms', 60 ); } $package_count = 0; if ( $table_exists === $packages_table ) { $package_count = wp_cache_get( $package_count_cache_key, 'wpforms' ); if ( $package_count === false ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $package_count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$packages_table}" ); wp_cache_set( $package_count_cache_key, $package_count, 'wpforms', 60 ); } } return (int) $package_count; } /** * Whether a plugin is active or not. * * @since 1.9.8.6 * * @return bool True if the plugin is active. */ protected function is_plugin_activated(): bool { return ( ( defined( 'DUPLICATOR_VERSION' ) || class_exists( 'Duplicator\Plugin' ) || class_exists( 'Duplicator\Pro\Requirements' ) ) && ( is_plugin_active( $this->config['lite_plugin'] ) || is_plugin_active( $this->config['pro_plugin'] ) ) ); } /** * Whether a plugin is available (class/function exists). * * @since 1.9.8.6 * * @return bool True if plugin is available. */ protected function is_plugin_available(): bool { return class_exists( 'Duplicator\Plugin' ) || defined( 'DUPLICATOR_VERSION' ) || class_exists( 'DUP_PRO_Plugin' ) || defined( 'DUPLICATOR_PRO_VERSION' ); } /** * Whether pro version is active. * * @since 1.9.8.6 * * @return bool True if pro version is active. */ protected function is_pro_active(): bool { return class_exists( 'DUP_PRO_Plugin' ) || defined( 'DUPLICATOR_PRO_VERSION' ); } /** * Get the heading for the install step. * * @since 1.9.8.6 * * @return string Install step heading. */ protected function get_install_heading(): string { return esc_html__( 'Install and Activate Duplicator', 'wpforms-lite' ); } /** * Get the description for the install step. * * @since 1.9.8.6 * * @return string Install step description. */ protected function get_install_description(): string { return esc_html__( 'Your first step toward bulletproof backups.', 'wpforms-lite' ); } /** * Get the plugin title. * * @since 1.9.8.6 * * @return string Plugin title. */ protected function get_plugin_title(): string { return esc_html__( 'Duplicator', 'wpforms-lite' ); } /** * Get the install button text. * * @since 1.9.8.6 * * @return string Install button text. */ protected function get_install_button_text(): string { return esc_html__( 'Install Duplicator', 'wpforms-lite' ); } /** * Get the text when a plugin is installed and activated. * * @since 1.9.8.6 * * @return string Installed & activated text. */ protected function get_installed_activated_text(): string { return esc_html__( 'Duplicator Installed & Activated', 'wpforms-lite' ); } /** * Get the activate button text. * * @since 1.9.8.6 * * @return string Activate button text. */ protected function get_activate_text(): string { return esc_html__( 'Activate Duplicator', 'wpforms-lite' ); } /** * Get the heading for the setup step. * * @since 1.9.8.6 * * @return string Setup step heading. */ protected function get_setup_heading(): string { return esc_html__( 'Create Your First Backup', 'wpforms-lite' ); } /** * Get the description for the setup step. * * @since 1.9.8.6 * * @return string Setup step description. */ protected function get_setup_description(): string { return esc_html__( 'Back up your site — forms, entries, settings, everything — in just one click.', 'wpforms-lite' ); } /** * Get the setup button text. * * @since 1.9.8.6 * * @return string Setup button text. */ protected function get_setup_button_text(): string { return esc_html__( 'Create First Backup', 'wpforms-lite' ); } /** * Get the text when setup is completed. * * @since 1.9.8.6 * * @return string Setup completed text. */ protected function get_setup_completed_text(): string { return esc_html__( 'Backup Created', 'wpforms-lite' ); } /** * Get the text when a pro-version is installed and activated. * * @since 1.9.8.6 * * @return string Pro installed and activated text. */ protected function get_pro_installed_activated_text(): string { return esc_html__( 'Duplicator Pro Installed & Activated', 'wpforms-lite' ); } } Pages/SMTP.php 0000644 00000032223 15252506741 0007110 0 ustar 00 <?php namespace WPForms\Admin\Pages; /** * SMTP Sub-page. * * @since 1.5.7 */ class SMTP extends Page { /** * Admin menu page slug. * * @since 1.5.7 * * @var string */ public const SLUG = 'wpforms-smtp'; /** * Transient name used to store the install source between page load and plugin activation. * * @since 2.0.0 * * @var string */ private const SOURCE_TRANSIENT = 'wpforms_smtp_source'; /** * Configuration. * * @since 1.5.7 * * @var array */ protected $config = [ 'lite_plugin' => 'wp-mail-smtp/wp_mail_smtp.php', 'lite_wporg_url' => 'https://wordpress.org/plugins/wp-mail-smtp/', 'lite_download_url' => 'https://downloads.wordpress.org/plugin/wp-mail-smtp.zip', 'pro_plugin' => 'wp-mail-smtp-pro/wp_mail_smtp.php', 'smtp_settings_url' => 'admin.php?page=wp-mail-smtp', 'smtp_wizard_url' => 'admin.php?page=wp-mail-smtp-setup-wizard', // The parent uses the `{name}_onboarding` key for the step 'Setup' button URL. 'smtp_onboarding' => 'admin.php?page=wp-mail-smtp-setup-wizard', ]; /** * Get the plugin name for use in IDs, CSS classes, config keys and AJAX action. * * @since 2.0.0 * * @return string Plugin name. */ protected static function get_plugin_name(): string { return 'smtp'; } /** * Hooks. * * @since 1.5.7 */ public function hooks(): void { // Check what page we are on. // phpcs:ignore WordPress.Security.NonceVerification.Recommended $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : ''; if ( $page === self::SLUG ) { add_action( 'admin_init', [ $this, 'redirect_to_smtp_settings' ] ); // Persist the install source so it survives the AJAX activation request. $this->maybe_store_source(); } parent::hooks(); // The addon hook is scoped to the SMTP page, matching the pre-refactor behavior. if ( $page === self::SLUG ) { /** * Hook for addons. * * @since 1.5.7 */ do_action( 'wpforms_admin_pages_smtp_hooks' ); } } /** * Store the install source in a short-lived, user-keyed transient. * * The shared AJAX handler does not forward a `source` parameter, so we capture * the `?source=` query argument on page load and read it back on activation. * * @since 2.0.0 */ protected function maybe_store_source(): void { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $source = sanitize_text_field( wp_unslash( $_GET['source'] ?? '' ) ); if ( $source === '' ) { return; } // Only a known source is mapped; everything else falls back to the default on activation. $value = $source === 'woocommerce' ? 'wpforms-woocommerce' : 'wpforms'; // TTL is generous enough to outlive a realistic install-and-activate flow. set_transient( self::SOURCE_TRANSIENT . '_' . get_current_user_id(), $value, HOUR_IN_SECONDS ); } /** * Set the wp_mail_smtp_source option on WP Mail SMTP plugin activation. * * @since 2.0.0 * * @param string $plugin_basename Plugin basename. */ public function plugin_activated( string $plugin_basename ): void { if ( $plugin_basename !== $this->config['lite_plugin'] ) { return; } $transient_key = self::SOURCE_TRANSIENT . '_' . get_current_user_id(); $source = get_transient( $transient_key ); delete_transient( $transient_key ); // Absent or unknown source falls back to the default attribution. if ( ! in_array( $source, [ 'wpforms', 'wpforms-woocommerce' ], true ) ) { $source = 'wpforms'; } update_option( 'wp_mail_smtp_source', $source ); } /** * Set wp_mail_smtp_source option to 'wpforms' on WP Mail SMTP plugin activation. * * @since 1.8.7 * @deprecated 2.0.0 * * @param string $plugin_basename Plugin basename. */ public function smtp_activated( $plugin_basename ): void { _deprecated_function( __METHOD__, '2.0.0', __CLASS__ . '::plugin_activated()' ); $this->plugin_activated( (string) $plugin_basename ); } /** * Get heading image URL. * * The parent default assumes an SVG, but WP Mail SMTP ships a PNG logo. * * @since 2.0.0 * * @return string Heading image URL. */ protected function get_heading_image_url(): string { return WPFORMS_PLUGIN_URL . 'assets/images/smtp/wpforms-wpmailsmtp.png'; } /** * Get heading title text. * * @since 2.0.0 * * @return string Heading title. */ protected function get_heading_title(): string { return esc_html__( 'Making Email Deliverability Easy for WordPress', 'wpforms-lite' ); } /** * Get heading alt text for logo. * * @since 2.0.0 * * @return string Heading alt text. */ protected function get_heading_alt_text(): string { return esc_attr__( 'WPForms ♥ WP Mail SMTP', 'wpforms-lite' ); } /** * Get heading description strings. * * @since 2.0.0 * * @return array Array of description strings. */ protected function get_heading_strings(): array { return [ esc_html__( 'WP Mail SMTP fixes deliverability problems with your WordPress emails and form notifications. It\'s built by the same folks behind WPForms.', 'wpforms-lite' ), ]; } /** * Get screenshot features list. * * @since 2.0.0 * * @return array Array of feature strings. */ protected function get_screenshot_features(): array { return [ esc_html__( 'Improves email deliverability in WordPress.', 'wpforms-lite' ), esc_html__( 'Used by 4+ million websites.', 'wpforms-lite' ), esc_html__( 'Free mailers: SendLayer, SMTP.com, Brevo, Google Workspace / Gmail, Mailgun, Postmark, SendGrid.', 'wpforms-lite' ), esc_html__( 'Pro mailers: Amazon SES, Microsoft 365 / Outlook.com, Zoho Mail.', 'wpforms-lite' ), ]; } /** * Get screenshot alt text. * * @since 2.0.0 * * @return string Alt text for screenshot image. */ protected function get_screenshot_alt_text(): string { return esc_attr__( 'WP Mail SMTP screenshot', 'wpforms-lite' ); } /** * Generate and output step 'Result' section HTML. * * The SMTP landing page completes at step 'Setup' (go to settings), * so no separate result section is rendered. * * @since 2.0.0 */ protected function output_section_step_result(): void { // SMTP has no third step; the flow ends after the setup step. } /** * Ajax endpoint. Check plugin setup status. * Used to properly init step 'Setup' section after completing step 'Install'. * * @since 1.5.7 */ public function ajax_check_plugin_status(): void { // Security checks. if ( ! check_ajax_referer( 'wpforms-admin', 'nonce', false ) || ! wpforms_current_user_can() ) { wp_send_json_error( [ 'error' => esc_html__( 'You do not have permission.', 'wpforms-lite' ) ] ); return; } if ( ! $this->is_smtp_activated() ) { wp_send_json_error( [ 'error' => esc_html__( 'Plugin unavailable.', 'wpforms-lite' ) ] ); return; } $result = []; $result['setup_status'] = (int) $this->is_smtp_configured(); $result['license_level'] = wp_mail_smtp()->get_license_type(); // Prevent redirect to the WP Mail SMTP Setup Wizard on the fresh installs. // We need this workaround since WP Mail SMTP doesn't check whether the mailer is already configured when redirecting to the Setup Wizard on the first run. if ( $result['setup_status'] > 0 ) { // SMTP has no third step, so the 'Setup' button itself becomes the final CTA linking to the settings page. $result['setup_completed_url'] = admin_url( $this->config['smtp_settings_url'] ); update_option( 'wp_mail_smtp_activation_prevent_redirect', true ); } wp_send_json_success( $result ); } /** * Get $phpmailer instance. * * @since 1.5.7 * @since 1.6.1.2 Conditionally returns $phpmailer v5 or v6. * @since 1.8.7 Use always $phpmailer v6. * * @return \PHPMailer|\PHPMailer\PHPMailer\PHPMailer Instance of PHPMailer. */ protected function get_phpmailer() { global $phpmailer; if ( ! ( $phpmailer instanceof \PHPMailer\PHPMailer\PHPMailer ) ) { require_once ABSPATH . WPINC . '/PHPMailer/PHPMailer.php'; require_once ABSPATH . WPINC . '/PHPMailer/SMTP.php'; require_once ABSPATH . WPINC . '/PHPMailer/Exception.php'; $phpmailer = new \PHPMailer\PHPMailer\PHPMailer( true ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited } return $phpmailer; } /** * Whether WP Mail SMTP plugin configured or not. * * @since 1.5.7 * * @return bool True if some mailer is selected and configured properly. */ protected function is_smtp_configured(): bool { if ( ! $this->is_smtp_activated() ) { return false; } $phpmailer = $this->get_phpmailer(); $mailer = \WPMailSMTP\Options::init()->get( 'mail', 'mailer' ); return ! empty( $mailer ) && $mailer !== 'mail' && wp_mail_smtp()->get_providers()->get_mailer( $mailer, $phpmailer )->is_mailer_complete(); } /** * Whether WP Mail SMTP plugin active or not. * * @since 1.5.7 * * @return bool True if SMTP plugin is active. */ protected function is_smtp_activated(): bool { return function_exists( 'wp_mail_smtp' ) && ( is_plugin_active( $this->config['lite_plugin'] ) || is_plugin_active( $this->config['pro_plugin'] ) ); } /** * Redirect to SMTP settings page. * * @since 1.5.7 */ public function redirect_to_smtp_settings(): void { // Redirect to SMTP plugin if it is activated. if ( $this->is_smtp_configured() ) { wp_safe_redirect( admin_url( $this->config['smtp_settings_url'] ) ); exit; } } /** * Whether a plugin is configured or not. * * @since 2.0.0 * * @return bool True if a plugin is configured properly. */ protected function is_plugin_configured(): bool { return $this->is_smtp_configured(); } /** * Whether a plugin is active or not. * * @since 2.0.0 * * @return bool True if the plugin is active. */ protected function is_plugin_activated(): bool { return $this->is_smtp_activated(); } /** * Whether a plugin is finished setup or not. * * @since 2.0.0 * * @return bool True if the plugin is finished setup. */ protected function is_plugin_finished_setup(): bool { return $this->is_smtp_configured(); } /** * Whether a plugin is available (class/function exists). * * @since 2.0.0 * * @return bool True if a plugin is available. */ protected function is_plugin_available(): bool { return function_exists( 'wp_mail_smtp' ); } /** * Whether a pro-version is active. * * @since 2.0.0 * * @return bool True if a pro-version is active. */ protected function is_pro_active(): bool { return $this->is_smtp_activated() && is_plugin_active( $this->config['pro_plugin'] ); } /** * Get the heading for the installation step. * * @since 2.0.0 * * @return string Install step heading. */ protected function get_install_heading(): string { return esc_html__( 'Install and Activate WP Mail SMTP', 'wpforms-lite' ); } /** * Get the description for the installation step. * * @since 2.0.0 * * @return string Install step description. */ protected function get_install_description(): string { return esc_html__( 'Install WP Mail SMTP from the WordPress.org plugin repository.', 'wpforms-lite' ); } /** * Get the plugin title. * * @since 2.0.0 * * @return string Plugin title. */ protected function get_plugin_title(): string { return esc_html__( 'WP Mail SMTP', 'wpforms-lite' ); } /** * Get the installation button text. * * @since 2.0.0 * * @return string Install button text. */ protected function get_install_button_text(): string { return esc_html__( 'Install WP Mail SMTP', 'wpforms-lite' ); } /** * Get the text when a plugin is installed and activated. * * @since 2.0.0 * * @return string Installed & activated text. */ protected function get_installed_activated_text(): string { return esc_html__( 'WP Mail SMTP Installed & Activated', 'wpforms-lite' ); } /** * Get the activate button text. * * @since 2.0.0 * * @return string Activate button text. */ protected function get_activate_text(): string { return esc_html__( 'Activate WP Mail SMTP', 'wpforms-lite' ); } /** * Get the heading for the setup step. * * @since 2.0.0 * * @return string Setup step heading. */ protected function get_setup_heading(): string { return esc_html__( 'Set Up WP Mail SMTP', 'wpforms-lite' ); } /** * Get the description for the setup step. * * @since 2.0.0 * * @return string Setup step description. */ protected function get_setup_description(): string { return esc_html__( 'Select and configure your mailer.', 'wpforms-lite' ); } /** * Get the setup button text. * * @since 2.0.0 * * @return string Setup button text. */ protected function get_setup_button_text(): string { return esc_html__( 'Open Setup Wizard', 'wpforms-lite' ); } /** * Get the text when setup is completed. * * @since 2.0.0 * * @return string Setup completed text. */ protected function get_setup_completed_text(): string { return esc_html__( 'Go to SMTP settings', 'wpforms-lite' ); } /** * Get the text when a pro-version is installed and activated. * * @since 2.0.0 * * @return string Pro installed and activated text. */ protected function get_pro_installed_activated_text(): string { return esc_html__( 'WP Mail SMTP Installed & Activated', 'wpforms-lite' ); } } Pages/Templates.php 0000644 00000007615 15252506741 0010272 0 ustar 00 <?php namespace WPForms\Admin\Pages; use WPForms\Admin\Traits\FormTemplates; use WPForms\Admin\Builder\Templates as BuilderTemplates; /** * Main Templates page class. * * @since 1.7.7 */ class Templates { use FormTemplates; /** * Page slug. * * @since 1.7.7 * * @var string */ const SLUG = 'wpforms-templates'; /** * Initialize class. * * @since 1.7.7 */ public function init() { if ( ! wpforms_is_admin_page( 'templates' ) && ! wpforms_is_admin_ajax() ) { return; } $this->addons_obj = wpforms()->obj( 'addons' ); $this->hooks(); } /** * Register hooks. * * @since 1.7.7 */ private function hooks() { add_action( 'wpforms_admin_page', [ $this, 'output' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueues' ] ); } /** * Enqueue assets. * * @since 1.7.7 */ public function enqueues() { $min = wpforms_get_min_suffix(); wp_enqueue_style( 'wpforms-form-templates', WPFORMS_PLUGIN_URL . "assets/css/admin/admin-form-templates{$min}.css", [], WPFORMS_VERSION ); wp_enqueue_script( 'wpforms-admin-form-templates', WPFORMS_PLUGIN_URL . "assets/js/admin/pages/form-templates{$min}.js", [ 'underscore', 'wp-util', 'wpforms-form-templates' ], WPFORMS_VERSION, true ); wp_enqueue_style( 'tooltipster', WPFORMS_PLUGIN_URL . 'assets/lib/jquery.tooltipster/jquery.tooltipster.min.css', [], '4.2.6' ); wp_enqueue_script( 'tooltipster', WPFORMS_PLUGIN_URL . 'assets/lib/jquery.tooltipster/jquery.tooltipster.min.js', [ 'jquery', 'wpforms-admin-form-templates' ], '4.2.6', true ); wp_localize_script( 'wpforms-admin-form-templates', 'wpforms_admin_form_templates', [ 'ajaxurl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'wpforms-builder' ), 'templates_nonce' => wp_create_nonce( 'wpforms-form-templates' ), 'heads_up' => esc_html__( 'Heads Up!', 'wpforms-lite' ), 'infinite_scroll_enabled' => BuilderTemplates::is_infinite_scroll_enabled(), 'openAIFormUrl' => admin_url( 'admin.php?page=wpforms-builder&view=setup&ai-form' ), 'infiniteScrollModuleUrl' => WPFORMS_PLUGIN_URL . "assets/js/admin/builder/modules/templates-infinite-scroll{$min}.js", ] ); } /** * Build the output for the Form Templates admin page. * * @since 1.7.7 */ public function output() { ?> <div id="wpforms-form-templates" class="wrap wpforms-admin-wrap"> <h1 class="page-title"><?php esc_html_e( 'Form Templates', 'wpforms-lite' ); ?></h1> <div class="wpforms-form-setup-content" > <div class="wpforms-setup-title"> <?php esc_html_e( 'Get a Head Start With Our Pre-Made Form Templates', 'wpforms-lite' ); ?> </div> <p class="wpforms-setup-desc secondary-text"> <?php printf( wp_kses( /* translators: %1$s - create template doc link; %2$s - Contact us page link. */ __( 'Choose a template to speed up the process of creating your form. You can also start with a <a href="#" class="wpforms-trigger-blank">blank form</a> or <a href="%1$s" target="_blank" rel="noopener noreferrer">create your own</a>. <br>Have a suggestion for a new template? <a href="%2$s" target="_blank" rel="noopener noreferrer">We’d love to hear it</a>!', 'wpforms-lite' ), [ 'strong' => [], 'br' => [], 'a' => [ 'href' => [], 'class' => [], 'target' => [], 'rel' => [], ], ] ), esc_url( wpforms_utm_link( 'https://wpforms.com/docs/how-to-create-a-custom-form-template/', 'Form Templates Subpage', 'Create Your Own Template' ) ), esc_url( wpforms_utm_link( 'https://wpforms.com/form-template-suggestion/', 'Form Templates Subpage', 'Form Template Suggestion' ) ) ); ?> </p> <?php $this->output_templates_content(); ?> </div> </div> <?php } } Pages/Page.php 0000644 00000045014 15252506741 0007203 0 ustar 00 <?php namespace WPForms\Admin\Pages; /** * Abstract class for admin pages. * * @since 1.9.8.6 */ abstract class Page { /** * Admin menu page slug. * * @since 1.9.8.6 * * @var string */ public const SLUG = ''; /** * Configuration. * * @since 1.9.8.6 * * @var array */ protected $config = []; /** * Runtime data used for generating page HTML. * * @since 1.9.8.6 * * @var array */ protected $output_data = []; /** * Constructor. * * @since 1.9.8.6 */ public function __construct() { if ( ! wpforms_current_user_can() ) { return; } $this->hooks(); } /** * Hooks. * * @since 1.9.8.6 */ public function hooks(): void { $plugin = static::get_plugin_name(); if ( wp_doing_ajax() ) { add_action( "wp_ajax_wpforms_page_check_{$plugin}_status", [ $this, 'ajax_check_plugin_status' ] ); add_action( 'wpforms_plugin_activated', [ $this, 'plugin_activated' ] ); } // Check what page we are on. // phpcs:ignore WordPress.Security.NonceVerification.Recommended $page = isset( $_GET['page'] ) ? sanitize_key( $_GET['page'] ) : ''; // Only load if we are actually on the correct page. if ( $page !== static::SLUG ) { return; } add_filter( 'wpforms_admin_header', '__return_false' ); add_action( 'wpforms_admin_page', [ $this, 'output' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] ); /** * Hook for addons. * * @since 1.9.8.6 */ do_action( 'wpforms_admin_pages_page_' . static::get_plugin_name() . '_hooks' ); } /** * Enqueue JS and CSS files. * * @since 1.9.8.6 */ public function enqueue_assets(): void { $min = wpforms_get_min_suffix(); // Lity. wp_enqueue_style( 'wpforms-lity', WPFORMS_PLUGIN_URL . 'assets/lib/lity/lity.min.css', null, '3.0.0' ); wp_enqueue_script( 'wpforms-lity', WPFORMS_PLUGIN_URL . 'assets/lib/lity/lity.min.js', [ 'jquery' ], '3.0.0', true ); // Custom styles for Lity image size limitation. wp_add_inline_style( 'wpforms-lity', ' .lity-image .lity-container { max-width: 1040px !important; } .lity-image img { max-width: 1040px !important; width: 100%; height: auto; } ' ); wp_enqueue_script( 'wpforms-admin-page-' . static::get_plugin_name(), WPFORMS_PLUGIN_URL . "assets/js/admin/pages/common{$min}.js", [ 'jquery' ], WPFORMS_VERSION, true ); wp_localize_script( 'wpforms-admin-page-' . static::get_plugin_name(), 'wpforms_pluginlanding', $this->get_js_strings() ); } /** * Generate and output page HTML. * * @since 1.9.8.6 */ public function output(): void { echo '<div id="wpforms-admin-' . esc_attr( static::get_plugin_name() ) . '" class="wrap wpforms-admin-wrap wpforms-admin-plugin-landing">'; $this->output_section_heading(); $this->output_section_screenshot(); $this->output_section_footer(); $this->output_section_step_install(); $this->output_section_step_setup(); $this->output_section_step_result(); echo '</div>'; } /** * Generate and output step 'Install' section HTML. * * @since 1.9.8.6 * * @noinspection HtmlUnknownTarget */ protected function output_section_step_install(): void { $step = $this->get_data_step_install(); if ( empty( $step ) ) { return; } $button_format = '<button class="button %3$s" data-plugin="%1$s" data-action="%4$s" data-provider="%5$s">%2$s</button>'; $button_allowed_html = [ 'button' => [ 'class' => true, 'data-plugin' => true, 'data-action' => true, 'data-provider' => true, ], ]; if ( ! $this->output_data['plugin_installed'] && ! $this->output_data['pro_plugin_installed'] && ! wpforms_can_install( 'plugin' ) ) { $button_format = '<a class="link" href="%1$s" target="_blank" rel="nofollow noopener">%2$s <span aria-hidden="true" class="dashicons dashicons-external"></span></a>'; $button_allowed_html = [ 'a' => [ 'class' => true, 'href' => true, 'target' => true, 'rel' => true, ], 'span' => [ 'class' => true, 'aria-hidden' => true, ], ]; } $plugin_attr = wpforms_is_url( $step['plugin'] ) ? esc_url( $step['plugin'] ) : esc_attr( $step['plugin'] ); $button = sprintf( $button_format, $plugin_attr, esc_html( $step['button_text'] ), esc_attr( $step['button_class'] ), esc_attr( $step['button_action'] ), esc_attr( static::get_plugin_name() ) ); printf( '<section class="step step-install"> <aside class="num"> <img src="%1$s" alt="%2$s" /> <i class="loader hidden"></i> </aside> <div> <h2>%3$s</h2> <p>%4$s</p> %5$s </div> </section>', esc_url( WPFORMS_PLUGIN_URL . 'assets/images/' . $step['icon'] ), esc_attr__( 'Step 1', 'wpforms-lite' ), esc_html( $step['heading'] ), esc_html( $step['description'] ), wp_kses( $button, $button_allowed_html ) ); } /** * Generate and output step 'Setup' section HTML. * * @since 1.9.8.6 * * @noinspection HtmlUnknownTarget */ protected function output_section_step_setup(): void { $step = $this->get_data_step_setup(); if ( empty( $step ) ) { return; } printf( '<section class="step step-setup %1$s"> <aside class="num"> <img src="%2$s" alt="%3$s" /> <i class="loader hidden"></i> </aside> <div> <h2>%4$s</h2> <p>%5$s</p> <button class="button %6$s" data-url="%7$s">%8$s</button> </div> </section>', esc_attr( $step['section_class'] ), esc_url( WPFORMS_PLUGIN_URL . 'assets/images/' . $step['icon'] ), esc_attr__( 'Step 2', 'wpforms-lite' ), esc_html( $step['heading'] ), esc_html( $step['description'] ), esc_attr( $step['button_class'] ), esc_url( admin_url( $this->config[ static::get_plugin_name() . '_onboarding' ] ) ), esc_html( $step['button_text'] ) ); } /** * Generate and output footer section HTML. * * @since 1.9.8.6 */ protected function output_section_footer(): void { // Default implementation - can be overridden by child classes. } /** * Step 'Install' data. * * @since 1.9.8.6 * * @return array Step data. */ protected function get_data_step_install(): array { $step = []; $step['heading'] = $this->get_install_heading(); $step['description'] = $this->get_install_description(); $this->output_data['all_plugins'] = get_plugins(); $this->output_data['plugin_installed'] = array_key_exists( $this->config['lite_plugin'], $this->output_data['all_plugins'] ); $this->output_data['plugin_activated'] = false; $this->output_data['pro_plugin_installed'] = array_key_exists( $this->config['pro_plugin'], $this->output_data['all_plugins'] ); $this->output_data['pro_plugin_activated'] = false; if ( ! $this->output_data['plugin_installed'] && ! $this->output_data['pro_plugin_installed'] ) { $step['icon'] = 'step-1.svg'; $step['button_text'] = $this->get_install_button_text(); $step['button_class'] = 'button-primary'; $step['button_action'] = 'install'; $step['plugin'] = $this->config['lite_download_url']; if ( ! wpforms_can_install( 'plugin' ) ) { $step['heading'] = $this->get_plugin_title(); $step['description'] = ''; $step['button_text'] = $this->get_plugin_title() . ' on WordPress.org'; $step['plugin'] = $this->config['lite_wporg_url']; } } else { $this->output_data['plugin_activated'] = is_plugin_active( $this->config['lite_plugin'] ) || is_plugin_active( $this->config['pro_plugin'] ); $step['icon'] = $this->output_data['plugin_activated'] ? 'step-complete.svg' : 'step-1.svg'; $step['button_text'] = $this->output_data['plugin_activated'] ? $this->get_installed_activated_text() : $this->get_activate_text(); $step['button_class'] = $this->output_data['plugin_activated'] ? 'grey disabled' : 'button-primary'; $step['button_action'] = $this->output_data['plugin_activated'] ? '' : 'activate'; $step['plugin'] = $this->output_data['pro_plugin_installed'] ? $this->config['pro_plugin'] : $this->config['lite_plugin']; $step['is_pro'] = $this->output_data['pro_plugin_installed']; } return $step; } /** * Step 'Setup' data. * * @since 1.9.8.6 * * @return array Step data. */ protected function get_data_step_setup(): array { $step = []; $this->output_data['plugin_setup'] = false; if ( $this->output_data['plugin_activated'] ) { $this->output_data['plugin_setup'] = $this->is_plugin_configured(); } $step['icon'] = 'step-2.svg'; $step['section_class'] = $this->output_data['plugin_activated'] ? '' : 'grey'; $step['heading'] = $this->get_setup_heading(); $step['description'] = $this->get_setup_description(); $step['button_text'] = $this->get_setup_button_text(); $step['button_class'] = 'grey disabled'; if ( $this->output_data['plugin_setup'] ) { $step['icon'] = 'step-complete.svg'; $step['section_class'] = ''; $step['button_text'] = $this->get_setup_completed_text(); } else { $step['button_class'] = $this->output_data['plugin_activated'] ? 'button-primary' : 'grey disabled'; } return $step; } /** * Ajax endpoint. Check plugin setup status. * Used to properly init the step 2 section after completing step 1. * * @since 1.9.8.6 */ public function ajax_check_plugin_status(): void { // Security checks. if ( ! check_ajax_referer( 'wpforms-admin', 'nonce', false ) || ! wpforms_current_user_can() ) { wp_send_json_error( [ 'error' => esc_html__( 'You do not have permission.', 'wpforms-lite' ) ] ); } $result = []; if ( ! $this->is_plugin_available() ) { wp_send_json_error( [ 'error' => esc_html__( 'Plugin unavailable.', 'wpforms-lite' ) ] ); } $result['setup_status'] = (int) $this->is_plugin_configured(); $result['license_level'] = 'lite'; $result['step3_button_url'] = $this->config[ static::get_plugin_name() . '_addon_page' ]; if ( $this->is_pro_active() ) { $result['license_level'] = 'pro'; } $result['result_status'] = $this->is_plugin_finished_setup(); $result['addon_installed'] = (int) array_key_exists( $this->config[ static::get_plugin_name() . '_addon' ], get_plugins() ); wp_send_json_success( $result ); } /** * Set the source of the plugin installation. * * @since 1.9.8.6 * * @param string $plugin_basename The basename of the plugin. */ public function plugin_activated( string $plugin_basename ): void { if ( $plugin_basename !== $this->config['lite_plugin'] ) { return; } $source = wpforms()->is_pro() ? 'WPForms' : 'WPForms Lite'; update_option( static::get_plugin_name() . '_source', $source, false ); update_option( static::get_plugin_name() . '_date', time(), false ); } /** * JS strings. * * @since 1.9.8.6 * * @return array Array of strings. * @noinspection HtmlUnknownTarget */ protected function get_js_strings(): array { $error_could_not_install = sprintf( wp_kses( /* translators: %1$s - Lite plugin download URL. */ __( 'Could not install the plugin automatically. Please <a href="%1$s">download</a> it and install it manually.', 'wpforms-lite' ), [ 'a' => [ 'href' => true, ], ] ), esc_url( $this->config['lite_download_url'] ?? '' ) ); $error_could_not_activate = sprintf( wp_kses( /* translators: %1$s - Plugins page URL. */ __( 'Could not activate the plugin. Please activate it on the <a href="%1$s">Plugins page</a>.', 'wpforms-lite' ), [ 'a' => [ 'href' => true, ], ] ), esc_url( admin_url( 'plugins.php' ) ) ); return [ 'installing' => esc_html__( 'Installing...', 'wpforms-lite' ), 'activating' => esc_html__( 'Activating...', 'wpforms-lite' ), 'activated' => $this->get_installed_activated_text(), 'activated_pro' => $this->get_pro_installed_activated_text(), 'setup_completed' => $this->get_setup_completed_text(), 'install_now' => esc_html__( 'Install Now', 'wpforms-lite' ), 'activate_now' => esc_html__( 'Activate Now', 'wpforms-lite' ), 'download_now' => esc_html__( 'Download Now', 'wpforms-lite' ), 'plugins_page' => esc_html__( 'Go to Plugins page', 'wpforms-lite' ), 'error_could_not_install' => $error_could_not_install, 'error_could_not_activate' => $error_could_not_activate, static::get_plugin_name() . '_manual_install_url' => $this->config['lite_download_url'], static::get_plugin_name() . '_manual_activate_url' => admin_url( 'plugins.php' ), ]; } /** * Get the plugin name for use in IDs, CSS classes, and config keys. * * @since 1.9.8.6 * * @return string Plugin name. */ abstract protected static function get_plugin_name(): string; /** * Generate and output heading section HTML. * * @since 1.9.8.6 * * @noinspection HtmlUnknownTarget */ public function output_section_heading(): void { $strings = $this->get_heading_strings(); // Heading section. printf( '<section class="top"> <img class="img-top" src="%1$s" alt="%2$s"/> <h1>%3$s</h1> <p>%4$s</p> </section>', esc_url( $this->get_heading_image_url() ), esc_attr( $this->get_heading_alt_text() ), esc_html( $this->get_heading_title() ), esc_html( implode( ' ', $strings ) ) ); } /** * Get heading image URL. * * @since 1.9.8.6 * * @return string Heading image URL. */ protected function get_heading_image_url(): string { return WPFORMS_PLUGIN_URL . 'assets/images/' . static::get_plugin_name() . '/wpforms-' . static::get_plugin_name() . '.svg'; } /** * Get heading title text. * * @since 1.9.8.6 * * @return string Heading title. */ abstract protected function get_heading_title(): string; /** * Get heading alt text for logo. * * @since 1.9.8.6 * * @return string Heading alt text. */ abstract protected function get_heading_alt_text(): string; /** * Get heading description strings. * * @since 1.9.8.6 * * @return array Array of description strings. */ abstract protected function get_heading_strings(): array; /** * Generate and output screenshot section HTML. * * @since 1.9.8.6 */ protected function output_section_screenshot(): void { $features = $this->get_screenshot_features(); $list = ''; foreach ( $features as $feature ) { $list .= '<li>' . esc_html( $feature ) . '</li>'; } // Screenshot section. printf( '<section class="screenshot"> <div class="cont"> <img src="%1$s" alt="%2$s" srcset="%4$s 2x"/> <a href="%3$s" class="hover" data-lity></a> </div> <ul>%5$s</ul> </section>', esc_url( WPFORMS_PLUGIN_URL . 'assets/images/' . static::get_plugin_name() . '/screenshot-tnail.png' ), esc_attr( $this->get_screenshot_alt_text() ), esc_url( WPFORMS_PLUGIN_URL . 'assets/images/' . static::get_plugin_name() . '/screenshot-full@2x.png' ), esc_url( WPFORMS_PLUGIN_URL . 'assets/images/' . static::get_plugin_name() . '/screenshot-tnail@2x.png' ), wp_kses( $list, [ 'li' => [] ] ) ); } /** * Get screenshot features list. * * @since 1.9.8.6 * * @return array Array of feature strings. */ abstract protected function get_screenshot_features(): array; /** * Get screenshot alt text. * * @since 1.9.8.6 * * @return string Alt text for screenshot image. */ abstract protected function get_screenshot_alt_text(): string; /** * Generate and output step 'Result' section HTML. * * @since 1.9.8.6 */ abstract protected function output_section_step_result(): void; /** * Whether a plugin is configured or not. * * @since 1.9.8.6 * * @return bool True if a plugin is configured properly. */ abstract protected function is_plugin_configured(): bool; /** * Whether a plugin is active or not. * * @since 1.9.8.6 * * @return bool True if the plugin is active. */ abstract protected function is_plugin_activated(): bool; /** * Whether a plugin is finished setup or not. * * @since 1.9.8.6 * * @return bool True if the plugin is finished setup. */ abstract protected function is_plugin_finished_setup(): bool; /** * Whether a plugin is available (class/function exists). * * @since 1.9.8.6 * * @return bool True if a plugin is available. */ abstract protected function is_plugin_available(): bool; /** * Whether a pro-version is active. * * @since 1.9.8.6 * * @return bool True if a pro-version is active. */ abstract protected function is_pro_active(): bool; /** * Get the heading for the installation step. * * @since 1.9.8.6 * * @return string Install step heading. */ abstract protected function get_install_heading(): string; /** * Get the description for the installation step. * * @since 1.9.8.6 * * @return string Install step description. */ abstract protected function get_install_description(): string; /** * Get the plugin title. * * @since 1.9.8.6 * * @return string Plugin title. */ abstract protected function get_plugin_title(): string; /** * Get the installation button text. * * @since 1.9.8.6 * * @return string Install button text. */ abstract protected function get_install_button_text(): string; /** * Get the text when a plugin is installed and activated. * * @since 1.9.8.6 * * @return string Installed & activated text. */ abstract protected function get_installed_activated_text(): string; /** * Get the activate button text. * * @since 1.9.8.6 * * @return string Activate button text. */ abstract protected function get_activate_text(): string; /** * Get the heading for the setup step. * * @since 1.9.8.6 * * @return string Setup step heading. */ abstract protected function get_setup_heading(): string; /** * Get the description for the setup step. * * @since 1.9.8.6 * * @return string Setup step description. */ abstract protected function get_setup_description(): string; /** * Get the setup button text. * * @since 1.9.8.6 * * @return string Setup button text. */ abstract protected function get_setup_button_text(): string; /** * Get the text when setup is completed. * * @since 1.9.8.6 * * @return string Setup completed text. */ abstract protected function get_setup_completed_text(): string; /** * Get the text when a pro-version is installed and activated. * * @since 1.9.8.6 * * @return string Pro installed and activated text. */ abstract protected function get_pro_installed_activated_text(): string; } Pages/PrivacyCompliance.php 0000644 00000026344 15252506741 0011744 0 ustar 00 <?php namespace WPForms\Admin\Pages; /** * Privacy Compliance Subpage. * * @since 1.9.7.3 */ class PrivacyCompliance extends Page { /** * Admin menu page slug. * * @since 1.9.7.3 * * @var string */ public const SLUG = 'wpforms-wpconsent'; /** * Configuration. * * @since 1.9.7.3 * * @var array */ protected $config = [ 'lite_plugin' => 'wpconsent-cookies-banner-privacy-suite/wpconsent.php', 'lite_wporg_url' => 'https://wordpress.org/plugins/wpconsent-cookies-banner-privacy-suite/', 'lite_download_url' => 'https://downloads.wordpress.org/plugin/wpconsent-cookies-banner-privacy-suite.zip', 'pro_plugin' => 'wpconsent-premium/wpconsent-premium.php', 'wpconsent_addon' => 'wpconsent-premium/wpconsent-premium.php', 'wpconsent_addon_page' => 'https://wpconsent.com/?utm_source=wpformsplugin&utm_medium=link&utm_campaign=privacy-compliance-page', 'wpconsent_onboarding' => 'admin.php?page=wpconsent-onboarding', ]; /** * Get the plugin name for use in IDs, CSS classes, and config keys. * * @since 1.9.7.3 * * @return string Plugin name. */ protected static function get_plugin_name(): string { return 'wpconsent'; } /** * Hooks. * * @since 1.9.7.3 */ public function hooks(): void { if ( wp_doing_ajax() ) { remove_action( 'admin_init', 'wpconsent_maybe_redirect_onboarding', 9999 ); } parent::hooks(); } /** * Get heading image URL. * * @since 1.9.7.3 * * @return string Heading image URL. */ protected function get_heading_image_url(): string { return WPFORMS_PLUGIN_URL . 'assets/images/wpconsent/wpforms-wpconsent.svg'; } /** * Get heading title text. * * @since 1.9.7.3 * * @return string Heading title. */ protected function get_heading_title(): string { return esc_html__( 'Make Your Website Privacy-Compliant in Minutes', 'wpforms-lite' ); } /** * Get heading alt text for logo. * * @since 1.9.7.3 * * @return string Heading alt text. */ protected function get_heading_alt_text(): string { return esc_attr__( 'WPForms ♥ WPConsent', 'wpforms-lite' ); } /** * Get heading description strings. * * @since 1.9.7.3 * * @return array Array of description strings. */ protected function get_heading_strings(): array { return [ esc_html__( 'Build trust with clear, compliant privacy practices. WPConsent adds clean, professional banners and handles the technical side for you.', 'wpforms-lite' ), esc_html__( 'Built for transparency. Designed for ease.', 'wpforms-lite' ), ]; } /** * Get screenshot features list. * * @since 1.9.7.3 * * @return array Array of feature strings. */ protected function get_screenshot_features(): array { return [ esc_html__( 'A professional banner that fits your site.', 'wpforms-lite' ), esc_html__( 'Tools like Google Analytics and Facebook Pixel paused until consent.', 'wpforms-lite' ), esc_html__( 'Peace of mind knowing you’re aligned with global laws.', 'wpforms-lite' ), esc_html__( 'Self-hosted. Your data remains on your site.', 'wpforms-lite' ), ]; } /** * Get screenshot alt text. * * @since 1.9.7.3 * * @return string Alt text for screenshot image. */ protected function get_screenshot_alt_text(): string { return esc_attr__( 'WPConsent screenshot', 'wpforms-lite' ); } /** * Generate and output step 'Result' section HTML. * * @since 1.9.7.3 * * @noinspection HtmlUnknownTarget */ protected function output_section_step_result(): void { $step = $this->get_data_step_result(); if ( empty( $step ) ) { return; } printf( '<section class="step step-result %1$s"> <aside class="num"> <img src="%2$s" alt="%3$s" /> <i class="loader hidden"></i> </aside> <div> <h2>%4$s</h2> <p>%5$s</p> <button class="button %6$s" data-url="%7$s">%8$s</button> </div> </section>', esc_attr( $step['section_class'] ), esc_url( WPFORMS_PLUGIN_URL . 'assets/images/' . $step['icon'] ), esc_attr__( 'Step 3', 'wpforms-lite' ), esc_html__( 'Get Advanced Cookie Consent Features', 'wpforms-lite' ), esc_html__( 'With WPConsent Pro you can access advanced features like geolocation, popup layout, records of consent, multilanguage support, and more.', 'wpforms-lite' ), esc_attr( $step['button_class'] ), esc_url( $step['button_url'] ), esc_html( $step['button_text'] ) ); } /** * Step 'Result' data. * * @since 1.9.7.3 * * @return array Step data. * @noinspection PhpUndefinedFunctionInspection */ protected function get_data_step_result(): array { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh $step = []; $step['icon'] = 'step-3.svg'; $step['section_class'] = $this->output_data['plugin_setup'] ? '' : 'grey'; $step['button_text'] = esc_html__( 'Learn More', 'wpforms-lite' ); $step['button_class'] = 'grey disabled'; $step['button_url'] = ''; $plugin_license_level = $this->get_license_level(); switch ( $plugin_license_level ) { case 'lite': $step['button_url'] = $this->config['wpconsent_addon_page']; $step['button_class'] = $this->output_data['plugin_setup'] ? 'button-primary' : 'grey disabled'; break; case 'pro': $addon_installed = array_key_exists( $this->config['wpconsent_addon'], $this->output_data['all_plugins'] ); $step['button_text'] = $addon_installed ? esc_html__( 'WPConsent Pro Installed & Activated', 'wpforms-lite' ) : esc_html__( 'Install Now', 'wpforms-lite' ); $step['button_class'] = $this->output_data['plugin_setup'] ? 'grey disabled' : 'button-primary'; $step['icon'] = $addon_installed ? 'step-complete.svg' : 'step-3.svg'; break; } return $step; } /** * Retrieve the license level of the plugin. * * @since 1.9.8.6 * * @return string The plugin license level ('lite' or 'pro'). */ protected function get_license_level(): string { $plugin_license_level = 'lite'; // Check if premium features are available. if ( function_exists( 'wpconsent' ) ) { $wpconsent = wpconsent(); if ( isset( $wpconsent->license ) && method_exists( $wpconsent->license, 'is_active' ) ) { $plugin_license_level = $wpconsent->license->is_active() ? 'pro' : 'lite'; } } return $plugin_license_level; } /** * Whether the plugin is finished setup or not. * * @since 1.9.8.6 */ protected function is_plugin_finished_setup(): bool { if ( ! $this->is_plugin_configured() ) { return false; } return $this->get_license_level() === 'pro'; } /** * Set the source of the plugin installation. * * @since 1.9.8 * @deprecated 1.9.8.6 * * @param string $plugin_basename The basename of the plugin. */ public function privacy_compliance_activated( string $plugin_basename ): void { $this->plugin_activated( $plugin_basename ); } /** * Whether a plugin is configured or not. * * @since 1.9.7.3 * * @return bool True if plugin is configured properly. * @noinspection PhpUndefinedFunctionInspection */ protected function is_plugin_configured(): bool { if ( ! $this->is_plugin_activated() ) { return false; } // Check if WPConsent has been configured with basic settings. // The plugin is considered configured if the consent banner is enabled. if ( function_exists( 'wpconsent' ) ) { $wpconsent = wpconsent(); if ( isset( $wpconsent->settings ) ) { $enable_consent_banner = $wpconsent->settings->get_option( 'enable_consent_banner', 0 ); return ! empty( $enable_consent_banner ); } } return false; } /** * Whether a plugin is active or not. * * @since 1.9.7.3 * * @return bool True if plugin is active. */ protected function is_plugin_activated(): bool { return ( function_exists( 'wpconsent' ) && ( is_plugin_active( $this->config['lite_plugin'] ) || is_plugin_active( $this->config['pro_plugin'] ) ) ); } /** * Whether a plugin is available (class/function exists). * * @since 1.9.7.3 * * @return bool True if plugin is available. */ protected function is_plugin_available(): bool { return function_exists( 'wpconsent' ); } /** * Whether pro version is active. * * @since 1.9.7.3 * * @return bool True if pro version is active. * @noinspection PhpUndefinedFunctionInspection */ protected function is_pro_active(): bool { if ( ! function_exists( 'wpconsent' ) ) { return false; } $wpconsent = wpconsent(); return isset( $wpconsent->license ) && method_exists( $wpconsent->license, 'is_active' ) && $wpconsent->license->is_active(); } /** * Get the heading for the install step. * * @since 1.9.7.3 * * @return string Install step heading. */ protected function get_install_heading(): string { return esc_html__( 'Install & Activate WPConsent', 'wpforms-lite' ); } /** * Get the description for the install step. * * @since 1.9.7.3 * * @return string Install step description. */ protected function get_install_description(): string { return esc_html__( 'Install WPConsent from the WordPress.org plugin repository.', 'wpforms-lite' ); } /** * Get the plugin title. * * @since 1.9.7.3 * * @return string Plugin title. */ protected function get_plugin_title(): string { return esc_html__( 'WPConsent', 'wpforms-lite' ); } /** * Get the install button text. * * @since 1.9.7.3 * * @return string Install button text. */ protected function get_install_button_text(): string { return esc_html__( 'Install WPConsent', 'wpforms-lite' ); } /** * Get the text when a plugin is installed and activated. * * @since 1.9.7.3 * * @return string Installed & activated text. */ protected function get_installed_activated_text(): string { return esc_html__( 'WPConsent Installed & Activated', 'wpforms-lite' ); } /** * Get the activate button text. * * @since 1.9.7.3 * * @return string Activate button text. */ protected function get_activate_text(): string { return esc_html__( 'Activate WPConsent', 'wpforms-lite' ); } /** * Get the heading for the setup step. * * @since 1.9.7.3 * * @return string Setup step heading. */ protected function get_setup_heading(): string { return esc_html__( 'Set Up WPConsent', 'wpforms-lite' ); } /** * Get the description for the setup step. * * @since 1.9.7.3 * * @return string Setup step description. */ protected function get_setup_description(): string { return esc_html__( 'WPConsent has an intuitive setup wizard to guide you through the cookie consent configuration process.', 'wpforms-lite' ); } /** * Get the setup button text. * * @since 1.9.7.3 * * @return string Setup button text. */ protected function get_setup_button_text(): string { return esc_html__( 'Run Setup Wizard', 'wpforms-lite' ); } /** * Get the text when setup is completed. * * @since 1.9.7.3 * * @return string Setup completed text. */ protected function get_setup_completed_text(): string { return esc_html__( 'Setup Complete', 'wpforms-lite' ); } /** * Get the text when a pro-version is installed and activated. * * @since 1.9.7.3 * * @return string Pro installed and activated text. */ protected function get_pro_installed_activated_text(): string { return esc_html__( 'WPConsent Pro Installed & Activated', 'wpforms-lite' ); } } Pages/ConstantContact.php 0000644 00000003115 15252506741 0011430 0 ustar 00 <?php namespace WPForms\Admin\Pages; /** * Constant Contact Sub-page. * * @since 1.7.3 */ class ConstantContact { /** * Determine if the class is allowed to be loaded. * * @since 1.7.3 */ private function allow_load() { return wpforms_is_admin_page( 'page', 'constant-contact' ); } /** * Initialize class. * * @since 1.7.3 */ public function init() { if ( ! $this->allow_load() ) { return; } $this->hooks(); } /** * Hooks. * * @since 1.7.3 */ private function hooks() { add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] ); add_action( 'wpforms_admin_page', [ $this, 'view' ] ); } /** * Enqueue JS and CSS files. * * @since 1.7.3 */ public function enqueue_assets() { // Lity. wp_enqueue_style( 'wpforms-lity', WPFORMS_PLUGIN_URL . 'assets/lib/lity/lity.min.css', null, '3.0.0' ); wp_enqueue_script( 'wpforms-lity', WPFORMS_PLUGIN_URL . 'assets/lib/lity/lity.min.js', [ 'jquery' ], '3.0.0', true ); } /** * Page view. * * @since 1.7.3 */ public function view() { $sign_up_link = get_option( 'wpforms_constant_contact_signup', 'https://constant-contact.evyy.net/c/11535/341874/3411?sharedid=wpforms' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wpforms_render( 'admin/pages/constant-contact', [ 'sign_up_link' => is_string( $sign_up_link ) ? $sign_up_link : '', 'wpbeginners_guide_link' => 'https://www.wpbeginner.com/beginners-guide/why-you-should-start-building-your-email-list-right-away', ], true ); } } Payments/BuilderReturnNotice.php 0000644 00000007521 15252506741 0013021 0 ustar 00 <?php namespace WPForms\Admin\Payments; use WPForms\Admin\Notice; /** * Queue a "Return to your form" success notice on the Payments settings page * after the user completes a connect flow that they started from the form * builder. * * The notice is rendered through the standard {@see Notice::success()} pipeline * so it inherits the same look-and-feel as "Settings were successfully saved." * * @since 1.10.1.1 */ class BuilderReturnNotice { /** * Query arg used to round-trip the source form ID through the connect flow. * * @since 1.10.1.1 */ const FORM_ID_QUERY_ARG = 'wpforms_return_form_id'; /** * Query arg used by OAuth callbacks to flag the gateway that just connected. * * @since 1.10.1.1 */ const GATEWAY_QUERY_ARG = 'wpforms_connected'; /** * Queue the notice if the OAuth callback round-tripped this gateway's slug * back to the Payments settings page. * * Thin wrapper around {@see self::maybe_queue()} that handles the * `?wpforms_connected=<slug>` query arg check shared by all OAuth-based * gateways (Stripe, Square). * * @since 1.10.1.1 * * @param string $gateway_slug Gateway slug as emitted by the callback * (e.g. "stripe", "square"). * @param string $gateway_label Localized gateway name (e.g. "Stripe"). */ public static function maybe_queue_oauth( string $gateway_slug, string $gateway_label ): void { if ( ! wpforms_is_admin_page( 'settings', 'payments' ) ) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( empty( $_GET[ self::GATEWAY_QUERY_ARG ] ) ) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( sanitize_key( $_GET[ self::GATEWAY_QUERY_ARG ] ) !== $gateway_slug ) { return; } self::maybe_queue( $gateway_label, $gateway_slug ); } /** * Queue the success notice if the request carries a valid return form ID * and the current user can edit that form. * * @since 1.10.1.1 * * @param string $gateway_label Localized gateway name (e.g. "Stripe"). * @param string $gateway_slug Builder payments-panel section slug * (e.g. "stripe", "square", "authorize_net"). * Appended as `§ion=…` so the user lands * back on the same gateway tab. */ public static function maybe_queue( string $gateway_label, string $gateway_slug = '' ): void { $form_id = self::get_return_form_id(); if ( $form_id <= 0 ) { return; } if ( ! wpforms_current_user_can( 'edit_form_single', $form_id ) ) { return; } $form = wpforms()->obj( 'form' )->get( $form_id ); if ( empty( $form ) ) { return; } $builder_url_args = [ 'page' => 'wpforms-builder', 'view' => 'payments', 'form_id' => $form_id, ]; if ( $gateway_slug !== '' ) { $builder_url_args['section'] = sanitize_key( $gateway_slug ); } $builder_url = add_query_arg( $builder_url_args, admin_url( 'admin.php' ) ); $message = sprintf( wp_kses( /* translators: %1$s - Gateway name, %2$s - Builder URL. */ __( 'You have successfully connected to <strong>%1$s</strong>. <a href="%2$s">Return to your form</a> to continue editing.', 'wpforms-lite' ), [ 'strong' => [], 'a' => [ 'href' => [] ], ] ), esc_html( $gateway_label ), esc_url( $builder_url ) ); Notice::success( $message, [ 'slug' => 'wpforms-payments-builder-return-' . $form_id, ] ); } /** * Read and validate the return form ID from the current request. * * @since 1.10.1.1 * * @return int Positive form ID, or 0 if missing/invalid. */ public static function get_return_form_id(): int { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return ! empty( $_GET[ self::FORM_ID_QUERY_ARG ] ) ? absint( $_GET[ self::FORM_ID_QUERY_ARG ] ) : 0; } } Payments/ScreenOptions.php 0000644 00000011337 15252506741 0011664 0 ustar 00 <?php namespace WPForms\Admin\Payments; use WP_Screen; /** * Payments screen options. * * @since 1.8.2 */ class ScreenOptions { /** * Screen id. * * @since 1.8.2 */ const SCREEN_ID = 'wpforms_page_wpforms-payments'; /** * Screen option name. * * @since 1.8.2 */ const PER_PAGE = 'wpforms_payments_per_page'; /** * Screen option name. * * @since 1.8.2 */ const SINGLE = 'wpforms_payments_single'; /** * Initialize. * * @since 1.8.2 */ public function init() { $this->hooks(); } /** * Hooks. * * @since 1.8.2 */ private function hooks() { // Setup screen options - this needs to run early. add_action( 'load-wpforms_page_wpforms-payments', [ $this, 'screen_options' ] ); add_filter( 'screen_settings', [ $this, 'single_screen_settings' ], 10, 2 ); add_filter( 'set-screen-option', [ $this, 'screen_options_set' ], 10, 3 ); add_filter( 'set_screen_option_wpforms_payments_per_page', [ $this, 'screen_options_set' ], 10, 3 ); add_filter( 'set_screen_option_wpforms_payments_single', [ $this, 'screen_options_set' ], 10, 3 ); } /** * Add per-page screen option to the Payments table. * * @since 1.8.2 */ public function screen_options() { $screen = get_current_screen(); if ( ! isset( $screen->id ) || $screen->id !== self::SCREEN_ID ) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( ! empty( $_GET['view'] ) && $_GET['view'] !== 'payments' ) { return; } /** * Filter the number of payments per page default value. * * Notice, the filter will be applied to default value in Screen Options only and still will be able to provide other value. * If you want to change the number of payments per page, use the `wpforms_payments_per_page` filter. * * @since 1.8.2 * * @param int $per_page Number of payments per page. */ $per_page = (int) apply_filters( 'wpforms_admin_payments_screen_options_per_page_default', 20 ); add_screen_option( 'per_page', [ 'label' => esc_html__( 'Number of payments per page:', 'wpforms-lite' ), 'option' => self::PER_PAGE, 'default' => $per_page, ] ); } /** * Returns the screen options markup for the payment single page. * * @since 1.8.2 * * @param string $status The current screen settings. * @param WP_Screen $args WP_Screen object. * * @return string */ public function single_screen_settings( $status, $args ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( $args->id !== self::SCREEN_ID || empty( $_GET['view'] ) || $_GET['view'] !== 'payment' ) { return $status; } $screen_options = self::get_single_page_options(); $advanced_options = [ 'advanced' => __( 'Advanced details', 'wpforms-lite' ), 'log' => __( 'Log', 'wpforms-lite' ), ]; $output = '<fieldset class="metabox-prefs">'; $output .= '<legend>' . esc_html__( 'Additional information', 'wpforms-lite' ) . '</legend>'; $output .= '<div>'; foreach ( $advanced_options as $key => $label ) { $output .= sprintf( '<input name="%1$s" type="checkbox" id="%1$s" value="true" %2$s /><label for="%1$s">%3$s</label>', esc_attr( $key ), ! empty( $screen_options[ $key ] ) ? 'checked="checked"' : '', esc_html( $label ) ); } $output .= '</div></fieldset>'; $output .= '<p class="submit">'; $output .= '<input type="hidden" name="wp_screen_options[option]" value="wpforms_payments_single">'; $output .= '<input type="hidden" name="wp_screen_options[value]" value="true">'; $output .= '<input type="submit" name="screen-options-apply" id="screen-options-apply" class="button button-primary" value="' . esc_html__( 'Apply', 'wpforms-lite' ) . '">'; $output .= wp_nonce_field( 'screen-options-nonce', 'screenoptionnonce', false, false ); $output .= '</p>'; return $output; } /** * Get single page screen options. * * @since 1.8.2 * * @return false|mixed */ public static function get_single_page_options() { return get_user_option( self::SINGLE ); } /** * Payments table per-page screen option value. * * @since 1.8.2 * * @param mixed $status The value to save instead of the option value. * @param string $option Screen option name. * @param mixed $value Screen option value. * * @return mixed */ public function screen_options_set( $status, $option, $value ) { if ( $option === self::PER_PAGE ) { return $value; } // phpcs:disable WordPress.Security.NonceVerification.Missing if ( $option === self::SINGLE ) { return [ 'advanced' => isset( $_POST['advanced'] ) && (bool) $_POST['advanced'], 'log' => isset( $_POST['log'] ) && (bool) $_POST['log'], ]; } // phpcs:enable WordPress.Security.NonceVerification.Missing return $status; } } Payments/Views/Overview/Coupon.php 0000644 00000011475 15252506741 0013242 0 ustar 00 <?php namespace WPForms\Admin\Payments\Views\Overview; use WPForms\Admin\Payments\Payments; /** * Generic functionality for interacting with the Coupons data. * * @since 1.8.4 */ class Coupon { /** * Initialize the Coupon class. * * @since 1.8.4 */ public function init() { $this->hooks(); } /** * Attach hooks for filtering payments by coupon ID. * * @since 1.8.4 */ private function hooks() { // This filter has been added for backward compatibility with older versions of the Coupons addon. add_filter( 'wpforms_admin_payments_views_overview_table_get_columns', [ $this, 'remove_legacy_coupon_column' ], 99, 1 ); // Bail early if the current page is not the Payments page // or if no coupon ID is given in the URL. if ( ! self::is_coupon() ) { return; } add_filter( 'wpforms_db_payments_payment_get_payments_query_after_where', [ $this, 'filter_by_coupon_id' ], 10, 2 ); add_filter( 'wpforms_db_payments_queries_count_all_query_after_where', [ $this, 'filter_by_coupon_id' ], 10, 2 ); add_filter( 'wpforms_admin_payments_views_overview_filters_renewals_by_subscription_id_query_after_where', [ $this, 'filter_by_coupon_id' ], 10, 2 ); add_filter( 'wpforms_admin_payments_views_overview_search_inner_join_query', [ $this, 'join_search_by_coupon_id' ], 10, 2 ); } /** * Remove the legacy coupon column from the Payments page. * * This function has been added for backward compatibility with older versions of the Coupons addon. * The legacy coupon column is no longer used by the Coupons addon. * * @since 1.8.4 * * @param array $columns List of columns to be displayed on the Payments page. * * @return array */ public function remove_legacy_coupon_column( $columns ) { // Bail early if the Coupons addon is not active. if ( ! $this->is_addon_active() ) { return $columns; } // Remove the legacy coupon column from the Payments page. unset( $columns['coupon_id'] ); return $columns; } /** * Retrieve payment entries based on a given coupon ID. * * @since 1.8.4 * * @param string $after_where SQL query after the WHERE clause. * @param array $args Query arguments. * * @return string */ public function filter_by_coupon_id( $after_where, $args ) { // Check if the query is for the Payments Overview table. // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( empty( $args['table_query'] ) ) { return $after_where; } // Retrieve the coupon ID from the URL. // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.NonceVerification.Recommended $coupon_id = absint( $_GET['coupon_id'] ); global $wpdb; $table_name = wpforms()->obj( 'payment_meta' )->table_name; // Prepare and return the modified SQL query. // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared return $wpdb->prepare( " AND EXISTS ( SELECT 1 FROM {$table_name} AS pm_coupon WHERE pm_coupon.payment_id = p.id AND pm_coupon.meta_key = 'coupon_id' AND pm_coupon.meta_value = %d )", $coupon_id ); } /** * Further filter down the search results by coupon ID. * * @since 1.8.4 * * @param string $query The SQL JOIN clause. * @param int $n The number of the JOIN clause. * * @return string */ public function join_search_by_coupon_id( $query, $n ) { // Retrieve the coupon ID from the URL. // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.NonceVerification.Recommended $coupon_id = absint( $_GET['coupon_id'] ); // Retrieve the global database instance. global $wpdb; $n = absint( $n ); $table_name = wpforms()->obj( 'payment_meta' )->table_name; // Build the derived query using a prepared statement. // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared $derived_query = $wpdb->prepare( "RIGHT JOIN ( SELECT payment_id, meta_key, meta_value FROM {$table_name} WHERE meta_key = 'coupon_id' AND meta_value = %d ) AS pm_coupon{$n} ON p.id = pm_coupon{$n}.payment_id", $coupon_id ); // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared // Combine the original query and the derived query. return "$query $derived_query"; } /** * Determine if the overview page is being viewed, and coupon ID is given. * * @since 1.8.4 * * @return bool */ public static function is_coupon() { // Check if the URL parameters contain a coupon ID and if the current page is the Payments page. // phpcs:ignore WordPress.Security.NonceVerification.Recommended return ! empty( $_GET['coupon_id'] ) && ! empty( $_GET['page'] ) && $_GET['page'] === Payments::SLUG; } /** * Determine whether the addon is activated. * * @since 1.8.4 * * @return bool */ private function is_addon_active() { return function_exists( 'wpforms_coupons' ); } } Payments/Views/Overview/Table.php 0000644 00000111532 15252506741 0013021 0 ustar 00 <?php namespace WPForms\Admin\Payments\Views\Overview; if ( ! defined( 'ABSPATH' ) ) { exit; } use WPForms\Db\Payments\ValueValidator; use WPForms\Db\Payments\Queries; if ( ! class_exists( 'WP_List_Table' ) ) { require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php'; } /** * Payments Overview Table class. * * @since 1.8.2 */ class Table extends \WP_List_Table { /** * Trait for using notices. * * @since 1.8.4 */ use Traits\ResetNotices; /** * Payment type: one-time. * * @since 1.8.2 * * @var string */ const ONE_TIME = 'one-time'; /** * Payment status: trash. * * @since 1.8.2 * * @var string */ const TRASH = 'trash'; /** * Total number of payments. * * @since 1.8.2 * * @var array */ private $counts; /** * Table query arguments. * * @since 1.8.4 * * @var array */ private $table_query_args = []; /** * Retrieve the table columns. * * @since 1.8.2 * * @return array $columns Array of all the list table columns. */ public function get_columns() { static $columns; if ( ! empty( $columns ) ) { return $columns; } $columns = [ 'cb' => '<input type="checkbox" />', 'title' => esc_html__( 'Payment', 'wpforms-lite' ), 'date' => esc_html__( 'Date', 'wpforms-lite' ), ]; if ( wpforms()->obj( 'payment_queries' )->has_different_values( 'gateway' ) ) { $columns['gateway'] = esc_html__( 'Gateway', 'wpforms-lite' ); } if ( wpforms()->obj( 'payment_queries' )->has_different_values( 'type' ) ) { $columns['type'] = esc_html__( 'Type', 'wpforms-lite' ); } if ( wpforms()->obj( 'payment_meta' )->is_valid_meta_by_meta_key( 'coupon_id' ) ) { $columns['coupon'] = esc_html__( 'Coupon', 'wpforms-lite' ); } $columns['total'] = esc_html__( 'Total', 'wpforms-lite' ); if ( wpforms()->obj( 'payment_queries' )->has_subscription() ) { $columns['subscription'] = esc_html__( 'Subscription', 'wpforms-lite' ); } $columns['form'] = esc_html__( 'Form', 'wpforms-lite' ); $columns['status'] = esc_html__( 'Status', 'wpforms-lite' ); /** * Filters the columns in the Payments Overview table. * * @since 1.8.2 * * @param array $columns Array of columns. */ return (array) apply_filters( 'wpforms_admin_payments_views_overview_table_get_columns', $columns ); } /** * Determine whether it is a trash view. * * @since 1.8.2 * * @return bool */ private function is_trash_view() { return $this->is_current_view( 'trash' ); } /** * Define the table's sortable columns. * * @since 1.8.2 * * @return array Array of all the sortable columns. */ protected function get_sortable_columns() { return [ 'title' => [ 'id', false ], 'date' => [ 'date', false ], 'total' => [ 'total', false ], ]; } /** * Prepare the table with different parameters, pagination, columns and table elements. * * @since 1.8.2 */ public function prepare_items() { $page = $this->get_pagenum(); $per_page = $this->get_items_per_page( 'wpforms_payments_per_page', 20 ); $data_args = [ 'number' => $per_page, 'offset' => $per_page * ( $page - 1 ), 'orderby' => $this->get_order_by(), 'search' => $this->get_search_query(), 'search_conditions' => $this->get_search_conditions(), 'status' => $this->get_valid_status_from_request(), 'is_published' => $this->is_trash_view() ? 0 : 1, ]; // Set the table query arguments for later use. $this->table_query_args = $this->prepare_table_query_args( $data_args ); // Retrieve the payment records for the given data arguments. $this->items = wpforms()->obj( 'payment' )->get_payments( $this->table_query_args ); // Setup the counts. $this->setup_counts(); // Check if we can continue. $this->can_prepare_records(); // Get the proper total number of records depending on the current status view. $total_items = $this->get_valid_status_count_from_request(); $total_pages = ceil( $total_items / $per_page ); // Finalize pagination. $this->set_pagination_args( [ 'total_items' => $total_items, 'total_pages' => (int) $total_pages, 'per_page' => $per_page, ] ); } /** * Prepare the query arguments for the overview table. * * @since 1.8.4 * * @param array $args Array of data arguments. * * @return array */ private function prepare_table_query_args( $args = [] ) { // phpcs:disable WordPress.Security.NonceVerification.Recommended return wp_parse_args( $args, [ 'table_query' => true, 'order' => isset( $_GET['order'] ) ? sanitize_key( $_GET['order'] ) : 'DESC', 'form_id' => isset( $_GET['form_id'] ) ? absint( $_GET['form_id'] ) : '', 'type' => isset( $_GET['type'] ) ? sanitize_text_field( wp_unslash( $_GET['type'] ) ) : '', 'gateway' => isset( $_GET['gateway'] ) ? sanitize_text_field( wp_unslash( $_GET['gateway'] ) ) : '', 'subscription_status' => isset( $_GET['subscription_status'] ) ? sanitize_text_field( wp_unslash( $_GET['subscription_status'] ) ) : '', ] ); // phpcs:enable WordPress.Security.NonceVerification.Recommended } /** * Message to be displayed when there are no payments. * * @since 1.8.2 */ public function no_items() { if ( $this->is_trash_view() ) { esc_html_e( 'No payments found in the trash.', 'wpforms-lite' ); return; } if ( $this->is_current_view( 'search' ) ) { esc_html_e( 'No payments found, please try a different search.', 'wpforms-lite' ); return; } esc_html_e( 'No payments found.', 'wpforms-lite' ); } /** * Generates content for a single row of the table. * * @since 1.8.4 * * @param array $item Item data. */ public function single_row( $item ) { // Leave the default row if the item is not a subscription. if ( empty( $item['subscription_id'] ) || empty( $item['subscription_status'] ) ) { parent::single_row( $item ); return; } $has_renewal = wpforms()->obj( 'payment_queries' )->if_subscription_has_renewal( $item['subscription_id'] ); // Leave the default row if the subscription has no renewal. if ( ! $has_renewal ) { parent::single_row( $item ); return; } echo '<tr class="subscription-has-renewal">'; $this->single_row_columns( $item ); echo '</tr>'; } /** * Column default values. * * @since 1.8.2 * * @param array $item Item data. * @param string $column_name Column name. * * @return string */ protected function column_default( $item, $column_name ) { if ( method_exists( $this, "get_column_{$column_name}" ) ) { return $this->{"get_column_{$column_name}"}( $item ); } if ( isset( $item[ $column_name ] ) ) { return esc_html( $item[ $column_name ] ); } /** * Allow to filter default column value. * * @since 1.8.2 * * @param string $value Default column value. * @param array $item Item data. * @param string $column_name Column name. */ return apply_filters( 'wpforms_admin_payments_views_overview_table_column_default_value', '', $item, $column_name ); } /** * Define the checkbox column. * * @since 1.8.2 * * @param array $item The current item. * * @return string */ protected function column_cb( $item ) { return '<input type="checkbox" name="payment_id[]" value="' . absint( $item['id'] ) . '" />'; } /** * Prepare the items and display the table. * * @since 1.8.2 */ public function display() { ?> <form id="wpforms-payments-table" method="GET" action="<?php echo esc_url( Page::get_url() ); ?>"> <?php $this->display_hidden_fields(); $this->show_reset_filter(); $this->views(); $this->search_box( esc_html__( 'Search Payments', 'wpforms-lite' ), 'wpforms-payments-search-input' ); parent::display(); ?> </form> <?php } /** * Extra filtering controls to be displayed between bulk actions and pagination. * * @since 1.8.4 * * @param string $which Position of the extra controls: 'top' or 'bottom'. */ protected function extra_tablenav( $which ) { // We only want to show the extra controls on the top. if ( $which !== 'top' ) { return; } $tablenav_data = [ 'type' => [ 'data' => ValueValidator::get_allowed_types(), 'plural_label' => __( 'types', 'wpforms-lite' ), ], 'gateway' => [ 'data' => ValueValidator::get_allowed_gateways(), 'plural_label' => __( 'gateways', 'wpforms-lite' ), ], 'subscription_status' => [ 'data' => ValueValidator::get_allowed_subscription_statuses(), 'plural_label' => __( 'subscriptions', 'wpforms-lite' ), ], ]; // Special case for showing all available types, gateways and subscription statuses. if ( ! $this->has_items() ) { unset( $this->table_query_args['type'], $this->table_query_args['gateway'], $this->table_query_args['subscription_status'] ); } // Output the reset filter notice. // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wpforms_render( 'admin/payments/tablenav-filters', [ 'filters' => $this->prepare_extra_tablenav_filters( $tablenav_data ), ], true ); } /** * Iterate through each given filter option and remove the ones that don't have any records. * * @since 1.8.4 * * @param array $tablenav_data Array of filter options. * * @return string */ private function prepare_extra_tablenav_filters( $tablenav_data ) { $rendered_nav_data = []; foreach ( $tablenav_data as $nav_key => $nav_attributes ) { $filtered_data = []; // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $selected = isset( $_GET[ $nav_key ] ) ? explode( '|', wp_unslash( $_GET[ $nav_key ] ) ) : []; foreach ( $nav_attributes['data'] as $attribute_key => $attribute_value ) { $query_args = array_merge( $this->table_query_args, [ $nav_key => $attribute_key ] ); if ( in_array( $attribute_key, $selected, true ) || wpforms()->obj( 'payment_queries' )->if_exists( $query_args ) ) { $filtered_data[ $attribute_key ] = $attribute_value; } } $selected = array_filter( $selected, static function ( $value ) use ( $filtered_data ) { return isset( $filtered_data[ $value ] ); } ); if ( empty( $filtered_data ) || ( count( $filtered_data ) === 1 && empty( $selected ) ) ) { continue; } $rendered_nav_data[] = wpforms_render( 'admin/payments/tablenav-filter-multiselect', [ 'selected' => $selected, 'options' => $filtered_data, 'name' => $nav_key, 'data_settings' => [ 'i18n' => [ 'multiple' => sprintf( /* translators: %s - plural label. */ __( 'Multiple %s selected', 'wpforms-lite' ), esc_attr( $nav_attributes['plural_label'] ) ), 'all' => sprintf( /* translators: %s - plural label. */ __( 'All %s', 'wpforms-lite' ), esc_attr( $nav_attributes['plural_label'] ) ), ], ], ], true ); } return implode( '', $rendered_nav_data ); } /** * Display the search box. * * @since 1.8.2 * * @param string $text The 'submit' button label. * @param string $input_id ID attribute value for the search input field. */ public function search_box( $text, $input_id ) { $search_where = $this->get_search_where_key(); $search_mode = $this->get_search_mode_key(); ?> <p class="search-box"> <label class="screen-reader-text" for="search_where"><?php esc_html_e( 'Select which field to use when searching for payments', 'wpforms-lite' ); ?></label> <select name="search_where"> <option value="<?php echo esc_attr( Search::TITLE ); ?>" <?php selected( $search_where, Search::TITLE ); ?> ><?php echo esc_html( $this->get_search_where( Search::TITLE ) ); ?></option> <option value="<?php echo esc_attr( Search::TRANSACTION_ID ); ?>" <?php selected( $search_where, Search::TRANSACTION_ID ); ?> ><?php echo esc_html( $this->get_search_where( Search::TRANSACTION_ID ) ); ?></option> <option value="<?php echo esc_attr( Search::SUBSCRIPTION_ID ); ?>" <?php selected( $search_where, Search::SUBSCRIPTION_ID ); ?> ><?php echo esc_html( $this->get_search_where( Search::SUBSCRIPTION_ID ) ); ?></option> <option value="<?php echo esc_attr( Search::EMAIL ); ?>" <?php selected( $search_where, Search::EMAIL ); ?> ><?php echo esc_html( $this->get_search_where( Search::EMAIL ) ); ?></option> <option value="<?php echo esc_attr( Search::CREDIT_CARD ); ?>" <?php selected( $search_where, Search::CREDIT_CARD ); ?> ><?php echo esc_html( $this->get_search_where( Search::CREDIT_CARD ) ); ?></option> <option value="<?php echo esc_attr( Search::ANY ); ?>" <?php selected( $search_where, Search::ANY ); ?> ><?php echo esc_html( $this->get_search_where( Search::ANY ) ); ?></option> </select> <label class="screen-reader-text" for="search_mode"><?php esc_html_e( 'Select which comparison method to use when searching for payments', 'wpforms-lite' ); ?></label> <select name="search_mode"> <option value="<?php echo esc_attr( Search::MODE_CONTAINS ); ?>" <?php selected( $search_mode, Search::MODE_CONTAINS ); ?> ><?php echo esc_html( $this->get_search_mode( Search::MODE_CONTAINS ) ); ?></option> <option value="<?php echo esc_attr( Search::MODE_EQUALS ); ?>" <?php selected( $search_mode, Search::MODE_EQUALS ); ?> ><?php echo esc_html( $this->get_search_mode( Search::MODE_EQUALS ) ); ?></option> <option value="<?php echo esc_attr( Search::MODE_STARTS ); ?>" <?php selected( $search_mode, Search::MODE_STARTS ); ?> ><?php echo esc_html( $this->get_search_mode( Search::MODE_STARTS ) ); ?></option> </select> <label class="screen-reader-text" for="<?php echo esc_attr( $input_id ); ?>"><?php echo esc_html( $text ); ?></label> <input type="search" id="<?php echo esc_attr( $input_id ); ?>" name="s" value="<?php echo esc_attr( $this->get_search_query() ); ?>" /> <input type="submit" class="button" value="<?php echo esc_attr( $text ); ?>" /> </p> <?php } /** * Get bulk actions to be displayed in bulk action dropdown. * * @since 1.8.2 * * @return array */ protected function get_bulk_actions() { if ( $this->is_trash_view() ) { return [ 'restore' => esc_html__( 'Restore', 'wpforms-lite' ), 'delete' => esc_html__( 'Delete Permanently', 'wpforms-lite' ), ]; } return [ 'trash' => esc_html__( 'Move to Trash', 'wpforms-lite' ), ]; } /** * Generates the table navigation above or below the table. * * @since 1.8.2 * * @param string $which The location of the bulk actions: 'top' or 'bottom'. */ protected function display_tablenav( $which ) { if ( $this->has_items() ) { parent::display_tablenav( $which ); return; } echo '<div class="tablenav ' . esc_attr( $which ) . '">'; if ( $this->is_trash_view() ) { echo '<div class="alignleft actions bulkactions">'; $this->bulk_actions(); echo '</div>'; } $this->extra_tablenav( $which ); echo '<br class="clear" />'; echo '</div>'; } /** * List of CSS classes for the "WP_List_Table" table tag. * * @global string $mode List table view mode. * * @since 1.8.2 * * @return array */ protected function get_table_classes() { global $mode; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited $mode = get_user_setting( 'posts_list_mode', 'list' ); $mode_class = esc_attr( 'table-view-' . $mode ); $classes = [ 'widefat', 'striped', 'wpforms-table-list', 'wpforms-table-list-payments', $mode_class, ]; // For styling purposes, we'll add a dedicated class name for determining the number of visible columns. // The ideal threshold for applying responsive styling is set at "5" columns based on the need for "Tablet" view. $columns_class = $this->get_column_count() > 5 ? 'many' : 'few'; $classes[] = "has-{$columns_class}-columns"; return $classes; } /** * Get valid status from request. * * @since 1.8.2 * * @return string */ private function get_valid_status_from_request() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash return ! empty( $_REQUEST['status'] ) && ( ValueValidator::is_valid( $_REQUEST['status'], 'status' ) || $_REQUEST['status'] === self::TRASH ) ? $_REQUEST['status'] : ''; } /** * Get number of payments for the current status. * Note that this function also validates the status internally. * * @since 1.8.4 * * @return string */ private function get_valid_status_count_from_request() { // Retrieve the current status. $current_status = $this->get_valid_status_from_request(); return $current_status && isset( $this->counts[ $current_status ] ) ? $this->counts[ $current_status ] : $this->counts['total']; } /** * Get search where value. * * @since 1.8.2 * * @param string $search_key Search where key. * * @return string Return default search where value if not valid key provided. */ private function get_search_where( $search_key ) { $allowed_values = $this->get_allowed_search_where(); return $search_key && isset( $allowed_values[ $search_key ] ) ? $allowed_values[ $search_key ] : $allowed_values[ Search::TITLE ]; } /** * Get search where key. * * @since 1.8.2 * * @return string */ private function get_search_where_key() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $where_key = isset( $_GET['search_where'] ) ? sanitize_key( $_GET['search_where'] ) : ''; return isset( $this->get_allowed_search_where()[ $where_key ] ) ? $where_key : Search::TITLE; } /** * Get allowed search where values. * * @since 1.8.2 * * @return array */ private function get_allowed_search_where() { static $search_values; if ( ! $search_values ) { $search_values = [ Search::TITLE => __( 'Payment Title', 'wpforms-lite' ), Search::TRANSACTION_ID => __( 'Transaction ID', 'wpforms-lite' ), Search::EMAIL => __( 'Customer Email', 'wpforms-lite' ), Search::SUBSCRIPTION_ID => __( 'Subscription ID', 'wpforms-lite' ), Search::CREDIT_CARD => __( 'Last 4 digits of credit card', 'wpforms-lite' ), Search::ANY => __( 'Any payment field', 'wpforms-lite' ), ]; } return $search_values; } /** * Get search where value. * * @since 1.8.2 * * @param string $mode_key Search mode key. * * @return string Return default search mode value if not valid key provided. */ private function get_search_mode( $mode_key ) { $allowed_modes = $this->get_allowed_search_modes(); return $mode_key && isset( $allowed_modes[ $mode_key ] ) ? $allowed_modes[ $mode_key ] : $allowed_modes[ Search::MODE_EQUALS ]; } /** * Get search mode key. * * @since 1.8.2 * * @return string */ private function get_search_mode_key() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $where_mode = isset( $_GET['search_mode'] ) ? sanitize_key( $_GET['search_mode'] ) : ''; return isset( $this->get_allowed_search_modes()[ $where_mode ] ) ? $where_mode : Search::MODE_CONTAINS; } /** * Get allowed search mode params. * * @since 1.8.2 * * @return array */ private function get_allowed_search_modes() { static $search_modes; if ( ! $search_modes ) { $search_modes = [ Search::MODE_CONTAINS => __( 'contains', 'wpforms-lite' ), Search::MODE_EQUALS => __( 'equals', 'wpforms-lite' ), Search::MODE_STARTS => __( 'starts with', 'wpforms-lite' ), ]; } return $search_modes; } /** * Prepare counters. * * @since 1.8.2 */ private function setup_counts() { // Define the general views with their respective arguments. $views = [ 'published' => [ 'is_published' => 1, 'status' => '', ], 'trash' => [ 'is_published' => 0, 'status' => '', ], ]; // Generate filterable status views with their respective arguments. foreach ( ValueValidator::get_allowed_one_time_statuses() as $status => $label ) { $views[ $status ] = [ 'is_published' => 1, 'status' => $status, ]; } // Calculate the counts for each view and store them in the $this->counts array. foreach ( $views as $status => $status_args ) { $this->counts[ $status ] = wpforms()->obj( 'payment_queries' )->count_all( array_merge( $this->table_query_args, $status_args ) ); } // If the current view is the trash view, set the 'total' count to the 'trash' count. if ( $this->is_trash_view() ) { $this->counts['total'] = $this->counts['trash']; return; } // Otherwise, set the 'total' count to the 'published' count. $this->counts['total'] = $this->counts['published']; } /** * Get the orderby value. * * @since 1.8.2 * * @return string */ private function get_order_by() { // phpcs:disable WordPress.Security.NonceVerification.Recommended if ( ! isset( $_GET['orderby'] ) ) { return 'id'; } if ( $_GET['orderby'] === 'date' ) { return 'date_updated_gmt'; } if ( $_GET['orderby'] === 'total' ) { return 'total_amount'; } return 'id'; // phpcs:enable WordPress.Security.NonceVerification.Recommended } /** * Get payment column value. * * @since 1.8.2 * * @param array $item Payment item. * * @return string */ private function get_column_title( array $item ) { $title = $this->get_payment_title( $item ); $na_status = empty( $title ) ? sprintf( '<span class="payment-title-is-empty">- %s</span>', Helpers::get_placeholder_na_text() ) : ''; if ( ! $item['is_published'] ) { return sprintf( '<span>#%1$d %2$s</span> %3$s', $item['id'], esc_html( $title ), $na_status ); } $single_url = add_query_arg( [ 'page' => 'wpforms-payments', 'view' => 'payment', 'payment_id' => absint( $item['id'] ), ], admin_url( 'admin.php' ) ); return sprintf( '<a href="%1$s">#%2$d %3$s</a> %4$s', esc_url( $single_url ), $item['id'], esc_html( $title ), $na_status ); } /** * Get date column value. * * @since 1.8.2 * * @param array $item Payment item. * * @return string */ private function get_column_date( array $item ): string { $item_date_gmt = $item['date_updated_gmt']; $item_date = get_date_from_gmt( $item_date_gmt, 'Y-m-d H:i' ); $item_timestamp = strtotime( $item_date ); $item_timestamp_gmt = strtotime( $item_date_gmt ); // Check if the $timestamp represents a time within the last 24 hours and is not in the future. if ( $item_timestamp_gmt <= time() ) { /* translators: %s - relative time difference, e.g. "5 minutes", "12 days". */ $human = sprintf( esc_html__( '%s ago', 'wpforms-lite' ), human_time_diff( $item_timestamp_gmt ) ); } else { $human = wpforms_datetime_format( $item_timestamp, 'M j, Y', false ); } return sprintf( '<span title="%s">%s</span>', wpforms_datetime_format( $item_timestamp, 'Y-m-d H:i', false ), $human ); } /** * Get gateway column value. * * @since 1.8.2 * * @param array $item Payment item. * * @return string */ private function get_column_gateway( array $item ) { if ( ! isset( $item['gateway'] ) || ! ValueValidator::is_valid( $item['gateway'], 'gateway' ) ) { return ''; } return ValueValidator::get_allowed_gateways()[ $item['gateway'] ]; } /** * Get total column value. * * @since 1.8.2 * * @param array $item Payment item. * * @return string */ private function get_column_total( array $item ) { return esc_html( $this->get_formatted_amount_from_item( $item ) ); } /** * Get form column value. * * @since 1.8.2 * * @param array $item Payment item. * * @return string */ private function get_column_form( array $item ) { // Display "N/A" placeholder text if the form is not found or not published. if ( empty( $item['form_id'] ) || get_post_status( $item['form_id'] ) !== 'publish' ) { return Helpers::get_placeholder_na_text(); } $form = wpforms()->obj( 'form' )->get( $item['form_id'] ); if ( ! $form || $form->post_status !== 'publish' ) { return Helpers::get_placeholder_na_text(); } // Display the form name with a link to the form builder. $name = ! empty( $form->post_title ) ? $form->post_title : $form->post_name; $url = add_query_arg( 'form_id', absint( $form->ID ), remove_query_arg( 'paged' ) ); return sprintf( '<a href="%s">%s</a>', esc_url( $url ), wp_kses_post( $name ) ); } /** * Get status column value. * * @since 1.8.2 * * @param array $item Payment item. * * @return string */ private function get_column_status( array $item ) { if ( ! isset( $item['status'] ) || ! ValueValidator::is_valid( $item['status'], 'status' ) ) { return Helpers::get_placeholder_na_text(); } return sprintf( wp_kses( '<span class="wpforms-payment-status status-%1$s">%2$s</span>', [ 'span' => [ 'class' => [], ], 'i' => [ 'class' => [], 'title' => [], ], ] ), strtolower( $item['status'] ), $item['status'] === 'partrefund' ? __( '% Refunded', 'wpforms-lite' ) : ValueValidator::get_allowed_statuses()[ $item['status'] ] ); } /** * Get subscription column value. * * @since 1.8.2 * * @param array $item Payment item. * * @return string */ private function get_column_subscription( array $item ) { if ( $item['type'] === self::ONE_TIME ) { return Helpers::get_placeholder_na_text(); } $amount = $this->get_formatted_amount_from_item( $item ); $description = Helpers::get_subscription_description( $item['id'], $amount ); $status = $this->get_subscription_status( $item ); return sprintf( '<span class="wpforms-subscription-status status-%1$s" title="%2$s">%3$s</span>', sanitize_html_class( $status ), $status ? ValueValidator::get_allowed_subscription_statuses()[ $status ] : '', $description ); } /** * Get type column value. * * @since 1.8.2 * * @param array $item Payment item. * * @return string */ private function get_column_type( array $item ) { if ( ! isset( $item['type'] ) || ! ValueValidator::is_valid( $item['type'], 'type' ) ) { return Helpers::get_placeholder_na_text(); } return ValueValidator::get_allowed_types()[ $item['type'] ]; } /** * Show the coupon code used for the payment. * If the coupon code is not found, show N/A. * * @since 1.8.4 * * @param array $item Payment item. * * @return string */ private function get_column_coupon( $item ) { $payment_meta = wpforms()->obj( 'payment_meta' )->get_all( $item['id'] ); // If the coupon info is empty, show N/A. if ( empty( $payment_meta['coupon_info'] ) || empty( $payment_meta['coupon_id'] ) ) { return Helpers::get_placeholder_na_text(); } $url = add_query_arg( 'coupon_id', $payment_meta['coupon_id']->value, remove_query_arg( 'paged' ) ); return sprintf( '<a href="%1$s" aria-label="%2$s">%3$s</a>', esc_url( $url ), esc_attr__( 'Filter entries by coupon', 'wpforms-lite' ), esc_html( $this->get_coupon_name_by_info( $payment_meta['coupon_info']->value ) ) ); } /** * Get subscription status. * * @since 1.8.4 * * @param array $item Payment item. * * @return string */ private function get_subscription_status( $item ) { if ( ! in_array( $item['type'], [ 'subscription', 'renewal' ], true ) ) { return ''; } if ( $item['type'] === 'subscription' ) { return $item['subscription_status']; } // For renewals, get subscription status from the parent subscription. $parent_subscription = ( new Queries() )->get_subscription( $item['subscription_id'] ); return ! empty( $parent_subscription->subscription_status ) ? $parent_subscription->subscription_status : ''; } /** * Get payment title. * * @param array $item Payment item. * * @since 1.8.2 * * @return string */ private function get_payment_title( array $item ) { if ( empty( $item['title'] ) ) { return ''; } return ' - ' . $item['title']; } /** * Get subscription icon. * * @since 1.8.2 * * @param array $item Payment item. * * @return string */ private function get_subscription_status_icon( array $item ) { if ( empty( $item['subscription_id'] ) ) { return ''; } return '<span class="dashicons dashicons-marker"></span>'; } /** * Get formatted amount from item. * * @since 1.8.2 * * @param array $item Payment item. * * @return string */ private function get_formatted_amount_from_item( $item ) { if ( empty( $item['total_amount'] ) ) { return ''; } return wpforms_format_amount( wpforms_sanitize_amount( $item['total_amount'], $item['currency'] ), true, $item['currency'] ); } /** * Get selectors which will be displayed over the bulk action menu. * * @since 1.8.2 * * @return array */ protected function get_views() { $base = remove_query_arg( [ 'status', 'paged' ] ); $is_trash_view = $this->is_trash_view(); $views = [ 'all' => sprintf( '<a href="%s"%s>%s <span class="count">(%d)</span></a>', esc_url( $base ), $this->is_current_view( 'all' ) ? ' class="current"' : '', esc_html__( 'All', 'wpforms-lite' ), (int) $this->counts['published'] ), ]; // Iterate through the filterable statuses and add them to the "$views" array. $views = array_merge( $views, $this->get_views_for_filterable_statuses( $base ) ); /** This filter is documented in \WPForms\Admin\Payments\Views\Overview\Table::display_tablenav(). */ if ( $this->counts['trash'] || $is_trash_view ) { $views['trash'] = sprintf( '<a href="%s"%s>%s <span class="count">(%d)</span></a>', esc_url( add_query_arg( [ 'status' => 'trash' ], $base ) ), $is_trash_view ? ' class="current"' : '', esc_html__( 'Trash', 'wpforms-lite' ), (int) $this->counts['trash'] ); } return array_filter( $views ); } /** * Determine whether it is a passed view. * * @since 1.8.2 * * @param string $view Current view to validate. * * @return bool */ private function is_current_view( $view ) { // phpcs:disable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash if ( $view === 'trash' && isset( $_GET['status'] ) && $_GET['status'] === self::TRASH ) { return true; } if ( ( $view === 'search' || $view === 'all' ) && Search::is_search() ) { return ! isset( $_GET['status'] ); } if ( ValueValidator::is_valid( $view, 'status' ) && isset( $_GET['status'] ) && $_GET['status'] === $view ) { return true; } if ( $view === 'all' && ! isset( $_GET['status'] ) && ! Search::is_search() ) { return true; } // phpcs:enable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash return false; } /** * Get value provided in search field. * * @since 1.8.2 * * @return string */ private function get_search_query() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotValidated return Search::is_search() ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : ''; } /** * Get search conditions. * * @since 1.8.2 * * @return array */ private function get_search_conditions() { if ( ! Search::is_search() ) { return []; } return [ 'search_where' => $this->get_search_where_key(), 'search_mode' => $this->get_search_mode_key(), ]; } /** * This function is responsible for determining whether the table items could be displayed. * * @since 1.8.4 */ private function can_prepare_records() { // phpcs:disable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash if ( isset( $_GET['form_id'] ) && get_post_status( $_GET['form_id'] ) !== 'publish' ) { wp_safe_redirect( Page::get_url() ); exit; } if ( isset( $_GET['status'] ) && $_GET['status'] !== $this->get_valid_status_from_request() ) { wp_safe_redirect( Page::get_url() ); exit; } if ( isset( $_GET['coupon_id'] ) && ! wpforms()->obj( 'payment_meta' )->is_valid_meta( 'coupon_id', absint( $_GET['coupon_id'] ) ) ) { wp_safe_redirect( Page::get_url() ); exit; } // Validate the "type," "gateway," and "subscription_status" parameters. foreach ( [ 'type', 'gateway', 'subscription_status' ] as $column_name ) { // Leave the loop if the parameter is not set. if ( empty( $_GET[ $column_name ] ) ) { continue; } foreach ( explode( '|', $_GET[ $column_name ] ) as $value ) { if ( ! ValueValidator::is_valid( $value, $column_name ) ) { wp_safe_redirect( Page::get_url() ); exit; } } } // phpcs:enable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash } /** * Display table form's hidden fields. * * @since 1.8.2 */ private function display_hidden_fields() { ?> <input type="hidden" name="page" value="wpforms-payments"> <input type="hidden" name="paged" value="1"> <?php $this->display_status_hidden_field(); $this->display_order_hidden_fields(); $this->display_coupon_id_hidden_field(); $this->display_form_id_hidden_field(); } /** * Display hidden field with status value. * * @since 1.8.2 */ private function display_status_hidden_field() { $status = $this->get_valid_status_from_request(); // Bail early if status is not valid. if ( ! $status ) { return; } // Output the hidden field. // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wpforms_render( 'admin/payments/hidden-field', [ 'name' => 'status', 'value' => $status, ], true ); } /** * Display hidden fields with order and orderby values. * * @since 1.8.2 */ private function display_order_hidden_fields() { // phpcs:disable WordPress.Security.NonceVerification.Recommended foreach ( [ 'orderby', 'order' ] as $param ) { // Skip if param is not set. if ( empty( $_GET[ $param ] ) ) { continue; } // Output the hidden field. // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wpforms_render( 'admin/payments/hidden-field', [ 'name' => $param, 'value' => sanitize_text_field( wp_unslash( $_GET[ $param ] ) ), ], true ); } // phpcs:enable WordPress.Security.NonceVerification.Recommended } /** * Display hidden field with coupon ID value. * * @since 1.8.4 */ private function display_coupon_id_hidden_field() { // phpcs:disable WordPress.Security.NonceVerification.Recommended if ( empty( $_GET['coupon_id'] ) ) { return; } // Output the hidden field. // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wpforms_render( 'admin/payments/hidden-field', [ 'name' => 'coupon_id', 'value' => absint( $_GET['coupon_id'] ), ], true ); // phpcs:enable WordPress.Security.NonceVerification.Recommended } /** * Display hidden field with form ID value. * * @since 1.8.4 */ private function display_form_id_hidden_field() { // phpcs:disable WordPress.Security.NonceVerification.Recommended if ( empty( $_GET['form_id'] ) ) { return; } // Output the hidden field. // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wpforms_render( 'admin/payments/hidden-field', [ 'name' => 'form_id', 'value' => absint( $_GET['form_id'] ), ], true ); // phpcs:enable WordPress.Security.NonceVerification.Recommended } /** * Get the coupon name from the coupon info. * * @since 1.8.4 * * @param string $coupon_info Coupon information. * * @return string */ private function get_coupon_name_by_info( $coupon_info ) { // Extract the coupon code from the coupon info using regex. if ( preg_match( '/^(.+)/i', $coupon_info, $coupon_code ) ) { return $coupon_code[0]; } return Helpers::get_placeholder_na_text(); } /** * Get the filterable statuses views for the overview table. * * @since 1.8.4 * * @param string $base Base URL for the view links. * * @return array */ private function get_views_for_filterable_statuses( $base ) { $views = []; $statuses = ValueValidator::get_allowed_one_time_statuses(); // Remove the "Partially Refunded" status from the views. unset( $statuses['partrefund'] ); foreach ( $statuses as $status => $label ) { // Skip if the count is zero and the view is not the current status. if ( ! $this->counts[ $status ] && ! $this->is_current_view( $status ) ) { continue; } // Add the view link to the $views array with the status as the key. $views[ $status ] = sprintf( '<a href="%s"%s>%s <span class="count">(%d)</span></a>', esc_url( add_query_arg( [ 'status' => $status ], $base ) ), $this->is_current_view( $status ) ? ' class="current"' : '', esc_html( $label ), (int) $this->counts[ $status ] ); } return $views; } } Payments/Views/Overview/Filters.php 0000644 00000011452 15252506741 0013402 0 ustar 00 <?php namespace WPForms\Admin\Payments\Views\Overview; /** * Class for extending SQL queries for filtering payments by multicheckbox fields. * * @since 1.8.4 */ class Filters { /** * Initialize the Filters class. * * @since 1.8.4 */ public function init() { $this->hooks(); } /** * Attach hooks for filtering payments by multicheckbox fields. * * @since 1.8.4 */ private function hooks() { add_filter( 'wpforms_db_payments_payment_get_payments_query_after_where', [ $this, 'add_renewals_by_subscription_id' ], 10, 2 ); add_filter( 'wpforms_db_payments_queries_count_all_query_after_where', [ $this, 'count_renewals_by_subscription_id' ], 10, 2 ); add_filter( 'wpforms_db_payments_queries_count_if_exists_after_where', [ $this, 'exists_renewals_by_subscription_id' ], 10, 2 ); } /** * Add renewals to the query. * * @since 1.8.4 * * @param string $after_where SQL query. * @param array $args Query arguments. * * @return string */ public function add_renewals_by_subscription_id( $after_where, $args ) { $query = $this->query_renewals_by_subscription_id( $args ); if ( empty( $query ) ) { return $after_where; // Return early if $query is empty. } return "{$after_where} UNION {$query}"; } /** * Add renewals to the count query. * * @since 1.8.4 * * @param string $after_where SQL query. * @param array $args Query arguments. * * @return string */ public function count_renewals_by_subscription_id( $after_where, $args ) { $query = $this->query_renewals_by_subscription_id( $args, 'COUNT(*)' ); if ( empty( $query ) ) { return $after_where; // Return early if $query is empty. } return "{$after_where} UNION ALL {$query}"; } /** * Add renewals to the exists query. * * @since 1.8.4 * * @param string $after_where SQL query. * @param array $args Query arguments. * * @return string */ public function exists_renewals_by_subscription_id( $after_where, $args ) { $query = $this->query_renewals_by_subscription_id( $args, '1' ); if ( empty( $query ) ) { return $after_where; // Return early if $query is empty. } return "{$after_where} UNION ALL {$query}"; } /** * Query renewals by subscription ID. * * @since 1.8.4 * * @param array $args Query arguments. * @param string $selector SQL selector. * * @return string */ private function query_renewals_by_subscription_id( $args, $selector = 'p.*' ) { // Check if essential arguments are missing. if ( empty( $args['table_query'] ) || empty( $args['subscription_status'] ) ) { return ''; } // Check if the query type is not 'renewal'. if ( ! empty( $args['type'] ) && ! in_array( 'renewal', explode( '|', $args['type'] ), true ) ) { return ''; } $payment_handle = wpforms()->obj( 'payment' ); $subscription_statuses = explode( '|', $args['subscription_status'] ); $placeholders = wpforms_wpdb_prepare_in( $subscription_statuses ); // This is needed to avoid the count_all method from adding the WHERE clause for the other types. $args['type'] = 'renewal'; // Remove the subscription_status argument from the query. // The primary reason for this is that the subscription_status has to be checked in the subquery. unset( $args['subscription_status'] ); // Prepare the query. $query[] = "SELECT {$selector} FROM {$payment_handle->table_name} as p"; /** * Append custom query parts before the WHERE clause. * * This hook allows external code to extend the SQL query by adding custom conditions * immediately before the WHERE clause. * * @since 1.8.4 * * @param string $where Before the WHERE clause in the database query. * @param array $args Query arguments. * * @return string */ $query[] = apply_filters( 'wpforms_admin_payments_views_overview_filters_renewals_by_subscription_id_query_before_where', '', $args ); // Add the WHERE clause. $query[] = 'WHERE 1=1'; $query[] = $payment_handle->add_columns_where_conditions( $args ); $query[] = $payment_handle->add_secondary_where_conditions( $args ); $query[] = "AND EXISTS ( SELECT 1 FROM {$payment_handle->table_name} as subquery_p WHERE subquery_p.subscription_id = p.subscription_id AND subquery_p.subscription_status IN ({$placeholders}) )"; /** * Append custom query parts after the WHERE clause. * * This hook allows external code to extend the SQL query by adding custom conditions * immediately after the WHERE clause. * * @since 1.8.4 * * @param string $where After the WHERE clause in the database query. * @param array $args Query arguments. * * @return string */ $query[] = apply_filters( 'wpforms_admin_payments_views_overview_filters_renewals_by_subscription_id_query_after_where', '', $args ); return implode( ' ', $query ); } } Payments/Views/Overview/Chart.php 0000644 00000020054 15252506741 0013031 0 ustar 00 <?php namespace WPForms\Admin\Payments\Views\Overview; use WPForms\Admin\Helpers\Datepicker; /** * Payment Overview Chart class. * * @since 1.8.2 */ class Chart { /** * Default payments summary report stat card. * * @since 1.8.2 */ const ACTIVE_REPORT = 'total_payments'; /** * Whether the chart should be displayed. * * @since 1.8.2 * * @return bool */ private function allow_load() { $disallowed_views = [ 's', // Search. 'type', // Payment type. 'status', // Payment status. 'gateway', // Payment gateway. 'subscription_status', // Subscription status. 'form_id', // Form ID. 'coupon_id', // Coupon ID. ]; // Avoid displaying the chart when filtering of payment records is performed. // phpcs:disable WordPress.Security.NonceVerification.Recommended return array_reduce( array_keys( $_GET ), static function ( $carry, $key ) use ( $disallowed_views ) { if ( ! $carry ) { return false; } return ! in_array( $key, $disallowed_views, true ) || empty( $_GET[ $key ] ); }, true ); // phpcs:enable WordPress.Security.NonceVerification.Recommended } /** * Display the chart. * * @since 1.8.2 */ public function display() { // If the chart should not be displayed, leave early. if ( ! $this->allow_load() ) { return; } // Output HTML elements on the page. $this->output_top_bar(); $this->output_test_mode_banner(); $this->output_chart(); } /** * Handles output of the overview page top-bar. * * Includes: * 1. Heading. * 2. Datepicker filter. * 3. Chart theme customization settings. * * @since 1.8.2 */ private function output_top_bar() { list( $choices, $chosen_filter, $value ) = Datepicker::process_datepicker_choices(); ?> <div class="wpforms-overview-top-bar"> <div class="wpforms-overview-top-bar-heading"> <h2><?php esc_html_e( 'Payments Summary', 'wpforms-lite' ); ?></h2> </div> <div class="wpforms-overview-top-bar-filters"> <?php // Output "Mode Toggle" template. ( new ModeToggle() )->display(); // Output "Datepicker" form template. // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wpforms_render( 'admin/components/datepicker', [ 'id' => 'payments', 'action' => Page::get_url(), 'chosen_filter' => $chosen_filter, 'choices' => $choices, 'value' => $value, 'hidden_fields' => [ 'statcard' ], ], true ); ?> <div class="wpforms-overview-chart-settings"> <?php // Output "Settings" template. // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wpforms_render( 'admin/dashboard/widget/settings', array_merge( $this->get_chart_settings(), [ 'enabled' => true ] ), true ); ?> </div> </div> </div> <?php } /** * Display a banner when viewing test data. * * @since 1.8.2 * * @return void */ private function output_test_mode_banner() { // Determine if we are viewing test data. if ( Page::get_mode() !== 'test' ) { return; } ?> <div class="wpforms-payments-viewing-test-mode"> <p> <?php esc_html_e( 'Viewing Test Data', 'wpforms-lite' ); ?> </p> </div> <?php } /** * Handles output of the overview page chart (graph). * * @since 1.8.2 */ private function output_chart() { // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped echo '<div class="wpforms-payments-overview-stats">'; echo wpforms_render( 'admin/components/chart', [ 'id' => 'payments', 'notice' => [ 'heading' => esc_html__( 'No payments for selected period', 'wpforms-lite' ), 'description' => esc_html__( 'Please select a different period or check back later.', 'wpforms-lite' ), ], ], true ); echo wpforms_render( 'admin/payments/reports', $this->get_reports_template_args(), true ); echo '</div>'; // phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped } /** * Get the user’s preferences for displaying of the graph. * * @since 1.8.2 * * @return array */ public function get_chart_settings() { $graph_style = get_user_meta( get_current_user_id(), 'wpforms_dash_widget_graph_style', true ); return [ 'graph_style' => $graph_style ? absint( $graph_style ) : 2, // Line. ]; } /** * Get the stat cards for the payment summary report. * * Note that "funnel" is used to filter the payments, and can take the following values: * - in: payments that match the given criteria. * - not_in: payments that do not match the given criteria. * * @since 1.8.2 * * @return array */ public static function stat_cards() { return [ 'total_payments' => [ 'label' => esc_html__( 'Total Payments', 'wpforms-lite' ), 'button_classes' => [ 'total-payments', ], ], 'total_sales' => [ 'label' => esc_html__( 'Total Sales', 'wpforms-lite' ), 'funnel' => [ 'not_in' => [ 'status' => [ 'failed' ], 'subscription_status' => [ 'failed' ], ], ], 'button_classes' => [ 'total-sales', 'is-amount', ], ], 'total_refunded' => [ 'label' => esc_html__( 'Total Refunded', 'wpforms-lite' ), 'has_count' => true, 'meta_key' => 'refunded_amount', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key 'button_classes' => [ 'total-refunded', 'is-amount', ], ], 'total_subscription' => [ 'label' => esc_html__( 'New Subscriptions', 'wpforms-lite' ), 'condition' => wpforms()->obj( 'payment_queries' )->has_subscription(), 'has_count' => true, 'funnel' => [ 'in' => [ 'type' => [ 'subscription' ], ], 'not_in' => [ 'subscription_status' => [ 'failed' ], ], ], 'button_classes' => [ 'total-subscription', 'is-amount', ], ], 'total_renewal_subscription' => [ 'label' => esc_html__( 'Subscription Renewals', 'wpforms-lite' ), 'condition' => wpforms()->obj( 'payment_queries' )->has_subscription(), 'has_count' => true, 'funnel' => [ 'in' => [ 'type' => [ 'renewal' ], ], 'not_in' => [ 'subscription_status' => [ 'failed' ], ], ], 'button_classes' => [ 'total-renewal-subscription', 'is-amount', ], ], 'total_coupons' => [ 'label' => esc_html__( 'Coupons Redeemed', 'wpforms-lite' ), 'meta_key' => 'coupon_id', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key 'funnel' => [ 'not_in' => [ 'status' => [ 'failed' ], 'subscription_status' => [ 'failed' ], ], ], 'button_classes' => [ 'total-coupons', ], ], ]; } /** * Retrieves the arguments for the reports template. * * @since 1.8.8 * * @return array */ private function get_reports_template_args(): array { // Retrieve the stat cards. $stat_cards = self::stat_cards(); // Set default arguments. $args = [ 'current' => self::ACTIVE_REPORT, 'statcards' => $stat_cards, ]; // Check if the statcard is set in the URL. // phpcs:disable WordPress.Security.NonceVerification.Recommended if ( empty( $_GET['statcard'] ) ) { return $args; } // Sanitize and retrieve the tab value from the URL. $active_report = sanitize_text_field( wp_unslash( $_GET['statcard'] ) ); // phpcs:enable WordPress.Security.NonceVerification.Recommended // If the statcard is not valid, return default arguments. if ( ! isset( $stat_cards[ $active_report ] ) ) { return $args; } // If the statcard is not going to be displayed, return default arguments. if ( isset( $stat_cards[ $active_report ]['condition'] ) && ! $stat_cards[ $active_report ]['condition'] ) { return $args; } // Set the current statcard. $args['current'] = $active_report; return $args; } } Payments/Views/Overview/ModeToggle.php 0000644 00000001255 15252506741 0014020 0 ustar 00 <?php namespace WPForms\Admin\Payments\Views\Overview; /** * Payments Overview Mode Toggle class. * * @since 1.8.2 */ class ModeToggle { /** * Determine if the toggle should be displayed and render it. * * @since 1.8.2 */ public function display() { // Bail early if no payments are found in test mode. if ( ! Helpers::is_test_payment_exists() ) { return; } $this->render(); } /** * Display the toggle button. * * @since 1.8.2 */ private function render() { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wpforms_render( 'admin/payments/mode-toggle', [ 'mode' => Page::get_mode(), ], true ); } } Payments/Views/Overview/Search.php 0000644 00000017247 15252506741 0013207 0 ustar 00 <?php namespace WPForms\Admin\Payments\Views\Overview; /** * Search related methods for Payment and Payment Meta. * * @since 1.8.2 */ class Search { /** * Credit card meta key. * * @since 1.8.2 * * @var string */ const CREDIT_CARD = 'credit_card_last4'; /** * Customer email meta key. * * @since 1.8.2 * * @var string */ const EMAIL = 'customer_email'; /** * Payment title column name. * * @since 1.8.2 * * @var string */ const TITLE = 'title'; /** * Transaction ID column name. * * @since 1.8.2 * * @var string */ const TRANSACTION_ID = 'transaction_id'; /** * Subscription ID column name. * * @since 1.8.2 * * @var string */ const SUBSCRIPTION_ID = 'subscription_id'; /** * Any column indicator key. * * @since 1.8.2 * * @var string */ const ANY = 'any'; /** * Equals mode. * * @since 1.8.2 * * @var string */ const MODE_EQUALS = 'equals'; /** * Starts with mode. * * @since 1.8.2 * * @var string */ const MODE_STARTS = 'starts'; /** * Contains mode. * * @since 1.8.2 * * @var string */ const MODE_CONTAINS = 'contains'; /** * Init. * * @since 1.8.2 */ public function init() { if ( ! self::is_search() ) { return; } $this->hooks(); } /** * Hooks. * * @since 1.8.2 */ private function hooks() { add_filter( 'wpforms_db_payments_queries_count_all_query_before_where', [ $this, 'add_search_where_conditions' ], 10, 2 ); add_filter( 'wpforms_db_payments_payment_get_payments_query_before_where', [ $this, 'add_search_where_conditions' ], 10, 2 ); add_filter( 'wpforms_admin_payments_views_overview_filters_renewals_by_subscription_id_query_before_where', [ $this, 'add_search_where_conditions' ], 10, 2 ); } /** * Check if search query. * * @since 1.8.2 * * @return bool */ public static function is_search() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return ! empty( $_GET['s'] ); } /** * Add search where conditions. * * @since 1.8.2 * * @param string $where Query where string. * @param array $args Query arguments. * * @return string */ public function add_search_where_conditions( $where, $args ) { if ( empty( $args['search'] ) ) { return $where; } if ( ! empty( $args['search_conditions']['search_mode'] ) && $args['search_conditions']['search_mode'] === self::MODE_CONTAINS ) { $to_search = explode( ' ', $args['search'] ); } else { $to_search = [ $args['search'] ]; } $query = []; foreach ( $to_search as $counter => $single ) { $query[] = $this->add_single_search_condition( $single, $args, $counter ); } return implode( ' ', $query ); } /** * Add single search condition. * * @since 1.8.2 * * @param string $word Single searched part. * @param array $args Query arguments. * @param int $n Word counter. * * @return string */ private function add_single_search_condition( $word, $args, $n ) { if ( empty( $word ) ) { return ''; } $mode = $this->prepare_mode( $args ); $where = $this->prepare_where( $args ); list( $operator, $word ) = $this->prepare_operator_and_word( $word, $mode ); $column = $this->prepare_column( $where ); if ( in_array( $column, [ self::EMAIL, self::CREDIT_CARD ], true ) ) { return $this->select_from_meta_table( $column, $operator, $word, $n ); } if ( $column === self::ANY ) { return $this->select_from_any( $operator, $word, $n ); } $payment_table = wpforms()->obj( 'payment' )->table_name; $query = "SELECT id FROM {$payment_table} WHERE {$payment_table}.{$column} {$operator} {$word}"; return $this->wrap_in_inner_join( $query, $n ); } /** * Prepare search mode part. * * @since 1.8.2 * * @param array $args Query arguments. * * @return string Mode part for search. */ private function prepare_mode( $args ) { return isset( $args['search_conditions']['search_mode'] ) ? $args['search_conditions']['search_mode'] : self::MODE_EQUALS; } /** * Prepare search where part. * * @since 1.8.2 * * @param array $args Query arguments. * * @return string Where part for search. */ private function prepare_where( $args ) { return isset( $args['search_conditions']['search_where'] ) ? $args['search_conditions']['search_where'] : self::TITLE; } /** * Prepare operator and word parts. * * @since 1.8.2 * * @param string $word Single word. * @param string $mode Search mode. * * @return array Array with operator and word parts for search. */ private function prepare_operator_and_word( $word, $mode ) { global $wpdb; if ( $mode === self::MODE_CONTAINS ) { return [ 'LIKE', $wpdb->prepare( '%s', '%' . $wpdb->esc_like( $word ) . '%' ), ]; } if ( $mode === self::MODE_STARTS ) { return [ 'LIKE', $wpdb->prepare( '%s', $wpdb->esc_like( $word ) . '%' ), ]; } return [ '=', $wpdb->prepare( '%s', $word ), ]; } /** * Prepare column to search in. * * @since 1.8.2 * * @param string $where Search where. * * @return string Column to search in. */ private function prepare_column( $where ) { if ( in_array( $where, [ self::TRANSACTION_ID, self::SUBSCRIPTION_ID, self::EMAIL, self::CREDIT_CARD, self::ANY ], true ) ) { return $where; } return self::TITLE; } /** * Prepare select part to select from payments meta table. * * @since 1.8.2 * * @param string $meta_key Meta key. * @param string $operator Comparison operator. * @param string $word Word to search. * @param int $n Word count. * * @return string * @noinspection CallableParameterUseCaseInTypeContextInspection */ private function select_from_meta_table( $meta_key, $operator, $word, $n ) { global $wpdb; $payment_table = wpforms()->obj( 'payment' )->table_name; $meta_table = wpforms()->obj( 'payment_meta' )->table_name; $meta_key = $wpdb->prepare( '%s', $meta_key ); $query = "SELECT id FROM $payment_table WHERE id IN ( SELECT DISTINCT payment_id FROM $meta_table WHERE meta_value $operator $word AND meta_key = $meta_key )"; return $this->wrap_in_inner_join( $query, $n ); } /** * Prepare select part to select from places from both tables. * * @since 1.8.2 * * @param string $operator Comparison operator. * @param string $word Word to search. * @param int $n Word count. * * @return string */ private function select_from_any( $operator, $word, $n ) { $payment_table = wpforms()->obj( 'payment' )->table_name; $meta_table = wpforms()->obj( 'payment_meta' )->table_name; $query = sprintf( "SELECT id FROM {$payment_table} WHERE ( {$payment_table}.%s {$operator} {$word} OR {$payment_table}.%s {$operator} {$word} OR {$payment_table}.%s {$operator} {$word} OR id IN ( SELECT DISTINCT payment_id FROM {$meta_table} WHERE meta_value {$operator} {$word} AND meta_key IN ( '%s', '%s' ) ) )", self::TITLE, self::TRANSACTION_ID, self::SUBSCRIPTION_ID, self::CREDIT_CARD, self::EMAIL ); return $this->wrap_in_inner_join( $query, $n ); } /** * Wrap the query in INNER JOIN part. * * @since 1.8.2 * * @param string $query Partial query. * @param int $n Word count. * * @return string */ private function wrap_in_inner_join( $query, $n ) { /** * Filter to modify the inner join query. * * @since 1.8.4 * * @param string $query Partial query. * @param int $n The number of the JOIN clause. */ return apply_filters( 'wpforms_admin_payments_views_overview_search_inner_join_query', sprintf( 'INNER JOIN ( %1$s ) AS p%2$d ON p.id = p%2$d.id', $query, $n ), $n ); } } Payments/Views/Overview/Ajax.php 0000644 00000041752 15252506741 0012663 0 ustar 00 <?php namespace WPForms\Admin\Payments\Views\Overview; use DateTimeImmutable; // phpcs:ignore WPForms.PHP.UseStatement.UnusedUseStatement use wpdb; use WPForms\Db\Payments\ValueValidator; use WPForms\Admin\Helpers\Chart as ChartHelper; use WPForms\Admin\Helpers\Datepicker; /** * "Payments" overview page inside the admin, which lists all payments. * This page will be accessible via "WPForms" → "Payments". * * When requested data is sent via Ajax, this class is responsible for exchanging datasets. * * @since 1.8.2 */ class Ajax { /** * Database table name. * * @since 1.8.2 * * @var string */ private $table_name; /** * Temporary storage for the stat cards. * * @since 1.8.4 * * @var array */ private $stat_cards; /** * Hooks. * * @since 1.8.2 */ public function hooks() { add_action( 'wp_ajax_wpforms_payments_overview_refresh_chart_dataset_data', [ $this, 'get_chart_dataset_data' ] ); add_action( 'wp_ajax_wpforms_payments_overview_save_chart_preference_settings', [ $this, 'save_chart_preference_settings' ] ); add_filter( 'wpforms_db_payments_payment_add_secondary_where_conditions_args', [ $this, 'modify_secondary_where_conditions_args' ] ); } /** * Generate and return the data for our dataset data. * * @since 1.8.2 */ public function get_chart_dataset_data() { // Run a security check. check_ajax_referer( 'wpforms_payments_overview_nonce' ); // Check for permissions. if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( esc_html__( 'You are not allowed to perform this action.', 'wpforms-lite' ) ); } $report = ! empty( $_POST['report'] ) ? sanitize_text_field( wp_unslash( $_POST['report'] ) ) : null; $dates = ! empty( $_POST['dates'] ) ? sanitize_text_field( wp_unslash( $_POST['dates'] ) ) : null; $fallback = [ 'data' => [], 'reports' => [], ]; // If the report type or dates for the timespan are missing, leave early. if ( ! $report || ! $dates ) { wp_send_json_error( $fallback ); } // Validates and creates date objects of given timespan string. $timespans = Datepicker::process_string_timespan( $dates ); // If the timespan is not validated, leave early. if ( ! $timespans ) { wp_send_json_error( $fallback ); } // Extract start and end timespans in local (site) and UTC timezones. list( $start_date, $end_date, $utc_start_date, $utc_end_date ) = $timespans; // Payment table name. $this->table_name = wpforms()->obj( 'payment' )->table_name; // Get the stat cards. $this->stat_cards = Chart::stat_cards(); // Get the payments in the given timespan. $results = $this->get_payments_in_timespan( $utc_start_date, $utc_end_date, $report ); // In case the database's results were empty, leave early. if ( $report === Chart::ACTIVE_REPORT && empty( $results ) ) { wp_send_json_error( $fallback ); } // Process the results and return the data. // The first element of the array is the total number of entries, the second is the data. list( , $data ) = ChartHelper::process_chart_dataset_data( $results, $start_date, $end_date ); // Sends the JSON response back to the Ajax request, indicating success. wp_send_json_success( [ 'data' => $data, 'reports' => $this->get_payments_summary_in_timespan( $start_date, $end_date ), ] ); } /** * Save the user's preferred graph style and color scheme. * * @since 1.8.2 */ public function save_chart_preference_settings() { // Run a security check. check_ajax_referer( 'wpforms_payments_overview_nonce' ); // Check for permissions. if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( esc_html__( 'You are not allowed to perform this action.', 'wpforms-lite' ) ); } $graph_style = isset( $_POST['graphStyle'] ) ? absint( $_POST['graphStyle'] ) : 2; // Line. update_user_meta( get_current_user_id(), 'wpforms_dash_widget_graph_style', $graph_style ); exit(); } /** * Retrieve and create payment entries from the database within the specified time frame (timespan). * * @global wpdb $wpdb Instantiation of the wpdb class. * * @since 1.8.2 * * @param DateTimeImmutable $start_date Start date for the timespan preferably in UTC. * @param DateTimeImmutable $end_date End date for the timespan preferably in UTC. * @param string $report Payment summary stat card name. i.e. "total_payments". * * @return array */ private function get_payments_in_timespan( $start_date, $end_date, $report ) { // Ensure given timespan dates are in UTC timezone. list( $utc_start_date, $utc_end_date ) = Datepicker::process_timespan_mysql( [ $start_date, $end_date ] ); // If the time period is not a date object, leave early. if ( ! ( $start_date instanceof DateTimeImmutable ) || ! ( $end_date instanceof DateTimeImmutable ) ) { return []; } // Get the database instance. global $wpdb; // SELECT clause to construct the SQL statement. $column_clause = $this->get_stats_column_clause( $report ); // JOIN clause to construct the SQL statement for metadata. $join_by_meta = $this->add_join_by_meta( $report ); // WHERE clauses for items query statement. $where_clause = $this->get_stats_where_clause( $report ); // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared return $wpdb->get_results( $wpdb->prepare( "SELECT date_created_gmt AS day, $column_clause AS count FROM $this->table_name AS p {$join_by_meta} WHERE 1=1 $where_clause AND date_created_gmt BETWEEN %s AND %s GROUP BY day ORDER BY day ASC", [ $utc_start_date->format( Datepicker::DATETIME_FORMAT ), $utc_end_date->format( Datepicker::DATETIME_FORMAT ), ] ), ARRAY_A ); // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared } /** * Fetch and generate payment summary reports from the database. * * @global wpdb $wpdb Instantiation of the wpdb class. * * @since 1.8.2 * * @param DateTimeImmutable $start_date Start date for the timespan preferably in UTC. * @param DateTimeImmutable $end_date End date for the timespan preferably in UTC. * * @return array */ private function get_payments_summary_in_timespan( $start_date, $end_date ) { // Ensure given timespan dates are in UTC timezone. list( $utc_start_date, $utc_end_date ) = Datepicker::process_timespan_mysql( [ $start_date, $end_date ] ); // If the time period is not a date object, leave early. if ( ! ( $start_date instanceof DateTimeImmutable ) || ! ( $end_date instanceof DateTimeImmutable ) ) { return []; } // Get the database instance. global $wpdb; list( $clause, $query ) = $this->prepare_sql_summary_reports( $utc_start_date, $utc_end_date ); $group_by = Chart::ACTIVE_REPORT; // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $results = $wpdb->get_row( "SELECT $clause FROM (SELECT $query) AS results GROUP BY $group_by", ARRAY_A ); // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared return $this->maybe_format_amounts( $results ); } /** * Generate SQL statements to create a derived (virtual) table for the report stat cards. * * @global wpdb $wpdb Instantiation of the wpdb class. * * @since 1.8.2 * * @param DateTimeImmutable $start_date Start date for the timespan. * @param DateTimeImmutable $end_date End date for the timespan. * * @return array */ private function prepare_sql_summary_reports( $start_date, $end_date ) { // In case there are no report stat cards defined, leave early. if ( empty( $this->stat_cards ) ) { return [ '', '' ]; } global $wpdb; $clause = []; // SELECT clause. $query = []; // Query statement for the derived table. // Validates and creates date objects for the previous time spans. $prev_timespans = Datepicker::get_prev_timespan_dates( $start_date, $end_date ); // If the timespan is not validated, leave early. if ( ! $prev_timespans ) { return [ '', '' ]; } list( $prev_start_date, $prev_end_date ) = $prev_timespans; // Get the default number of decimals for the payment currency. $current_currency = wpforms_get_currency(); $currency_decimals = wpforms_get_currency_decimals( $current_currency ); // Loop through the reports and create the SQL statements. foreach ( $this->stat_cards as $report => $attributes ) { // Skip stat card, if it's not supposed to be displayed or disabled (upsell). if ( ( isset( $attributes['condition'] ) && ! $attributes['condition'] ) || in_array( 'disabled', $attributes['button_classes'], true ) ) { continue; } // Determine whether the number of rows has to be counted. $has_count = isset( $attributes['has_count'] ) && $attributes['has_count']; // SELECT clause to construct the SQL statement. $column_clause = $this->get_stats_column_clause( $report, $has_count ); // JOIN clause to construct the SQL statement for metadata. $join_by_meta = $this->add_join_by_meta( $report ); // WHERE clauses for items query statement. $where_clause = $this->get_stats_where_clause( $report ); // Get the current and previous values for the report. $current_value = "TRUNCATE($report,$currency_decimals)"; $prev_value = "TRUNCATE({$report}_prev,$currency_decimals)"; // Add the current and previous reports to the SELECT clause. $clause[] = $report; $clause[] = "ROUND( ( ( $current_value - $prev_value ) / $current_value ) * 100 ) AS {$report}_delta"; // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.MissingReplacements $query[] = $wpdb->prepare( "( SELECT $column_clause FROM $this->table_name AS p {$join_by_meta} WHERE 1=1 $where_clause AND date_created_gmt BETWEEN %s AND %s ) AS $report, ( SELECT $column_clause FROM $this->table_name AS p {$join_by_meta} WHERE 1=1 $where_clause AND date_created_gmt BETWEEN %s AND %s ) AS {$report}_prev", [ $start_date->format( Datepicker::DATETIME_FORMAT ), $end_date->format( Datepicker::DATETIME_FORMAT ), $prev_start_date->format( Datepicker::DATETIME_FORMAT ), $prev_end_date->format( Datepicker::DATETIME_FORMAT ), ] ); // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.MissingReplacements } return [ implode( ',', $clause ), implode( ',', $query ), ]; } /** * Helper method to build where clause used to construct the SQL statement. * * @since 1.8.2 * * @param string $report Payment summary stat card name. i.e. "total_payments". * * @return string */ private function get_stats_where_clause( $report ) { // Get the default WHERE clause from the Payments database class. $clause = wpforms()->obj( 'payment' )->add_secondary_where_conditions(); // If the report doesn't have any additional funnel arguments, leave early. if ( ! isset( $this->stat_cards[ $report ]['funnel'] ) ) { return $clause; } // Get the where arguments for the report. $where_args = (array) $this->stat_cards[ $report ]['funnel']; // If the where arguments are empty, leave early. if ( empty( $where_args ) ) { return $clause; } return $this->prepare_sql_where_clause( $where_args, $clause ); } /** * Prepare SQL where clause for the given funnel arguments. * * @since 1.8.4 * * @param array $where_args Array of where arguments. * @param string $clause SQL where clause. * * @return string */ private function prepare_sql_where_clause( $where_args, $clause ) { $allowed_funnels = [ 'in', 'not_in' ]; $filtered_where_args = array_filter( $where_args, static function ( $key ) use ( $allowed_funnels ) { return in_array( $key, $allowed_funnels, true ); }, ARRAY_FILTER_USE_KEY ); // Leave early if the filtered where arguments are empty. if ( empty( $filtered_where_args ) ) { return $clause; } // Loop through the where arguments and add them to the clause. foreach ( $filtered_where_args as $operator => $columns ) { foreach ( $columns as $column => $values ) { if ( ! is_array( $values ) ) { continue; } // Skip if the value is not valid. $valid_values = array_filter( $values, static function ( $item ) use ( $column ) { return ValueValidator::is_valid( $item, $column ); } ); $placeholders = wpforms_wpdb_prepare_in( $valid_values ); $clause .= $operator === 'in' ? " AND {$column} IN ({$placeholders})" : " AND {$column} NOT IN ({$placeholders})"; } } return $clause; } /** * Helper method to build column clause used to construct the SQL statement. * * @since 1.8.2 * * @param string $report Stats card chart type (name). i.e. "total_payments". * @param bool $with_count Whether to concatenate the count to the clause. * * @return string */ private function get_stats_column_clause( $report, $with_count = false ) { // Default column clause. // Count the number of rows as fast as possible. $default = 'COUNT(*)'; // If the report has a meta key, then count the number of unique rows for the meta table. if ( isset( $this->stat_cards[ $report ]['meta_key'] ) ) { $default = 'COUNT(pm.id)'; } /** * Filters the column clauses for the stat cards. * * @since 1.8.2 * * @param array $clauses Array of column clauses. */ $clauses = (array) apply_filters( 'wpforms_admin_payments_views_overview_ajax_stats_column_clauses', [ 'total_payments' => "FORMAT({$default},0)", 'total_sales' => 'IFNULL(SUM(total_amount),0)', 'total_refunded' => 'IFNULL(SUM(pm.meta_value),0)', 'total_subscription' => 'IFNULL(SUM(total_amount),0)', 'total_renewal_subscription' => 'IFNULL(SUM(total_amount),0)', 'total_coupons' => "FORMAT({$default},0)", ] ); $clause = isset( $clauses[ $report ] ) ? $clauses[ $report ] : $default; // Several stat cards might include the count of payment records. if ( $with_count ) { $clause = "CONCAT({$clause}, ' (', {$default}, ')')"; } return $clause; } /** * Add join by meta table. * * @since 1.8.4 * * @param string $report Stats card chart type (name). i.e. "total_payments". * * @return string */ private function add_join_by_meta( $report ) { // Leave early if the meta key is empty. if ( ! isset( $this->stat_cards[ $report ]['meta_key'] ) ) { return ''; } // Retrieve the global database instance. global $wpdb; // Retrieve the meta table name. $meta_table_name = wpforms()->obj( 'payment_meta' )->table_name; return $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared "LEFT JOIN {$meta_table_name} AS pm ON p.id = pm.payment_id AND pm.meta_key = %s", $this->stat_cards[ $report ]['meta_key'] ); } /** * Modify arguments of secondary where clauses. * * @since 1.8.2 * * @param array $args Query arguments. * * @return array */ public function modify_secondary_where_conditions_args( $args ) { // Set a current mode. if ( ! isset( $args['mode'] ) ) { $args['mode'] = Page::get_mode(); } return $args; } /** * Maybe format the amounts for the given stat cards. * * @since 1.8.4 * * @param array $results Query results. * * @return array */ private function maybe_format_amounts( $results ) { // If the input is empty, leave early. if ( empty( $results ) ) { return []; } foreach ( $results as $key => $value ) { // If the given stat card doesn't have a button class, leave early. // If the given stat card doesn't have a button class of "is-amount," leave early. if ( ! isset( $this->stat_cards[ $key ]['button_classes'] ) || ! in_array( 'is-amount', $this->stat_cards[ $key ]['button_classes'], true ) ) { continue; } // Split the input by space to look for the count. $input_arr = (array) explode( ' ', $value ); // If the given stat card doesn't have a count, leave early. if ( empty( $this->stat_cards[ $key ]['has_count'] ) || ! isset( $input_arr[1] ) ) { // Format the given amount and split the input by space. $results[ $key ] = wpforms_format_amount( $value, true ); continue; } // The fields are stored as a `decimal` in the DB, and appears here as the string. // But all strings values, passed to wpforms_format_amount() are sanitized. // There is no need to sanitize it, as it is already a regular numeric string. $amount = wpforms_format_amount( (float) ( $input_arr[0] ?? $value ), true ); // Format the amount with the concatenation of count in parentheses. // Example: 2185.52000000 (79). $results[ $key ] = sprintf( '%s <span>%s</span>', esc_html( $amount ), esc_html( $input_arr[1] ) // 1: Would be count of the records. ); } return $results; } } Payments/Views/Overview/Traits/ResetNotices.php 0000644 00000017721 15252506741 0015654 0 ustar 00 <?php namespace WPForms\Admin\Payments\Views\Overview\Traits; use WPForms\Admin\Payments\Views\Overview\Coupon; use WPForms\Admin\Payments\Views\Overview\Search; use WPForms\Db\Payments\ValueValidator; /** * This file is part of the Table class and contains methods responsible for * displaying notices on the Payments Overview page. * * @since 1.8.4 */ trait ResetNotices { /** * Show reset filter box. * * @since 1.8.4 */ private function show_reset_filter() { $applied_filters = [ $this->get_search_reset_filter(), $this->get_status_reset_filter(), $this->get_coupon_reset_filter(), $this->get_form_reset_filter(), $this->get_type_reset_filter(), $this->get_gateway_reset_filter(), $this->get_subscription_status_reset_filter(), ]; $applied_filters = array_filter( $applied_filters ); // Let's not show the reset filter notice if there are no applied filters. if ( empty( $applied_filters ) ) { return; } // Output the reset filter notice. // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wpforms_render( 'admin/payments/reset-filter-notice', [ 'total' => $this->get_valid_status_count_from_request(), 'applied_filters' => $applied_filters, ], true ); } /** * Show search reset filter. * * @since 1.8.4 * * @return array */ private function get_search_reset_filter() { // Do not show the reset filter notice on the search results page. if ( ! Search::is_search() ) { return []; } $search_where = $this->get_search_where( $this->get_search_where_key() ); $search_mode = $this->get_search_mode( $this->get_search_mode_key() ); return [ 'reset_url' => remove_query_arg( [ 's', 'search_where', 'search_mode', 'paged' ] ), 'results' => sprintf( ' %s <em>%s</em> %s "<em>%s</em>"', __( 'where', 'wpforms-lite' ), esc_html( $search_where ), esc_html( $search_mode ), // It's important to escape the search term here for security. // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized esc_html( isset( $_GET['s'] ) ? wp_unslash( $_GET['s'] ) : '' ) ), ]; } /** * Show status reset filter. * * @since 1.8.4 * * @return array */ private function get_status_reset_filter() { // Do not show the reset filter notice on the status results page. // phpcs:disable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized if ( empty( $this->get_valid_status_from_request() ) || $this->is_trash_view() ) { return []; } $statuses = ValueValidator::get_allowed_one_time_statuses(); // Leave early if the status is not found. if ( ! isset( $statuses[ $this->get_valid_status_from_request() ] ) ) { return []; } return [ 'reset_url' => remove_query_arg( [ 'status' ] ), 'results' => sprintf( ' %s "<em>%s</em>"', __( 'with the status', 'wpforms-lite' ), $statuses[ $this->get_valid_status_from_request() ] ), ]; } /** * Show coupon reset filter. * * @since 1.8.4 * * @return array */ private function get_coupon_reset_filter() { // Do not show the reset filter notice on the coupon results page. if ( ! Coupon::is_coupon() ) { return []; } // Get the payment meta with the specified coupon ID. $payment_meta = wpforms()->obj( 'payment_meta' )->get_all_by_meta( 'coupon_id', // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.NonceVerification.Recommended absint( $_GET['coupon_id'] ) ); // If the coupon info is empty, exit the function. if ( empty( $payment_meta['coupon_info'] ) ) { return []; } return [ 'reset_url' => remove_query_arg( [ 'coupon_id', 'paged' ] ), 'results' => sprintf( ' %s "<em>%s</em>"', __( 'with the coupon', 'wpforms-lite' ), $this->get_coupon_name_by_info( $payment_meta['coupon_info']->value ) ), ]; } /** * Show form reset filter. * * @since 1.8.4 * * @return array */ private function get_form_reset_filter() { // Do not show the reset filter notice on the form results page. // phpcs:disable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized if ( empty( $_GET['form_id'] ) ) { return []; } // Retrieve the form with the specified ID. $form = wpforms()->obj( 'form' )->get( absint( $_GET['form_id'] ) ); // phpcs:enable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized // If the form is not found or not published, exit the function. if ( ! $form || $form->post_status !== 'publish' ) { return []; } return [ 'reset_url' => remove_query_arg( [ 'form_id', 'paged' ] ), 'results' => sprintf( ' %s "<em>%s</em>"', __( 'with the form titled', 'wpforms-lite' ), ! empty( $form->post_title ) ? $form->post_title : $form->post_name ), ]; } /** * Show type reset filter. * * @since 1.8.4 * * @return array */ private function get_type_reset_filter() { // Do not show the reset filter notice on the type results page. // phpcs:disable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized if ( empty( $_GET['type'] ) ) { return []; } $allowed_types = ValueValidator::get_allowed_types(); $type = explode( '|', sanitize_text_field( wp_unslash( $_GET['type'] ) ) ); // phpcs:enable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized return [ 'reset_url' => remove_query_arg( [ 'type', 'paged' ] ), 'results' => sprintf( ' %s "<em>%s</em>"', _n( 'with the type', 'with the types', count( $type ), 'wpforms-lite' ), implode( ', ', array_intersect_key( $allowed_types, array_flip( $type ) ) ) ), ]; } /** * Show gateway reset filter. * * @since 1.8.4 * * @return array */ private function get_gateway_reset_filter() { // Do not show the reset filter notice on the gateway results page. // phpcs:disable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized if ( empty( $_GET['gateway'] ) ) { return []; } $allowed_gateways = ValueValidator::get_allowed_gateways(); $gateway = explode( '|', sanitize_text_field( wp_unslash( $_GET['gateway'] ) ) ); // phpcs:enable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized return [ 'reset_url' => remove_query_arg( [ 'gateway', 'paged' ] ), 'results' => sprintf( ' %s "<em>%s</em>"', _n( 'with the gateway', 'with the gateways', count( $gateway ), 'wpforms-lite' ), implode( ', ', array_intersect_key( $allowed_gateways, array_flip( $gateway ) ) ) ), ]; } /** * Show subscription status reset filter. * * @since 1.8.4 * * @return array */ private function get_subscription_status_reset_filter() { // Do not show the reset filter notice on the subscription status results page. // phpcs:disable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized if ( empty( $_GET['subscription_status'] ) ) { return []; } $allowed_subscription_statuses = ValueValidator::get_allowed_subscription_statuses(); $subscription_status = explode( '|', sanitize_text_field( wp_unslash( $_GET['subscription_status'] ) ) ); // phpcs:enable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized return [ 'reset_url' => remove_query_arg( [ 'subscription_status', 'paged' ] ), 'results' => sprintf( ' %s "<em>%s</em>"', _n( 'with the subscription status', 'with the subscription statuses', count( $subscription_status ), 'wpforms-lite' ), implode( ', ', array_intersect_key( $allowed_subscription_statuses, array_flip( $subscription_status ) ) ) ), ]; } } Payments/Views/Overview/Helpers.php 0000644 00000005136 15252506741 0013376 0 ustar 00 <?php namespace WPForms\Admin\Payments\Views\Overview; use WPForms\Db\Payments\ValueValidator; /** * Helper methods for the Overview page. * * @since 1.8.2 */ class Helpers { /** * Get subscription description. * * @since 1.8.2 * * @param string $payment_id Payment id. * @param string $amount Payment amount. * * @return string */ public static function get_subscription_description( $payment_id, $amount ) { // Get the subscription period for the payment. $period = wpforms()->obj( 'payment_meta' )->get_single( $payment_id, 'subscription_period' ); $intervals = ValueValidator::get_allowed_subscription_intervals(); // If the subscription period is not set or not allowed, return the amount only. if ( ! isset( $intervals[ $period ] ) ) { return $amount; } // Use "/" as a separator between the amount and the subscription period. return $amount . ' / ' . $intervals[ $period ]; } /** * Return a placeholder text "N/A" when there is no actual data to display. * * @since 1.8.2 * * @param string $with_wrapper Wrap the text within a span tag for styling purposes. Default: true. * * @return string */ public static function get_placeholder_na_text( $with_wrapper = true ) { $text = __( 'N/A', 'wpforms-lite' ); // Check if the text should be wrapped within a span tag. if ( $with_wrapper ) { return sprintf( '<span class="payment-placeholder-text-none">%s</span>', $text ); } return $text; } /** * Get the default heading for the Payments pages. * * @since 1.8.2.2 * * @param string $help_link Help link. */ public static function get_default_heading( $help_link = '' ) { if ( ! $help_link ) { $help_link = 'https://wpforms.com/docs/viewing-and-managing-payments/'; } echo '<span class="wpforms-payments-overview-help">'; printf( '<a href="%s" target="_blank"><i class="fa fa-question-circle-o"></i>%s</a>', esc_url( wpforms_utm_link( $help_link, 'Payments Dashboard', 'Manage Payments Documentation' ) ), esc_html__( 'Help', 'wpforms-lite' ) ); echo '</span>'; } /** * Look for at least one payment in test mode. * * @since 1.9.0 * * @return bool */ public static function is_test_payment_exists(): bool { $published = wpforms()->obj( 'payment' )->get_payments( [ 'mode' => 'test', 'number' => 1, ] ); if ( $published ) { return true; } // Check for trashed payments. return ! empty( wpforms()->obj( 'payment' )->get_payments( [ 'mode' => 'test', 'number' => 1, 'is_published' => 0, ] ) ); } } Payments/Views/Overview/Page.php 0000644 00000034266 15252506741 0012656 0 ustar 00 <?php namespace WPForms\Admin\Payments\Views\Overview; use WPForms\Admin\Helpers\Datepicker; use WPForms\Db\Payments\ValueValidator; use WPForms\Admin\Payments\Payments; use WPForms\Admin\Payments\Views\PaymentsViewsInterface; use WPForms\Integrations\Stripe\Helpers as StripeHelpers; use WPForms\Integrations\Stripe\Stripe; use WPForms\Integrations\Square\Helpers as SquareHelpers; use WPForms\Integrations\Square\Square; use WPForms\Integrations\PayPalCommerce\Connection as PayPalCommerceConnection; use WPForms\Integrations\PayPalCommerce\PayPalCommerce; use WPFormsAuthorizeNet\Helpers as AuthorizeNetHelpers; /** * Payments Overview Page class. * * @since 1.8.2 */ class Page implements PaymentsViewsInterface { /** * Payments table. * * @since 1.8.2 * * @var Table */ private $table; /** * Payments chart. * * @since 1.8.2 * * @var Chart */ private $chart; /** * Initialize class. * * @since 1.8.2 */ public function init() { if ( ! $this->has_any_mode_payment() ) { return; } $this->chart = new Chart(); $this->table = new Table(); $this->table->prepare_items(); $this->clean_request_uri(); $this->hooks(); } /** * Register hooks. * * @since 1.8.2 */ private function hooks() { add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] ); } /** * Get the tab label. * * @since 1.8.2.2 * * @return string */ public function get_tab_label() { return __( 'Overview', 'wpforms-lite' ); } /** * Enqueue scripts and styles. * * @since 1.8.2 */ public function enqueue_assets() { $min = wpforms_get_min_suffix(); wp_enqueue_style( 'wpforms-flatpickr', WPFORMS_PLUGIN_URL . 'assets/lib/flatpickr/flatpickr.min.css', [], '4.6.9' ); wp_enqueue_script( 'wpforms-flatpickr', WPFORMS_PLUGIN_URL . 'assets/lib/flatpickr/flatpickr.min.js', [ 'jquery' ], '4.6.9', true ); wp_enqueue_style( 'wpforms-multiselect-checkboxes', WPFORMS_PLUGIN_URL . 'assets/lib/wpforms-multiselect/wpforms-multiselect-checkboxes.min.css', [], '1.0.0' ); wp_enqueue_script( 'wpforms-multiselect-checkboxes', WPFORMS_PLUGIN_URL . 'assets/lib/wpforms-multiselect/wpforms-multiselect-checkboxes.min.js', [], '1.0.0', true ); wp_enqueue_script( 'wpforms-chart', WPFORMS_PLUGIN_URL . 'assets/lib/chart.min.js', [ 'moment' ], '4.5.1', true ); wp_enqueue_script( 'wpforms-chart-adapter-moment', WPFORMS_PLUGIN_URL . 'assets/lib/chartjs-adapter-moment.min.js', [ 'moment', 'wpforms-chart' ], '1.0.1', true ); wp_enqueue_script( 'wpforms-admin-payments-overview', WPFORMS_PLUGIN_URL . "assets/js/admin/payments/overview{$min}.js", [ 'jquery', 'wpforms-flatpickr', 'wpforms-chart' ], WPFORMS_VERSION, true ); $admin_l10n = [ 'settings' => $this->chart->get_chart_settings(), 'locale' => sanitize_key( wpforms_get_language_code() ), 'nonce' => wp_create_nonce( 'wpforms_payments_overview_nonce' ), 'date_format' => sanitize_text_field( Datepicker::get_wp_date_format_for_momentjs() ), 'delimiter' => Datepicker::TIMESPAN_DELIMITER, 'report' => Chart::ACTIVE_REPORT, 'currency' => sanitize_text_field( wpforms_get_currency() ), 'decimals' => absint( wpforms_get_currency_decimals( wpforms_get_currency() ) ), 'i18n' => [ 'label' => esc_html__( 'Payments', 'wpforms-lite' ), 'delete_button' => esc_html__( 'Delete', 'wpforms-lite' ), 'subscription_delete_confirm' => $this->get_subscription_delete_confirmation_message(), 'no_dataset' => [ 'total_payments' => esc_html__( 'No payments for selected period', 'wpforms-lite' ), 'total_sales' => esc_html__( 'No sales for selected period', 'wpforms-lite' ), 'total_refunded' => esc_html__( 'No refunds for selected period', 'wpforms-lite' ), 'total_subscription' => esc_html__( 'No new subscriptions for selected period', 'wpforms-lite' ), 'total_renewal_subscription' => esc_html__( 'No subscription renewals for the selected period', 'wpforms-lite' ), 'total_coupons' => esc_html__( 'No coupons applied during the selected period', 'wpforms-lite' ), ], ], 'page_uri' => $this->get_current_uri(), ]; wp_localize_script( 'wpforms-admin-payments-overview', // Script handle the data will be attached to. 'wpforms_admin_payments_overview', // Name for the JavaScript object. $admin_l10n ); } /** * Retrieve a Payment Overview URI. * * @since 1.8.2 * * @return string */ private function get_current_uri() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $query = $_GET; unset( $query['mode'], $query['paged'] ); return add_query_arg( $query, self::get_url() ); } /** * Determine whether the current user has the capability to view the page. * * @since 1.8.2 * * @return bool */ public function current_user_can() { return wpforms_current_user_can(); } /** * Page heading. * * @since 1.8.2 */ public function heading() { Helpers::get_default_heading(); } /** * Page content. * * @since 1.8.2 */ public function display() { // If there are no payments at all, display an empty state. if ( ! $this->has_any_mode_payment() ) { $this->display_empty_state(); return; } // Display the page content, including the chart and the table. $this->chart->display(); $this->table->display(); } /** * Get the URL of the page. * * @since 1.8.2 * * @return string */ public static function get_url() { static $url; if ( $url ) { return $url; } $url = add_query_arg( [ 'page' => Payments::SLUG, ], admin_url( 'admin.php' ) ); return $url; } /** * Get payment mode. * * Use only for logged-in users. Returns mode from user meta data or from the $_GET['mode'] parameter. * * @since 1.8.2 * * @return string */ public static function get_mode(): string { static $mode; if ( ! self::is_valid_context_for_mode() ) { return 'live'; } if ( $mode ) { return $mode; } $mode = self::get_mode_from_request(); $user_id = get_current_user_id(); $meta_key = 'wpforms-payments-mode'; if ( self::is_mode_valid_and_nonce_verified( $mode ) ) { update_user_meta( $user_id, $meta_key, $mode ); return $mode; } $mode = (string) get_user_meta( $user_id, $meta_key, true ); if ( empty( $mode ) || ! Helpers::is_test_payment_exists() ) { $mode = 'live'; } return $mode; } /** * Check if the context is valid for payment mode. * * @since 1.9.5 * * @return bool */ private static function is_valid_context_for_mode(): bool { return wpforms_is_admin_ajax() || wpforms_is_admin_page( 'payments' ) || wpforms_is_admin_page( 'entries' ); } /** * Retrieve the payment mode from the request. * * @since 1.9.5 * * @return string */ private static function get_mode_from_request(): string { // Nonce is checked in the `is_mode_valid_and_nonce_verified` method. // phpcs:ignore WordPress.Security.NonceVerification.Recommended return isset( $_GET['mode'] ) ? sanitize_key( $_GET['mode'] ) : ''; } /** * Determine if the mode is valid and the nonce is verified. * * @since 1.9.5 * * @param string $mode Payment mode to validate. * * @return bool */ private static function is_mode_valid_and_nonce_verified( string $mode ): bool { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return ValueValidator::is_valid( $mode, 'mode' ) && isset( $_GET['_wpnonce'] ) && wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), 'wpforms_payments_overview_nonce' ); } /** * Display one of the empty states. * * @since 1.8.2 */ private function display_empty_state() { $version = StripeHelpers::is_allowed_license_type() ? 'pro' : 'lite'; // If a payment gateway is configured, output no payments state. if ( $this->is_gateway_configured() ) { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wpforms_render( 'admin/empty-states/payments/no-payments', [ 'cta_url' => add_query_arg( [ 'page' => 'wpforms-overview', ], 'admin.php' ), 'version' => $version, ], true ); return; } // Otherwise, output get started state. // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wpforms_render( 'admin/empty-states/payments/get-started', [ 'version' => $version, 'gateways' => $this->get_started_gateways(), ], true ); } /** * Compose the gateway tiles shown on the Get Started empty state. * * @since 1.10.1.1 * * @return array */ private function get_started_gateways(): array { $gateways = array_filter( [ 'stripe' => Stripe::get_started_gateway(), 'paypal-commerce' => PayPalCommerce::get_started_gateway(), 'square' => Square::get_started_gateway(), ] ); return $this->add_authorize_net_gateway( $gateways ); } /** * Append the Authorize.Net tile to the Get Started gateway list. * * @since 1.10.1.1 * * @param array $gateways Existing gateway entries. * * @return array */ private function add_authorize_net_gateway( array $gateways ): array { $is_elite_tier = in_array( wpforms_get_license_type(), [ 'elite', 'agency', 'ultimate' ], true ); $is_authorize_net_active = class_exists( '\WPFormsAuthorizeNet\Loader' ); $settings_payments_url = admin_url( 'admin.php?page=wpforms-settings&view=payments' ); $addons_url = admin_url( 'admin.php?page=wpforms-addons' ); if ( $is_authorize_net_active ) { $url = $settings_payments_url . '#wpforms-setting-row-authorize_net-heading'; $cta = __( 'Connect', 'wpforms-lite' ); } elseif ( $is_elite_tier ) { $url = $addons_url . '&search=authorize'; $cta = __( 'Install', 'wpforms-lite' ); } else { $url = wpforms_admin_upgrade_link( 'Payments Dashboard', 'Splash - Authorize.Net Upgrade' ); $cta = __( 'Upgrade', 'wpforms-lite' ); } $gateways['authorize-net'] = [ 'icon' => WPFORMS_PLUGIN_URL . 'assets/images/addon-icon-authorize-net.png', 'name' => __( 'Authorize.Net', 'wpforms-lite' ), 'description' => __( 'Accept credit cards and eChecks with enterprise-grade fraud protection.', 'wpforms-lite' ), 'url' => $url, 'badge' => $is_elite_tier ? '' : __( 'Elite', 'wpforms-lite' ), 'cta' => $cta, 'cta_target' => $is_elite_tier ? '_self' : '_blank', 'cta_class' => $is_elite_tier ? '' : 'wpforms-upgrade-modal', ]; return $gateways; } /** * Determine whether Stripe or Square payment gateway is configured. * * @since 1.8.2 * * @return bool */ private function is_gateway_configured(): bool { $is_configured = StripeHelpers::has_stripe_keys() || SquareHelpers::is_square_configured() || self::is_paypal_commerce_configured() || self::is_authorize_net_configured(); /** * Allow to modify a status whether a payment gateway is configured. * * @since 1.8.2 * * @param bool $is_configured True if any supported payment gateway is configured. */ return (bool) apply_filters( 'wpforms_admin_payments_views_overview_page_gateway_is_configured', $is_configured ); } /** * Determine whether the PayPal Commerce gateway has a saved connection. * * @since 1.10.1.1 * * @return bool */ private static function is_paypal_commerce_configured(): bool { $connection = PayPalCommerceConnection::get(); return $connection && $connection->is_configured(); } /** * Determine whether the Authorize.Net addon is active and has API credentials saved. * * @since 1.10.1.1 * * @return bool */ private static function is_authorize_net_configured(): bool { if ( ! class_exists( AuthorizeNetHelpers::class ) ) { return false; } return (bool) AuthorizeNetHelpers::has_authorize_net_keys(); } /** * Determine whether there are payments of any modes. * * @since 1.8.2 * * @return bool */ private function has_any_mode_payment() { static $has_any_mode_payment; if ( $has_any_mode_payment !== null ) { return $has_any_mode_payment; } $has_any_mode_payment = count( wpforms()->obj( 'payment' )->get_payments( [ 'mode' => 'any', 'number' => 1, ] ) ) > 0; // Check on trashed payments. if ( ! $has_any_mode_payment ) { $has_any_mode_payment = count( wpforms()->obj( 'payment' )->get_payments( [ 'mode' => 'any', 'number' => 1, 'is_published' => 0, ] ) ) > 0; } return $has_any_mode_payment; } /** * To avoid recursively, remove the previous variables from the REQUEST_URI. * * @since 1.8.2 */ private function clean_request_uri() { if ( isset( $_SERVER['REQUEST_URI'] ) ) { // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Recommended $_SERVER['REQUEST_URI'] = remove_query_arg( [ '_wpnonce', '_wp_http_referer', 'action', 'action2', 'payment_id' ], wp_unslash( $_SERVER['REQUEST_URI'] ) ); if ( empty( $_GET['s'] ) ) { $_SERVER['REQUEST_URI'] = remove_query_arg( [ 'search_where', 'search_mode', 's' ], wp_unslash( $_SERVER['REQUEST_URI'] ) ); } // phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Recommended } } /** * Get the subscription delete confirmation message. * The returned message is used in the JavaScript file and shown in a "Heads up!" modal. * * @since 1.8.4 * * @return string */ private function get_subscription_delete_confirmation_message() { $help_link = wpforms_utm_link( 'https://wpforms.com/docs/viewing-and-managing-payments/#deleting-parent-subscription', 'Delete Payment', 'Learn More' ); return sprintf( wp_kses( /* translators: WPForms.com docs page URL. */ __( 'Deleting one or more selected payments may prevent processing of future subscription renewals. Payment filtering may also be affected. <a href="%1$s" rel="noopener" target="_blank">Learn More</a>', 'wpforms-lite' ), [ 'a' => [ 'href' => [], 'rel' => [], 'target' => [], ], ] ), esc_url( $help_link ) ); } } Payments/Views/Overview/BulkActions.php 0000644 00000010551 15252506741 0014207 0 ustar 00 <?php namespace WPForms\Admin\Payments\Views\Overview; use WPForms\Admin\Notice; /** * Bulk actions on the Payments Overview page. * * @since 1.8.2 */ class BulkActions { /** * Allowed actions. * * @since 1.8.2 * * @const array */ const ALLOWED_ACTIONS = [ 'trash', 'restore', 'delete', ]; /** * Payments ids. * * @since 1.8.2 * * @var array */ private $ids; /** * Current action. * * @since 1.8.2 * * @var string */ private $action; /** * Init. * * @since 1.8.2 */ public function init() { $this->process(); } /** * Get the current action selected from the bulk actions dropdown. * * @since 1.8.2 * * @return string|false The action name or False if no action was selected */ private function current_action() { // phpcs:disable WordPress.Security.NonceVerification.Recommended if ( isset( $_REQUEST['action'] ) && $_REQUEST['action'] !== '-1' ) { return sanitize_key( $_REQUEST['action'] ); } if ( isset( $_REQUEST['action2'] ) && $_REQUEST['action2'] !== '-1' ) { return sanitize_key( $_REQUEST['action2'] ); } // phpcs:enable WordPress.Security.NonceVerification.Recommended return false; } /** * Process bulk actions. * * @since 1.8.2 */ private function process() { if ( empty( $_GET['_wpnonce'] ) || empty( $_GET['payment_id'] ) ) { return; } if ( ! wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), 'bulk-wpforms_page_wpforms-payments' ) ) { wp_die( esc_html__( 'Your session expired. Please reload the page.', 'wpforms-lite' ) ); } $this->ids = array_map( 'absint', (array) $_GET['payment_id'] ); $this->action = $this->current_action(); if ( empty( $this->ids ) || ! $this->action || ! $this->is_allowed_action( $this->action ) ) { return; } $this->process_action(); } /** * Process a bulk action. * * @since 1.8.2 */ private function process_action() { $method = "process_action_{$this->action}"; // Check that we have a method for this action. if ( ! method_exists( $this, $method ) ) { return; } $processed = 0; foreach ( $this->ids as $id ) { $processed = $this->$method( $id ) ? $processed + 1 : $processed; } if ( ! $processed ) { return; } $this->display_bulk_action_message( $processed ); } /** * Trash the payment. * * @since 1.8.2 * * @param int $id Payment ID to trash. * * @return bool */ private function process_action_trash( $id ) { return wpforms()->obj( 'payment' )->update( $id, [ 'is_published' => 0 ] ); } /** * Restore the payment. * * @since 1.8.2 * * @param int $id Payment ID to restore from trash. * * @return bool */ private function process_action_restore( $id ) { return wpforms()->obj( 'payment' )->update( $id, [ 'is_published' => 1 ] ); } /** * Delete the payment. * * @since 1.8.2 * * @param int $id Payment ID to delete. * * @return bool */ private function process_action_delete( $id ) { return wpforms()->obj( 'payment' )->delete( $id ); } /** * Display a bulk action message. * * @since 1.8.2 * * @param int $count Count of processed payment IDs. */ private function display_bulk_action_message( $count ) { switch ( $this->action ) { case 'delete': /* translators: %d - number of deleted payments. */ $message = sprintf( _n( '%d payment was successfully permanently deleted.', '%d payments were successfully permanently deleted.', $count, 'wpforms-lite' ), number_format_i18n( $count ) ); break; case 'restore': /* translators: %d - number of restored payments. */ $message = sprintf( _n( '%d payment was successfully restored.', '%d payments were successfully restored.', $count, 'wpforms-lite' ), number_format_i18n( $count ) ); break; case 'trash': /* translators: %d - number of trashed payments. */ $message = sprintf( _n( '%d payment was successfully moved to the Trash.', '%d payments were successfully moved to the Trash.', $count, 'wpforms-lite' ), number_format_i18n( $count ) ); break; default: $message = ''; } if ( empty( $message ) ) { return; } Notice::success( $message ); } /** * Determine whether the action is allowed. * * @since 1.8.2 * * @param string $action Action name. * * @return bool */ private function is_allowed_action( $action ) { return in_array( $action, self::ALLOWED_ACTIONS, true ); } } Payments/Views/Single.php 0000644 00000113220 15252506741 0011401 0 ustar 00 <?php namespace WPForms\Admin\Payments\Views; use WPForms\Admin\Payments\ScreenOptions; use WPForms\Admin\Payments\Views\Overview\Helpers; use WPForms\Db\Payments\ValueValidator; /** * Payments Overview Page class. * * @since 1.8.2 */ class Single implements PaymentsViewsInterface { /** * Abort. Bail on proceeding to process the page. * * @since 1.8.2 * * @var bool */ private $abort = false; /** * The human readable error message. * * @since 1.8.2 * * @var string */ private $abort_message; /** * Payment object. * * @since 1.8.2 * * @var object */ private $payment; /** * Payment meta. * * @since 1.8.2 * * @var array */ private $payment_meta; /** * Subscription object, if applicable. * * @since 1.8.4 * * @var object */ private $subscription; /** * Subscription meta, if applicable. * * @since 1.8.4 * * @var array */ private $subscription_meta; /** * Subscription renewal payments, if applicable. * This is an array of payment objects. * * @since 1.8.4 * * @var array */ private $renewals = []; /** * Initialize class. * * @since 1.8.2 */ public function init() { $this->setup(); $this->hooks(); } /** * Register hooks. * * @since 1.8.2 */ private function hooks() { add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] ); } /** * Get the tab label. * * @since 1.8.2.2 * * @return string */ public function get_tab_label() { return ''; } /** * Enqueue scripts and styles. * * @since 1.8.2 */ public function enqueue_assets() { $min = wpforms_get_min_suffix(); wp_enqueue_style( 'tooltipster', WPFORMS_PLUGIN_URL . 'assets/lib/jquery.tooltipster/jquery.tooltipster.min.css', null, '4.2.6' ); wp_enqueue_script( 'tooltipster', WPFORMS_PLUGIN_URL . 'assets/lib/jquery.tooltipster/jquery.tooltipster.min.js', [ 'jquery' ], '4.2.6', true ); wp_enqueue_script( 'wpforms-admin-payments-single', WPFORMS_PLUGIN_URL . "assets/js/admin/payments/single{$min}.js", [ 'tooltipster' ], WPFORMS_VERSION, true ); wp_localize_script( 'wpforms-admin-payments-single', 'wpforms_admin_payments_single', [ 'payment_delete_confirm' => esc_html__( 'Are you sure you want to delete this payment and all its information (details, notes, etc.)?', 'wpforms-lite' ), 'payment_refund_confirm' => esc_html__( 'Are you sure you want to refund this payment?', 'wpforms-lite' ), 'payment_cancel_confirm' => esc_html__( 'Are you sure you want to cancel this subscription?', 'wpforms-lite' ), 'payment_refund_success' => esc_html__( 'Payment was successfully refunded!', 'wpforms-lite' ), 'payment_cancel_success' => esc_html__( 'Subscription was successfully canceled!', 'wpforms-lite' ), ] ); } /** * Setup data. * * @since 1.8.2 */ private function setup() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $payment_id = ! empty( $_GET['payment_id'] ) ? absint( $_GET['payment_id'] ) : 0; if ( ! $payment_id ) { $this->abort_message = esc_html__( 'It looks like the provided payment ID is not valid.', 'wpforms-lite' ); $this->abort = true; return; } $this->payment = wpforms()->obj( 'payment' )->get( $payment_id ); // No payment was found. if ( empty( $this->payment ) ) { $this->abort_message = esc_html__( 'It looks like the payment you are trying to access is no longer available.', 'wpforms-lite' ); $this->abort = true; return; } // Payment in the Trash. if ( ! $this->payment->is_published ) { $this->abort_message = esc_html__( "You can't edit this payment because it's in the trash.", 'wpforms-lite' ); $this->abort = true; return; } $this->payment_meta = wpforms()->obj( 'payment_meta' )->get_all( $payment_id ); // Retrieve the subscription renewal payments, if applicable. if ( ! empty( $this->payment->subscription_id ) ) { // Assign renewals to reduce queries and reuse later. list( $this->subscription, $this->renewals ) = wpforms()->obj( 'payment_queries' )->get_subscription_payment_history( $this->payment->subscription_id, $this->payment->currency ); if ( ! empty( $this->subscription ) ) { $this->subscription_meta = wpforms()->obj( 'payment_meta' )->get_all( $this->subscription->id ); } } } /** * Check if the current user has the capability to view the page. * * @since 1.8.2 * * @return bool */ public function current_user_can() { return wpforms_current_user_can(); } /** * Page heading. * * @since 1.8.2 */ public function heading() { if ( $this->abort ) { return; } $payment_prev = wpforms()->obj( 'payment_queries' )->get_prev( $this->payment->id, [ 'mode' => $this->payment->mode ] ); $payment_next = wpforms()->obj( 'payment_queries' )->get_next( $this->payment->id, [ 'mode' => $this->payment->mode ] ); $prev_url = ! empty( $payment_prev ) ? add_query_arg( [ 'page' => 'wpforms-payments', 'view' => 'payment', 'payment_id' => (int) $payment_prev->id, ], admin_url( 'admin.php' ) ) : ''; $next_url = ! empty( $payment_next ) ? add_query_arg( [ 'page' => 'wpforms-payments', 'view' => 'payment', 'payment_id' => (int) $payment_next->id, ], admin_url( 'admin.php' ) ) : ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wpforms_render( 'admin/payments/single/heading-navigation', [ 'count' => (int) wpforms()->obj( 'payment_queries' )->count_all( [ 'mode' => $this->payment->mode ] ), 'prev_count' => (int) wpforms()->obj( 'payment_queries' )->get_prev_count( $this->payment->id, [ 'mode' => $this->payment->mode ] ), 'prev_url' => $prev_url, 'prev_class' => empty( $payment_prev ) ? 'inactive' : '', 'next_url' => $next_url, 'next_class' => empty( $payment_next ) ? 'inactive' : '', 'overview_url' => add_query_arg( [ 'page' => 'wpforms-payments', ], admin_url( 'admin.php' ) ), ], true ); } /** * Page content. * * @since 1.8.2 */ public function display() { if ( $this->abort ) { echo '<div class="wpforms-admin-content">'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wpforms_render( 'admin/payments/single/no-payment', [ 'message' => $this->abort_message, ], true ); echo '</div>'; return; } $screen_options = ScreenOptions::get_single_page_options(); echo '<div id="poststuff"><div id="post-body" class="metabox-holder columns-2">'; echo '<div id="post-body-content">'; $this->payment_details(); $this->education_details(); $this->subscription_details(); $this->subscription_payment_history(); if ( ! empty( $screen_options['advanced'] ) ) { $this->advanced_details(); } $this->entry_details(); echo '</div>'; echo '<div id="postbox-container-1" class="postbox-container">'; $this->details(); if ( ! empty( $screen_options['log'] ) ) { $this->log(); } echo '</div></div></div>'; } /** * Payment details output. * * @since 1.8.2 */ private function payment_details() { $payment_type_class = ! empty( $this->payment->subscription_id ) ? 'subscription' : 'one-time'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wpforms_render( 'admin/payments/single/payment-details', [ 'id' => 'wpforms-payment-info', 'class' => 'payment-details', 'title' => __( 'Payment Details', 'wpforms-lite' ), 'payment_id' => "#{$this->payment->id}", 'gateway_link' => $this->get_gateway_transaction_link(), 'gateway_text' => sprintf( /* translators: %s - payment gateway name. */ __( 'View in %s', 'wpforms-lite' ), $this->get_gateway_name() ), 'gateway_name' => $this->payment->gateway, 'gateway_action_text' => __( 'Refund', 'wpforms-lite' ), 'gateway_action_slug' => 'refund', 'gateway_action_link' => $this->get_gateway_action_link( 'refund' ), 'payment_id_raw' => $this->payment->id, 'status' => $this->payment->status, 'status_label' => $this->get_status_label(), 'disabled' => $this->payment->status === 'refunded', 'stat_cards' => [ 'total' => [ 'label' => esc_html__( 'Total', 'wpforms-lite' ), 'value' => wpforms_format_amount( wpforms_sanitize_amount( $this->payment->total_amount, $this->payment->currency ), true, $this->payment->currency ), 'button_classes' => [ 'total', 'is-amount', ], ], 'type' => [ 'label' => esc_html__( 'Type', 'wpforms-lite' ), 'value' => $this->get_payment_type(), 'button_classes' => [ $payment_type_class, ], ], 'method' => [ 'label' => esc_html__( 'Method', 'wpforms-lite' ), 'value' => $this->get_payment_method(), 'button_classes' => [ 'method', ], 'tooltip' => $this->get_payment_method_details(), ], 'coupon' => [ 'label' => esc_html__( 'Coupon', 'wpforms-lite' ), 'value' => $this->get_coupon_value(), 'button_classes' => [ 'coupon', 'upsell', ], 'tooltip' => nl2br( $this->get_coupon_info() ), ], ], ], true ); } /** * Subscription details output. * * @since 1.8.2 */ private function subscription_details() { if ( empty( $this->subscription ) ) { return; } // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wpforms_render( 'admin/payments/single/payment-details', [ 'id' => 'wpforms-subscription-details', 'class' => 'subscription-details', 'title' => __( 'Subscription Details', 'wpforms-lite' ), 'gateway_link' => $this->get_gateway_subscription_link(), 'gateway_text' => sprintf( /* translators: %s - payment gateway name. */ __( 'View in %s', 'wpforms-lite' ), $this->get_gateway_name() ), 'gateway_name' => $this->payment->gateway, 'gateway_action_text' => __( 'Cancel', 'wpforms-lite' ), 'gateway_action_slug' => 'cancel', 'gateway_action_link' => $this->get_gateway_action_link( 'cancel' ), 'payment_id_raw' => $this->subscription->id, 'status' => $this->subscription->subscription_status, 'status_label' => ValueValidator::get_allowed_subscription_statuses()[ $this->subscription->subscription_status ], 'disabled' => in_array( $this->subscription->subscription_status, [ 'cancelled', 'completed' ], true ), 'stat_cards' => [ 'total' => [ 'label' => esc_html__( 'Lifetime Total', 'wpforms-lite' ), 'value' => $this->get_subscription_lifetime_total(), 'button_classes' => [ 'lifetime-total', 'is-amount', ], ], 'cycle' => [ 'label' => esc_html__( 'Billing Cycle', 'wpforms-lite' ), 'value' => $this->get_subscription_cycle(), 'button_classes' => [ 'cycle', ], ], 'billed' => [ 'label' => esc_html__( 'Times Billed', 'wpforms-lite' ), 'value' => $this->get_subscription_times_billed(), 'button_classes' => [ 'cycle', ], ], 'renewal' => [ 'label' => esc_html__( 'Renewal Date', 'wpforms-lite' ), 'value' => $this->get_renewal_date(), 'button_classes' => [ 'date', ], ], ], ], true ); } /** * Subscription payment history output. * * @since 1.8.4 */ private function subscription_payment_history() { // Early bail if no subscription ID. if ( empty( $this->payment->subscription_id ) ) { return; } // Early bail if no subscription or renewals found. // "$this->renewals" is set in the "setup" method. if ( empty( $this->renewals ) ) { return; } // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wpforms_render( 'admin/payments/single/payment-history', [ 'title' => __( 'Payment History', 'wpforms-lite' ), 'renewals' => $this->renewals, 'types' => ValueValidator::get_allowed_subscription_types(), 'statuses' => ValueValidator::get_allowed_statuses(), 'placeholder_na_text' => Helpers::get_placeholder_na_text( false ), 'single_url' => add_query_arg( [ 'page' => 'wpforms-payments', 'view' => 'payment', ], admin_url( 'admin.php' ) ), ], true ); } /** * Get Subscription cycle. * * @since 1.8.2 * * @return string */ private function get_subscription_cycle() { $allowed_intervals = ValueValidator::get_allowed_subscription_intervals(); if ( isset( $this->subscription_meta['subscription_period']->value, $allowed_intervals[ $this->subscription_meta['subscription_period']->value ] ) ) { $amount = wpforms_format_amount( wpforms_sanitize_amount( $this->payment->total_amount, $this->payment->currency ), true, $this->payment->currency ); $interval = $allowed_intervals[ $this->subscription_meta['subscription_period']->value ]; return "{$amount} / {$interval}"; } return Helpers::get_placeholder_na_text( false ); } /** * Get Subscription lifetime total. * * @since 1.8.4 * * @return string */ private function get_subscription_lifetime_total() { return wpforms_format_amount( (float) $this->subscription->total_amount + array_sum( array_column( $this->renewals, 'total_amount' ) ), true, $this->payment->currency ); } /** * Get Subscription times billed. * * @since 1.8.4 * * @return int|string */ private function get_subscription_times_billed() { // Display "N/A", in case no subscription ID is found. if ( empty( $this->payment->subscription_id ) ) { return Helpers::get_placeholder_na_text( false ); } // Add the initial subscription payment object to the renewal array. // The "+1" has to be added, because the initial subscription payment is not included in the renewals array. if ( ! empty( $this->subscription ) ) { $this->renewals[] = $this->subscription; } return count( $this->renewals ); } /** * Get Subscription renewal date. * * @since 1.8.2 * * @return string */ private function get_renewal_date() { if ( $this->is_renewal_of_finished_subscription() || in_array( $this->payment->subscription_status, $this->get_finished_subscription_statuses(), true ) || $this->is_final_renewal_reached() ) { return Helpers::get_placeholder_na_text( false ); } $converted_periods = [ 'daily' => '+1 day', 'weekly' => '+1 week', 'monthly' => '+1 month', 'quarterly' => '+3 month', 'semiyearly' => '+6 month', 'yearly' => '+1 year', ]; if ( ! isset( $this->subscription_meta['subscription_period']->value, $converted_periods[ $this->subscription_meta['subscription_period']->value ] ) ) { return ''; } return gmdate( 'M d, Y', strtotime( $this->get_latest_subscription_payment_date() . $converted_periods[ $this->subscription_meta['subscription_period']->value ] ) ); } /** * Get the most recent billing date across the subscription and its renewals. * * The renewal date must reflect the latest billing in the subscription rather than the payment * currently being viewed, so it advances as renewals are processed regardless of which payment * page is open. The billing date is "date_created_gmt" (the Stripe period start), not * "date_updated_gmt" which only tracks when the row was last written. * * @since 2.0.0 * * @return string GMT datetime string. */ private function get_latest_subscription_payment_date(): string { $dates = []; if ( ! empty( $this->subscription->date_created_gmt ) ) { $dates[] = $this->subscription->date_created_gmt; } foreach ( $this->renewals as $renewal ) { if ( ! empty( $renewal->date_created_gmt ) ) { $dates[] = $renewal->date_created_gmt; } } // Date strings in "Y-m-d H:i:s" format compare lexicographically as chronologically. return $dates ? max( $dates ) : $this->payment->date_created_gmt; } /** * Is renewal of a finished (cancelled or completed) subscription. * * @since 1.8.4 * * @return bool */ private function is_renewal_of_finished_subscription(): bool { return $this->payment->type === 'renewal' && ! empty( $this->subscription ) && in_array( $this->subscription->subscription_status, $this->get_finished_subscription_statuses(), true ); } /** * Get subscription statuses that represent a finished subscription with no future renewal. * * @since 1.10.2 * * @return string[] */ private function get_finished_subscription_statuses(): array { return [ 'cancelled', 'completed' ]; } /** * Whether the subscription's final renewal has already been billed. * * Limited-cycle subscriptions stay active until their scheduled end date, so the next renewal date * must read N/A once the last cycle is processed instead of pointing to a period that never bills. * * @since 2.0.0 * * @return bool */ private function is_final_renewal_reached(): bool { return ! empty( $this->subscription_meta['subscription_final_renewal']->value ); } /** * Get payment type name. * i.e. One-time, Subscription, etc. * * @since 1.8.4 * * @return string */ private function get_payment_type() { if ( isset( $this->payment->type ) && ValueValidator::is_valid( $this->payment->type, 'type' ) ) { return ValueValidator::get_allowed_types()[ $this->payment->type ]; } return Helpers::get_placeholder_na_text( false ); } /** * Get payment method type. * * @since 1.8.2 * * @return string */ private function get_payment_method(): string { $method = isset( $this->payment_meta['credit_card_method'] ) ? ucfirst( $this->payment_meta['credit_card_method']->value ) : ''; if ( $method ) { return $method; } if ( ! isset( $this->payment_meta['method_type'] ) ) { return Helpers::get_placeholder_na_text( false ); } return $this->get_formatted_payment_method(); } /** * Get payment method details. * * @since 1.8.2 * * @return string */ private function get_payment_method_details(): string { if ( empty( $this->payment_meta['credit_card_last4'] ) ) { return ''; } $rows = []; // 1. Credit Card Name. if ( ! empty( $this->payment_meta['credit_card_name']->value ) ) { $rows[] = $this->payment_meta['credit_card_name']->value; } // 2. Credit Card Last 4 digits. $rows[] = "xxxx xxxx xxxx {$this->payment_meta['credit_card_last4']->value}"; // 3. Credit Card Expiry Date. if ( ! empty( $this->payment_meta['credit_card_expires']->value ) ) { $rows[] = sprintf( /* translators: %s - credit card expiry date. */ __( 'Expires %s', 'wpforms-lite' ), $this->payment_meta['credit_card_expires']->value ); } // 4. Payment Method Type. if ( ! empty( $this->payment_meta['method_type']->value ) ) { $rows[] = sprintf( /* translators: %s - credit card expiry date. */ __( 'Method: %s', 'wpforms-lite' ), $this->get_formatted_payment_method() ); } // Escape all rows. $rows = array_map( 'esc_html', $rows ); // Wrap each row in a span tag. $output = '<div><span>'; $output .= implode( '</span></br><span>', $rows ); return $output . '</span></div>'; } /** * Retrieves the formatted payment method name. * * Converts the payment method type from a stored format (e.g., snake_case or kebab-case) * into a human-readable string with each word capitalized. * * @since 1.10.0 * * @return string The formatted payment method name. */ private function get_formatted_payment_method(): string { $method_type = $this->payment_meta['method_type']->value; $parts = preg_split( '/[-_]/', $method_type ); $parts = array_map( 'ucfirst', $parts ); return implode( ' ', $parts ); } /** * Get coupon info. * * @since 1.8.2.2 * * @return string */ private function get_coupon_info() { $coupon_info = ! empty( $this->payment_meta['coupon_info']->value ) ? $this->payment_meta['coupon_info']->value : ''; /** * Allow modifying coupon info. * * @since 1.8.2.2 * * @param string $coupon_info Coupon info. * @param object $payment Payment object. * @param array $payment_meta Payment meta. */ return apply_filters( 'wpforms_admin_payments_views_single_get_coupon_info', $coupon_info, $this->payment, $this->payment_meta ); } /** * Get coupon value. * * @since 1.8.2.2 * * @return string */ private function get_coupon_value() { return ! empty( $this->payment_meta['coupon_value']->value ) ? sprintf( '-%s', $this->payment_meta['coupon_value']->value ) : ''; } /** * Education notice for lite users output. * * @since 1.8.2 */ private function education_details() { if ( in_array( wpforms_get_license_type(), [ 'pro', 'elite', 'agency', 'ultimate' ], true ) ) { return; } $dismissed = get_user_meta( get_current_user_id(), 'wpforms_dismissed', true ); if ( ! empty( $dismissed['edu-single-payment'] ) ) { return; } // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wpforms_render( 'education/admin/payments/single-page' ); } /** * Advanced details output. * * @since 1.8.2 */ private function advanced_details() { /** * Allow to modify a single payment page advanced details list. * * @since 1.8.2 * * @param array $list Advanced details to show. * @param object $payment Payment object. */ $details_list = (array) apply_filters( 'wpforms_admin_payments_views_single_advanced_details_list', [ 'transaction_id' => [ 'label' => __( 'Transaction ID', 'wpforms-lite' ), 'link' => $this->get_gateway_transaction_link(), 'value' => $this->payment->transaction_id, ], 'subscription_id' => [ 'label' => __( 'Subscription ID', 'wpforms-lite' ), 'link' => $this->get_gateway_subscription_link(), 'value' => $this->payment->subscription_id, ], 'customer_id' => [ 'label' => __( 'Customer ID', 'wpforms-lite' ), 'link' => $this->get_gateway_customer_link(), 'value' => $this->payment->customer_id, ], 'customer_ip' => [ 'label' => __( 'Customer IP Address', 'wpforms-lite' ), 'value' => ! empty( $this->payment_meta['ip_address']->value ) ? $this->payment_meta['ip_address']->value : false, ], 'payment_method' => [ 'label' => __( 'Payment Method', 'wpforms-lite' ), 'value' => $this->get_payment_method_details(), ], 'coupon_info' => [ 'label' => __( 'Coupon', 'wpforms-lite' ), 'value' => $this->get_coupon_info(), ], ], $this->payment ); // Skip empty details. $details_list = array_filter( $details_list, static function ( $item ) { return ! empty( $item['value'] ); } ); // Return early if there are no details. if ( empty( $details_list ) ) { return; } // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wpforms_render( 'admin/payments/single/advanced-details', [ 'details_list' => $details_list, ], true ); } /** * Entry details output. * * @since 1.8.2 */ private function entry_details() { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks $entry_id_title = ''; $fields = ''; $entry_status = ''; // Grab submitted values from the entry if it exists. if ( ! empty( $this->payment->entry_id ) && wpforms()->is_pro() ) { $entry = wpforms()->obj( 'entry' )->get( $this->payment->entry_id ); if ( $entry ) { $fields = wpforms_decode( $entry->fields ); $entry_id_title .= "#{$this->payment->entry_id}"; $entry_status = $entry->status; } } // Otherwise, grab submitted values from the payment meta if it exists. if ( empty( $fields ) && ! empty( $this->payment_meta['fields'] ) ) { $fields = wpforms_decode( $this->payment_meta['fields']->value ); } // Bail early if there are submitted values. if ( empty( $fields ) ) { return; } /** * Allow modifying the form data before rendering the entry details. * * @since 1.8.9 * * @param array $form_data Form data. * @param array $fields Entry fields. */ $form_data = apply_filters( 'wpforms_admin_payments_views_single_form_data', wpforms()->obj( 'form' )->get( $this->payment->form_id, [ 'content_only' => true ] ), $fields ); add_filter( 'wp_kses_allowed_html', [ $this, 'modify_allowed_tags_payment_field_value' ], 10, 2 ); /** * Allow modifying the entry fields before rendering the entry details. * * @since 1.8.9 * * @param array $entry_fields Entry fields. * @param array $form_data Form data. */ $entry_fields = apply_filters( 'wpforms_admin_payments_views_single_fields', $this->prepare_entry_fields( $fields, $form_data ), $form_data ); $entry_output = wpforms_render( 'admin/payments/single/entry-details', [ 'entry_fields' => $entry_fields, 'form_data' => $form_data, 'entry_id_title' => $entry_id_title, 'entry_id' => $this->payment->entry_id, 'entry_status' => $entry_status, 'entry_url' => add_query_arg( [ 'page' => 'wpforms-entries', 'view' => 'details', 'entry_id' => $this->payment->entry_id, ], admin_url( 'admin.php' ) ), ], true ); remove_filter( 'wp_kses_allowed_html', [ $this, 'modify_allowed_tags_payment_field_value' ] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo $entry_output; } /** * Prepare entry fields. * * @since 1.8.2 * * @param array $fields Entry fields. * @param array $form_data Form data. * * @return array */ private function prepare_entry_fields( $fields, $form_data ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh if ( empty( $form_data['fields'] ) || empty( $fields ) ) { return []; } $prepared_fields = []; // Display the fields and their values. foreach ( $form_data['fields'] as $key => $field_data ) { if ( empty( $field_data['type'] ) ) { continue; } $field_type = $field_data['type']; // Add repeater and layout fields as is. if ( in_array( $field_type, [ 'repeater', 'layout' ], true ) && wpforms()->is_pro() ) { $prepared_fields[ $key ] = $field_data; continue; } $field = $fields[ $field_data['id'] ] ?? []; if ( empty( $field ) || ! isset( $field['id'] ) ) { continue; } // phpcs:disable WPForms.PHP.ValidateHooks.InvalidHookName /** This filter is documented in /src/Pro/Admin/Entries/Edit.php */ if ( $this->payment->entry_id && ! (bool) apply_filters( "wpforms_pro_admin_entries_edit_is_field_displayable_{$field_type}", true, $field, $form_data ) ) { continue; } $field_value = isset( $field['value'] ) ? $field['value'] : ''; /** This filter is documented in src/SmartTags/SmartTag/FieldHtmlId.php.*/ $prepared_fields[ $key ]['field_value'] = apply_filters( 'wpforms_html_field_value', wp_strip_all_tags( $field_value ), $field, $form_data, 'payment-single' ); // phpcs:enable WPForms.PHP.ValidateHooks.InvalidHookName $prepared_fields[ $key ]['field_class'] = sanitize_html_class( 'wpforms-field-' . $field_type ); $prepared_fields[ $key ]['type'] = $field_type; $prepared_fields[ $key ]['id'] = $field_data['id']; $prepared_fields[ $key ]['field_name'] = ! empty( $field['name'] ) ? $field['name'] : sprintf( /* translators: %d - field ID. */ esc_html__( 'Field ID #%d', 'wpforms-lite' ), absint( $field['id'] ) ); $is_empty_value = wpforms_is_empty_string( $field_value ); $is_empty_quantity = isset( $field['quantity'] ) && ! $field['quantity']; if ( $is_empty_value ) { $prepared_fields[ $key ]['field_value'] = esc_html__( 'Empty', 'wpforms-lite' ); } if ( $is_empty_value || $is_empty_quantity ) { $prepared_fields[ $key ]['field_class'] .= ' empty'; } } return $prepared_fields; } /** * Allow additional tags for the wp_kses_post function. * * @since 1.8.2 * * @param array $allowed_html List of allowed HTML. * @param string $context Context name. * * @return array */ public function modify_allowed_tags_payment_field_value( $allowed_html, $context ) { if ( $context !== 'post' ) { return $allowed_html; } $allowed_html['iframe'] = [ 'data-src' => [], 'class' => [], ]; return $allowed_html; } /** * Details metabox output. * * @since 1.8.2 */ private function details() { $form_edit_link = $this->get_form_edit_link(); $date = sprintf( /* translators: %1$s - date, %2$s - time when item was created, e.g. "Oct 22, 2022 at 11:11 am". */ __( '%1$s at %2$s', 'wpforms-lite' ), wpforms_date_format( $this->payment->date_created_gmt, 'M j, Y', true ), wpforms_time_format( $this->payment->date_created_gmt, '', true ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wpforms_render( 'admin/payments/single/details', [ 'payment' => $this->payment, 'submitted' => $date, 'gateway_name' => $this->get_gateway_name(), 'gateway_link' => $this->get_gateway_dashboard_link(), 'form_edit_link' => ! empty( $form_edit_link ) ? $form_edit_link : Helpers::get_placeholder_na_text(), 'test_mode' => $this->payment->mode === 'test', 'delete_link' => wp_nonce_url( add_query_arg( [ 'page' => 'wpforms-payments', 'action' => 'delete', 'payment_id' => $this->payment->id, ], admin_url( 'admin.php' ) ), 'bulk-wpforms_page_wpforms-payments' ), ], true ); } /** * Logs metabox output. * * @since 1.8.2 */ private function log() { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wpforms_render( 'admin/payments/single/log', [ 'logs' => wpforms()->obj( 'payment_meta' )->get_all_by( 'log', $this->payment->id ), ], true ); } // TODO: Remove hardcoded values in methods below after all payment addons updated to use new filters. /** * Get gateway transaction link. * * @since 1.8.2 * * @return string */ private function get_gateway_transaction_link() { /** * Allow to modify a single payment page gateway transaction link. * * @since 1.8.2 * * @param string $link Gateway transaction link. * @param object $payment Payment object. */ $link = apply_filters( 'wpforms_admin_payments_views_single_gateway_transaction_link', '', $this->payment ); if ( $link ) { return $link; } if ( ! $this->payment->transaction_id ) { return ''; } switch ( $this->payment->gateway ) { case 'stripe': $link = 'payments/'; break; case 'paypal_standard': case 'paypal_commerce': $link = 'activity/payment/'; break; case 'square': $link = 'sales/transactions/'; break; default: $link = ''; break; } if ( ! $link ) { return $this->get_gateway_dashboard_link(); } return $this->get_gateway_dashboard_link() . $link . $this->payment->transaction_id; } /** * Get gateway subscription link. * * @since 1.8.2 * * @return string */ private function get_gateway_subscription_link() { /** * Allow to modify a single payment page gateway subscription link. * * @since 1.8.2 * * @param string $link Gateway subscription link. * @param object $payment Payment object. */ $link = apply_filters( 'wpforms_admin_payments_views_single_gateway_subscription_link', '', $this->payment ); if ( $link ) { return $link; } if ( $this->payment->gateway === 'paypal_commerce' ) { return $this->get_paypal_subscription_link(); } switch ( $this->payment->gateway ) { case 'square': case 'stripe': $link = 'subscriptions/'; break; default: $link = ''; break; } if ( ! $link ) { return $this->get_gateway_dashboard_link(); } return $this->get_gateway_dashboard_link() . $link . $this->payment->subscription_id; } /** * Generates the PayPal subscription link based on payment metadata. * * @since 1.10.0 * * @return string PayPal subscription link. */ private function get_paypal_subscription_link(): string { $dashboard = $this->get_gateway_dashboard_link(); if ( ! isset( $this->payment_meta['processor_type'] ) ) { return $dashboard . 'billing/subscriptions/' . $this->payment->subscription_id; } $url = $dashboard . 'unifiedtransactions/'; if ( empty( $this->payment_meta['payer_email'] ) ) { return $url; } return add_query_arg( [ 'filter' => '1', 'query' => rawurlencode( $this->payment_meta['payer_email']->value ), ], $url ); } /** * Get gateway customer link. * * @since 1.8.2 * * @return string */ private function get_gateway_customer_link() { /** * Allow to modify a single payment page gateway customer link. * * @since 1.8.2 * * @param string $link Gateway customer link. * @param object $payment Payment object. */ $link = apply_filters( 'wpforms_admin_payments_views_single_gateway_customer_link', '', $this->payment ); if ( $link ) { return $link; } if ( in_array( $this->payment->gateway, [ 'paypal_commerce', 'paypal_standard' ], true ) ) { return $this->get_gateway_dashboard_link() . 'unifiedtransactions/customers/'; } switch ( $this->payment->gateway ) { case 'stripe': $link = 'customers/'; break; case 'square': $link = 'customers/directory/customer/'; break; default: $link = ''; break; } if ( ! $link ) { return $this->get_gateway_dashboard_link(); } return $this->get_gateway_dashboard_link() . $link . $this->payment->customer_id; } /** * Get gateway dashboard link. * * @since 1.8.2 * * @return string */ private function get_gateway_dashboard_link() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh /** * Allow to modify a single payment page gateway dashboard link. * * @since 1.8.2 * * @param string $link Gateway dashboard link. * @param object $payment Payment object. */ $link = apply_filters( 'wpforms_admin_payments_views_single_gateway_dashboard_link', '', $this->payment ); if ( $link ) { return $link; } $is_test_mode = $this->payment->mode === 'test'; // Backward compatibility until all addons has been updated. switch ( $this->payment->gateway ) { case 'stripe': $link = $is_test_mode ? 'https://dashboard.stripe.com/test/' : 'https://dashboard.stripe.com/'; break; case 'paypal_standard': case 'paypal_commerce': $link = $is_test_mode ? 'https://www.sandbox.paypal.com/' : 'https://www.paypal.com/'; break; case 'authorize_net': $link = $is_test_mode ? 'https://sandbox.authorize.net/' : 'https://account.authorize.net/'; break; case 'square': $link = $is_test_mode ? 'https://squareupsandbox.com/dashboard/' : 'https://squareup.com/t/cmtp_performance/pr_developers/d_partnerships/p_0010L00001tJz7nQAC/?route=dashboard/'; break; default: $link = ''; break; } return $link; } /** * Get gateway action link. * * @since 1.8.2 * * @param string $action Action. * * @return string */ private function get_gateway_action_link( $action ) { /** * Allow to modify a single payment page gateway action link. * * @since 1.8.2 * * @param string $link Gateway action link. * @param string $action Action to perform. * @param object $payment Payment object. */ $link = apply_filters( 'wpforms_admin_payments_views_single_gateway_action_link', '', $action, $this->payment ); if ( $link ) { return $link; } // Backward compatibility until all addons has been updated. if ( $action === 'refund' ) { return $this->get_gateway_transaction_link(); } return $this->get_gateway_subscription_link(); } /** * Retrieve a readable payment gateway name. * * @since 1.8.2 * * @return string */ private function get_gateway_name() { $gateway_name = Helpers::get_placeholder_na_text( false ); if ( isset( $this->payment->gateway ) && ValueValidator::is_valid( $this->payment->gateway, 'gateway' ) ) { $gateway_name = ValueValidator::get_allowed_gateways()[ $this->payment->gateway ]; } return $gateway_name; } /** * Retrieve a readable payment status label. * * @since 1.8.4 * * @return string */ private function get_status_label() { $label = ValueValidator::get_allowed_one_time_statuses()[ $this->payment->status ]; if ( $this->payment->status !== 'partrefund' ) { return $label; } $refunded_amount = isset( $this->payment_meta['refunded_amount']->value ) ? wpforms_sanitize_amount( $this->payment_meta['refunded_amount']->value, $this->payment->currency ) : 0; $label .= ' <span>('; $label .= wpforms_format_amount( $refunded_amount, true, $this->payment->currency ); $label .= ')</span>'; return $label; } /** * If the form is still available, return a link to edit it. * Otherwise, return an empty string. * * @since 1.8.4 * * @return string */ private function get_form_edit_link() { // Leave early if no form ID is found. if ( ! $this->payment->form_id ) { return ''; } $form = wpforms()->obj( 'form' )->get( $this->payment->form_id ); // Leave early if form is no longer available. if ( ! $form || $form->post_status !== 'publish' ) { return ''; } $name = ! empty( $form->post_title ) ? $form->post_title : $form->post_name; $url = add_query_arg( [ 'view' => 'fields', 'page' => 'wpforms-builder', 'form_id' => $this->payment->form_id, ], admin_url( 'admin.php' ) ); return sprintf( '<a href="%1$s" class="wpforms-link">%2$s</a>', esc_url( $url ), wp_kses_post( $name ) ); } } Payments/Views/Coupons/Education.php 0000644 00000010541 15252506741 0013523 0 ustar 00 <?php namespace WPForms\Admin\Payments\Views\Coupons; use WPForms\Admin\Payments\Views\Overview\Helpers; use WPForms\Admin\Payments\Views\PaymentsViewsInterface; /** * Payments Coupons Education class. * * @since 1.8.2.2 */ class Education implements PaymentsViewsInterface { /** * Coupons addon data. * * @since 1.8.2.2 * * @var array */ private $addon; /** * Initialize class. * * @since 1.8.2.2 */ public function init() { $this->hooks(); } /** * Register hooks. * * @since 1.8.2.2 */ private function hooks() { add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] ); } /** * Get the page label. * * @since 1.8.2.2 * * @return string */ public function get_tab_label() { return __( 'Coupons', 'wpforms-lite' ); } /** * Enqueue scripts. * * @since 1.8.2.2 */ public function enqueue_scripts() { // Lity - lightbox for images. wp_enqueue_style( 'wpforms-lity', WPFORMS_PLUGIN_URL . 'assets/lib/lity/lity.min.css', null, '3.0.0' ); wp_enqueue_script( 'wpforms-lity', WPFORMS_PLUGIN_URL . 'assets/lib/lity/lity.min.js', [ 'jquery' ], '3.0.0', true ); } /** * Check if the current user has the capability to view the page. * * @since 1.8.2.2 * * @return bool */ public function current_user_can() { if ( ! wpforms_current_user_can() ) { return false; } $this->addon = wpforms()->obj( 'addons' )->get_addon( 'coupons' ); if ( empty( $this->addon ) || empty( $this->addon['status'] ) || empty( $this->addon['action'] ) ) { return false; } return true; } /** * Page heading content. * * @since 1.8.2.2 */ public function heading() { Helpers::get_default_heading(); } /** * Page content. * * @since 1.8.2.2 */ public function display() { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wpforms_render( 'education/admin/page', $this->template_data(), true ); } /** * Get the template data. * * @since 1.8.6 * * @return array */ private function template_data(): array { $images_url = WPFORMS_PLUGIN_URL . 'assets/images/coupons-education/'; $utm_medium = 'Payments - Coupons'; $utm_content = 'Coupons Addon'; $upgrade_link = $this->addon['action'] === 'upgrade' ? sprintf( /* translators: %1$s - WPForms.com Upgrade page URL. */ ' <strong><a href="%1$s" target="_blank" rel="noopener noreferrer">%2$s</a></strong>', esc_url( wpforms_admin_upgrade_link( $utm_medium, $utm_content ) ), esc_html__( 'Upgrade to WPForms Pro', 'wpforms-lite' ) ) : ''; $params = [ 'features' => [ __( 'Custom Coupon Codes', 'wpforms-lite' ), __( 'Percentage or Fixed Discounts', 'wpforms-lite' ), __( 'Start and End Dates', 'wpforms-lite' ), __( 'Maximum Usage Limit', 'wpforms-lite' ), __( 'Once Per Email Address Limit', 'wpforms-lite' ), __( 'Usage Statistics', 'wpforms-lite' ), ], 'images' => [ [ 'url' => $images_url . 'coupons-addon-thumbnail-01.png', 'url2x' => $images_url . 'coupons-addon-screenshot-01.png', 'title' => __( 'Coupons Overview', 'wpforms-lite' ), ], [ 'url' => $images_url . 'coupons-addon-thumbnail-02.png', 'url2x' => $images_url . 'coupons-addon-screenshot-02.png', 'title' => __( 'Coupon Settings', 'wpforms-lite' ), ], ], 'utm_medium' => $utm_medium, 'utm_content' => $utm_content, 'upgrade_link' => $upgrade_link, 'heading_description' => '<p>' . sprintf( /* translators: %1$s - WPForms.com Upgrade page URL. */ esc_html__( 'With the Coupons addon, you can offer customers discounts using custom coupon codes. Create your own percentage or fixed rate discount, then add the Coupon field to any payment form. When a customer enters your unique code, they’ll receive the specified discount. You can also add limits to restrict when coupons are available and how often they can be used. The Coupons addon requires a license level of Pro or higher.%s', 'wpforms-lite' ), wp_kses( $upgrade_link, [ 'a' => [ 'href' => [], 'rel' => [], 'target' => [], ], 'strong' => [], ] ) ) . '</p>', 'features_description' => __( 'Easy to Use, Yet Powerful', 'wpforms-lite' ), ]; return array_merge( $params, $this->addon ); } } Payments/Views/PaymentsViewsInterface.php 0000644 00000001100 15252506741 0014610 0 ustar 00 <?php namespace WPForms\Admin\Payments\Views; interface PaymentsViewsInterface { /** * Initialize class. * * @since 1.8.2 */ public function init(); /** * Check if the current user has the capability to view the page. * * @since 1.8.2 * * @return bool */ public function current_user_can(); /** * Page heading content. * * @since 1.8.2 */ public function heading(); /** * Page content. * * @since 1.8.2 */ public function display(); /** * Get the Tab label. * * @since 1.8.2.2 */ public function get_tab_label(); } Payments/Payments.php 0000644 00000014063 15252506741 0010670 0 ustar 00 <?php namespace WPForms\Admin\Payments; use WPForms\Admin\Payments\Views\Coupons\Education; use WPForms\Admin\Payments\Views\Overview\BulkActions; use WPForms\Admin\Payments\Views\Single; use WPForms\Admin\Payments\Views\Overview\Page; use WPForms\Admin\Payments\Views\Overview\Coupon; use WPForms\Admin\Payments\Views\Overview\Filters; use WPForms\Admin\Payments\Views\Overview\Search; /** * Payments class. * * @since 1.8.2 */ class Payments { /** * Payments page slug. * * @since 1.8.2 * * @var string */ const SLUG = 'wpforms-payments'; /** * Available views (pages). * * @since 1.8.2 * * @var array */ private $views = []; /** * The current page slug. * * @since 1.8.2 * * @var string */ private $active_view_slug; /** * The current page view. * * @since 1.8.2 * * @var null|\WPForms\Admin\Payments\Views\PaymentsViewsInterface */ private $view; /** * Initialize class. * * @since 1.8.2 */ public function init() { if ( ! wpforms_is_admin_page( 'payments' ) ) { return; } $this->update_request_uri(); ( new ScreenOptions() )->init(); ( new Coupon() )->init(); ( new Filters() )->init(); ( new Search() )->init(); ( new BulkActions() )->init(); $this->hooks(); } /** * Initialize the active view. * * @since 1.8.2 */ public function init_view() { $view_ids = array_keys( $this->get_views() ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended $this->active_view_slug = isset( $_GET['view'] ) ? sanitize_key( $_GET['view'] ) : 'payments'; // If the user tries to load an invalid view - fallback to the first available. if ( ! in_array( $this->active_view_slug, $view_ids, true ) ) { $this->active_view_slug = reset( $view_ids ); } if ( ! isset( $this->views[ $this->active_view_slug ] ) ) { return; } $this->view = $this->views[ $this->active_view_slug ]; $this->view->init(); } /** * Get available views. * * @since 1.8.2 * * @return array */ private function get_views() { if ( ! empty( $this->views ) ) { return $this->views; } $views = [ 'coupons' => new Education(), ]; /** * Allow to extend payment views. * * @since 1.8.2 * * @param array $views Array of views where key is slug. */ $this->views = (array) apply_filters( 'wpforms_admin_payments_payments_get_views', $views ); $this->views['payments'] = new Page(); $this->views['payment'] = new Single(); // Payments view should be the first one. $this->views = array_merge( [ 'payments' => $this->views['payments'] ], $this->views ); $this->views = array_filter( $this->views, static function ( $view ) { return $view->current_user_can(); } ); return $this->views; } /** * Register hooks. * * @since 1.8.2 */ private function hooks() { add_action( 'wpforms_admin_page', [ $this, 'output' ] ); add_action( 'current_screen', [ $this, 'init_view' ] ); add_filter( 'wpforms_db_payments_payment_add_secondary_where_conditions_args', [ $this, 'modify_secondary_where_conditions_args' ] ); } /** * Output the page. * * @since 1.8.2 */ public function output() { if ( empty( $this->view ) ) { return; } ?> <div id="wpforms-payments" class="wrap wpforms-admin-wrap wpforms-payments-wrap wpforms-payments-wrap-<?php echo esc_attr( $this->active_view_slug ); ?>"> <h1 class="page-title"> <?php esc_html_e( 'Payments', 'wpforms-lite' ); ?> <?php $this->view->heading(); ?> </h1> <?php if ( ! empty( $this->view->get_tab_label() ) ) : ?> <div class="wpforms-tabs-wrapper"> <?php $this->display_tabs(); ?> </div> <?php endif; ?> <div class="wpforms-admin-content wpforms-admin-settings"> <?php $this->view->display(); ?> </div> </div> <?php } /** * Display tabs. * * @since 1.8.2.2 */ private function display_tabs() { $views = $this->get_views(); // Remove views that should not be displayed. $views = array_filter( $views, static function ( $view ) { return ! empty( $view->get_tab_label() ); } ); // If there is only one view - no need to display tabs. if ( count( $views ) === 1 ) { return; } ?> <nav class="nav-tab-wrapper"> <?php foreach ( $views as $slug => $view ) : ?> <a href="<?php echo esc_url( $this->get_tab_url( $slug ) ); ?>" class="nav-tab <?php echo $slug === $this->active_view_slug ? 'nav-tab-active' : ''; ?>"> <?php echo esc_html( $view->get_tab_label() ); ?> </a> <?php endforeach; ?> </nav> <?php } /** * Get tab URL. * * @since 1.8.2.2 * * @param string $tab Tab slug. * * @return string */ private function get_tab_url( $tab ) { return add_query_arg( [ 'page' => self::SLUG, 'view' => $tab, ], admin_url( 'admin.php' ) ); } /** * Modify arguments of secondary where clauses. * * @since 1.8.2 * * @param array $args Query arguments. * * @return array */ public function modify_secondary_where_conditions_args( $args ) { // Set a current mode. if ( ! isset( $args['mode'] ) ) { $args['mode'] = Page::get_mode(); } return $args; } /** * Update view param in request URI. * * Backward compatibility for old URLs. * * @since 1.8.4 */ private function update_request_uri() { // phpcs:disable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash if ( ! isset( $_GET['view'], $_SERVER['REQUEST_URI'] ) ) { return; } $old_new = [ 'single' => 'payment', 'overview' => 'payments', ]; if ( ! array_key_exists( $_GET['view'], $old_new ) || in_array( $_GET['view'], $old_new, true ) ) { return; } wp_safe_redirect( str_replace( 'view=' . $_GET['view'], 'view=' . $old_new[ $_GET['view'] ], esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) ) ); // phpcs:enable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash exit; } } EmailSendingErrors/Registry.php 0000644 00000015574 15252526345 0012646 0 ustar 00 <?php namespace WPMailSMTP\Admin\EmailSendingErrors; use WPMailSMTP\Helpers\Helpers; /** * Plugin-side mirror of the product-api error-doc registry. Fetches a single * mailer's structured-error-code → doc-url map on demand and caches it in a * per-mailer transient. * * @since 4.9.0 */ class Registry { /** * Fallback product-api base URL when WPMS_PRODUCT_API_BASE_URL is undefined. * * @since 4.9.0 */ const BASE_URL = 'https://wpmailsmtpapi.com'; /** * Product-api registry endpoint path, relative to the base URL. * * @since 4.9.0 */ const ENDPOINT_PATH = 'troubleshooting/v1/errors'; /** * Transient key for the per-mailer-keyed map. Each entry carries its own * `populated_at` so mailers refresh independently. * * @since 4.9.0 */ const TRANSIENT_KEY = 'wp_mail_smtp_email_sending_errors_registry'; /** * Per-mailer cache TTL, in seconds (1 week). * * @since 4.9.0 */ const TTL = WEEK_IN_SECONDS; /** * Transient key prefix for the per-mailer fetch-failure backoff. When the * remote API returns a non-200 response (or transport error), the next * fetch for that mailer is skipped until this transient expires — prevents * a thundering herd against our API and outbound HTTP spam from the host. * * @since 4.9.0 */ const FETCH_BACKOFF_TRANSIENT_PREFIX = 'wp_mail_smtp_email_sending_errors_registry_fetch_backoff_'; /** * Backoff window applied after a failed fetch, in seconds. * * @since 4.9.0 */ const FETCH_BACKOFF_TTL = HOUR_IN_SECONDS; /** * Look up the doc-url for a (mailer, code) pair. Returns null when the * mailer's entry does not list the code. * * A cached null under `codes[ $code ]` is a per-code negative marker: * the API has been asked for this code and didn't return it, so further * lookups short-circuit until the next full mailer refresh. * * @since 4.9.0 * * @param string $mailer Mailer slug ('gmail', 'brevo', 'smtp', etc.). * @param string $code Structured error code. * * @return string|null Doc URL, or null when not published for this mailer. */ public function doc_url_for( $mailer, $code ) { if ( empty( $mailer ) || empty( $code ) ) { return null; } $cache = get_transient( self::TRANSIENT_KEY ); $cache = is_array( $cache ) ? $cache : []; if ( $this->cache_is_stale( $cache, $mailer ) ) { $cache = $this->refresh( $cache, $mailer, $code ); } elseif ( ! isset( $cache[ $mailer ]['codes'] ) || ! array_key_exists( $code, $cache[ $mailer ]['codes'] ) ) { // Per-code miss: this code has never been asked of the API for this mailer. // Fetch fresh; if the API still doesn't return it, refresh() will plant an // explicit null marker so we don't keep refetching until the next full refresh. $cache = $this->refresh( $cache, $mailer, $code ); } if ( empty( $cache[ $mailer ]['codes'][ $code ] ) ) { return null; } return (string) $cache[ $mailer ]['codes'][ $code ]; } /** * Fetch the mailer's error map and merge it into the cache. When `$code` is * given and the freshly-fetched map doesn't contain it, stores an explicit * `null` under that code so subsequent lookups skip the network until the * next full refresh. * * @since 4.9.0 * * @param array $cache Current transient payload (passed by value, returned mutated). * @param string $mailer Mailer slug to refresh. * @param string $code Specific code that triggered the refresh, if any. * * @return array Updated cache payload. */ private function refresh( $cache, $mailer, $code = '' ) { $fresh = $this->fetch( $mailer ); // Transport failures return null; leave the existing cache untouched so the // caller will retry on the next render. if ( ! is_array( $fresh ) ) { return $cache; } // Negative-cache the requested code when the API came back without it. This // is what lets unknown codes stop refetching after one round-trip — they sit // as null until the whole mailer entry ages out of cache. if ( $code !== '' && ! array_key_exists( $code, $fresh['codes'] ) ) { $fresh['codes'][ $code ] = null; } $cache[ $mailer ] = $fresh; set_transient( self::TRANSIENT_KEY, $cache, self::TTL ); return $cache; } /** * Whether the cached entry for the given mailer is missing or older than the TTL. * * @since 4.9.0 * * @param array $cache Full transient payload. * @param string $mailer Mailer slug. * * @return bool */ private function cache_is_stale( $cache, $mailer ) { if ( ! isset( $cache[ $mailer ] ) || ! isset( $cache[ $mailer ]['populated_at'] ) ) { return true; } return ( time() - (int) $cache[ $mailer ]['populated_at'] ) >= self::TTL; } /** * Fetch a single mailer's error map from the registry endpoint. * * @since 4.9.0 * * @param string $mailer Mailer slug. * * @return array|null Array (`populated_at` + `codes` map) on a successful response, * including an empty codes map when the API has no entry for this * mailer (negative cache). Null on transport / server failure so * the caller can retry on the next render. */ private function fetch( $mailer ) { $backoff_key = self::FETCH_BACKOFF_TRANSIENT_PREFIX . $mailer; // A prior fetch failed within the backoff window; skip the HTTP request so a // degraded API or flaky network doesn't translate into a stream of outbound // requests on every banner render. if ( get_transient( $backoff_key ) ) { return null; } $base_url = defined( 'WPMS_PRODUCT_API_BASE_URL' ) ? WPMS_PRODUCT_API_BASE_URL : self::BASE_URL; $endpoint = trailingslashit( $base_url ) . self::ENDPOINT_PATH; $response = wp_remote_get( add_query_arg( 'mailer', $mailer, $endpoint ), [ 'timeout' => 5, 'user-agent' => Helpers::get_default_user_agent(), ] ); if ( is_wp_error( $response ) ) { set_transient( $backoff_key, time(), self::FETCH_BACKOFF_TTL ); return null; } $response_code = (int) wp_remote_retrieve_response_code( $response ); if ( $response_code !== 200 ) { set_transient( $backoff_key, time(), self::FETCH_BACKOFF_TTL ); return null; } $body = wp_remote_retrieve_body( $response ); if ( empty( $body ) ) { set_transient( $backoff_key, time(), self::FETCH_BACKOFF_TTL ); return null; } $decoded = json_decode( $body, true ); if ( ! is_array( $decoded ) ) { set_transient( $backoff_key, time(), self::FETCH_BACKOFF_TTL ); return null; } // Successful response — clear any stale backoff marker so future failures // start a fresh window instead of inheriting a partially-elapsed one. delete_transient( $backoff_key ); if ( ! isset( $decoded['errors'] ) ) { return [ 'populated_at' => time(), 'codes' => [], ]; } $errors = is_array( $decoded['errors'] ) ? $decoded['errors'] : (array) $decoded['errors']; return [ 'populated_at' => time(), 'codes' => array_map( 'strval', $errors ), ]; } } EmailSendingErrors/EmailSendingErrors.php 0000644 00000171402 15252526345 0014563 0 ustar 00 <?php namespace WPMailSMTP\Admin\EmailSendingErrors; use WPMailSMTP\EmailSendingDebug; use WPMailSMTP\Options; use WPMailSMTP\ConnectionInterface; /** * Persistent error banner shown across WP Mail SMTP admin pages whenever a real * send fails, with severity styling, troubleshooting links, an inline error log, * and a Need Help section. * * @since 4.9.0 */ class EmailSendingErrors { /** * Registry instance for doc-url lookups. * * @since 4.9.0 * * @var Registry */ private $registry; /** * Constructor. * * @since 4.9.0 * * @param Registry|null $registry Optional Registry override. */ public function __construct( $registry = null ) { $this->registry = $registry instanceof Registry ? $registry : new Registry(); } /** * Register hooks. * * @since 4.9.0 */ public function hooks() { add_action( 'wp_mail_smtp_admin_pages_before_content', [ $this, 'render_error_banner' ] ); add_action( 'admin_notices', [ $this, 'render_global_error_notice' ] ); add_action( 'wp_ajax_wp_mail_smtp_email_sending_errors_dismiss', [ $this, 'ajax_dismiss' ] ); } /** * Render the in-plugin banner on every WP Mail SMTP admin page. * * Renders the primary connection's record as a full banner (when set) and * each additional connection's record as a stacked one-liner. Suppressed * when no records exist or the current user lacks the manage-options cap. * * @since 4.9.0 */ public function render_error_banner() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded -- Linear gate chain over context (capability, multisite, test-tab, AC-edit). Splitting hurts readability. if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_options() ) ) { return; } // Read-only sticky-banner gate during admin render. Submission nonce is verified by TestTab::process_post() before any side effects. // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended $is_test_submission = $this->is_test_tab() && isset( $_POST['wp-mail-smtp'] ); // Respect the "hide delivery errors" option, except on a fresh test // email submission — the user just ran a test and expects to see its // result regardless of the global suppression. if ( ! $is_test_submission && ! wp_mail_smtp()->get_admin()->is_error_delivery_notice_enabled() ) { return; } // Multisite network admin shows an aggregated view in a dedicated handler — // this notice is per-site only. if ( is_network_admin() ) { return; } $all = EmailSendingDebug::get(); if ( empty( $all ) ) { return; } // Test Email tab always starts fresh on initial load — the banner only // surfaces after the user actually submits a test send. Read-only // presence check; submission nonce is verified by TestTab::process_post(). // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended if ( $this->is_test_tab() && ! isset( $_POST['wp-mail-smtp'] ) ) { return; } // Which connection's record gets the primary (full banner) slot on this // page: the AC edit-view's `connection_id`, the just-tested connection on // the Test Email tab, or 'primary' everywhere else. $connection_id = $this->get_primary_banner_connection_id(); // Render the full banner — except on the Connections list page, where // primary failures route to the one-liner notice and the full banner is // reserved for the per-connection edit view. if ( isset( $all[ $connection_id ] ) && ! ( $this->is_connections_tab() && $connection_id === 'primary' ) ) { $this->print_primary_banner( $connection_id === 'primary' ? 'primary' : 'additional', $all[ $connection_id ], $this->get_connection_name( $connection_id ), $connection_id ); unset( $all[ $connection_id ] ); } // AC edit page and Test Email tab are connection-focused surfaces: // they show only the active connection's banner. Skip stacking the // one-liner notices for every other failing connection. Read-only // presence check; the connection_id GET param is a navigation key. // phpcs:disable WordPress.Security.NonceVerification.Recommended if ( ( $this->is_connections_tab() && isset( $_GET['connection_id'] ) ) || $this->is_test_tab() ) { return; } // phpcs:enable WordPress.Security.NonceVerification.Recommended // Primary was either rendered above (and removed from $all) or is // suppressed on the Connections list page. Either way, the loop below // only handles additional connections. unset( $all['primary'] ); foreach ( $all as $connection_id => $record ) { $connection = wp_mail_smtp()->get_connections_manager()->get_connection( $connection_id, false ); if ( $connection !== false ) { $this->print_additional_connection_note( $record, $connection ); } } } /** * Render the cross-admin one-liner notice on non–WP Mail SMTP admin pages. * * Outputs plain WP core `<div class="notice notice-error">…</div>` markup * so it inherits standard admin-notice styling. Only renders when at least * one record has `status: failed` AND `context: regular` — test-context * failures stay scoped to the in-plugin banner. * * @since 4.9.0 */ public function render_global_error_notice() { // Multisite network admin shows an aggregated view in a dedicated handler; // this notice is per-site only. if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_options() ) || ! wp_mail_smtp()->get_admin()->is_error_delivery_notice_enabled() || wp_mail_smtp()->get_admin()->is_admin_page() || is_network_admin() ) { return; } $all = EmailSendingDebug::get(); $live = array_filter( $all, static function ( $record ) { return isset( $record['status'], $record['context'] ) && $record['status'] === 'failed' && $record['context'] === 'regular'; } ); if ( empty( $live ) ) { return; } $settings_url = wp_mail_smtp()->get_admin()->get_admin_page_url(); $connection_id = array_keys( $live )[0]; if ( count( $live ) === 1 && $connection_id !== 'primary' ) { $settings_url = add_query_arg( [ 'page' => 'wp-mail-smtp', 'tab' => 'connections', 'mode' => 'edit', 'connection_id' => $connection_id, ], admin_url( 'admin.php' ) ); } printf( '<div class="notice notice-error"><p>%1$s <a href="%2$s">%3$s</a></p></div>', esc_html__( 'WP Mail SMTP: One or more emails recently failed to send.', 'wp-mail-smtp' ), esc_url( $settings_url ), esc_html__( 'View details', 'wp-mail-smtp' ) ); } /** * AJAX endpoint: dismiss a single connection's failure record. * * @since 4.9.0 */ public function ajax_dismiss() { if ( ! check_ajax_referer( 'wp-mail-smtp-admin', 'nonce', false ) ) { wp_send_json_error( esc_html__( 'Security check failed. Please reload the page and try again.', 'wp-mail-smtp' ) ); } if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_options() ) ) { wp_send_json_error( esc_html__( 'You don\'t have permission to perform this action.', 'wp-mail-smtp' ) ); } $connection_id = isset( $_POST['connection_id'] ) ? sanitize_key( wp_unslash( $_POST['connection_id'] ) ) : ''; if ( empty( $connection_id ) ) { wp_send_json_error( esc_html__( 'Missing connection identifier.', 'wp-mail-smtp' ) ); } EmailSendingDebug::clear( $connection_id ); wp_send_json_success(); } /** * Resolve the connection_id when viewing an Additional Connection's edit page. * * Returns an empty string when not on the AC edit view. Read-only access to * request superglobals — no state changes are made here. * * @since 4.9.0 * * @return string */ private function get_primary_banner_connection_id() { $connection_id = 'primary'; // Read-only banner-target resolution during admin render. Submission // nonce is verified by TestTab::process_post() before any side effects, // and the AC edit view's connection_id is a navigation parameter (not // processed input). // phpcs:disable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended if ( $this->is_connections_tab() && isset( $_GET['connection_id'] ) ) { $connection_id = sanitize_key( wp_unslash( $_GET['connection_id'] ) ); } elseif ( $this->is_test_tab() && isset( $_POST['wp-mail-smtp']['test']['connection'] ) ) { $connection_id = sanitize_key( wp_unslash( $_POST['wp-mail-smtp']['test']['connection'] ) ); } // phpcs:enable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended return $connection_id; } /** * Look up a connection's display name (primary or additional). * * @since 4.9.0 * * @param string $connection_id Connection ID. * * @return string */ private function get_connection_name( $connection_id ) { if ( $connection_id === 'primary' ) { return esc_html__( 'Primary Connection', 'wp-mail-smtp' ); } $connection = wp_mail_smtp()->get_connections_manager()->get_connection( $connection_id, false ); if ( $connection ) { return $connection->get_name(); } return esc_html__( 'an additional connection', 'wp-mail-smtp' ); } /** * Render the full banner for a connection (primary scope, or General Settings * banner shell). * * @since 4.9.0 * * @param string $role Role: 'primary' or 'additional'. * @param array $record Failure record. * @param string $connection_name Display name. * @param string $connection_id Connection id this banner is rendering ('primary' or an additional id). */ private function print_primary_banner( $role, $record, $connection_name, $connection_id ) { $is_warning = ( isset( $record['status'] ) && $record['status'] === 'sent' ); $severity_class = $is_warning ? 'wpms-email-sending-errors-banner--warning-severity' : 'wpms-email-sending-errors-banner--failed-severity'; $severity_label = $is_warning ? esc_html__( 'Warning', 'wp-mail-smtp' ) : esc_html__( 'Error', 'wp-mail-smtp' ); $title = $this->get_title( $record, $role, $connection_name ); $doc_state = $this->resolve_doc_state( $record ); ob_start(); $this->print_banner_body( $record, $doc_state, $connection_id ); $body = ob_get_clean(); printf( '<div class="wpms-email-sending-errors-banner %1$s" data-connection-id="%2$s">' . '<button type="button" class="notice-dismiss wpms-email-sending-errors-banner__dismiss" aria-label="%3$s"><span class="screen-reader-text">%3$s</span></button>' . '<div class="wpms-email-sending-errors-banner__header">' . '<span class="wpms-email-sending-errors-banner__icon wpms:icon-[fa6-solid--circle-exclamation] wpms:w-[16px] wpms:h-[16px] wpms:shrink-0" role="img" aria-label="%4$s"></span>' . '<p class="wpms-email-sending-errors-banner__title">%5$s</p>' . '</div>' . '<div class="wpms-email-sending-errors-banner__body">%6$s</div>' . '</div>', esc_attr( $severity_class ), esc_attr( $connection_id ), esc_attr__( 'Dismiss this notice', 'wp-mail-smtp' ), esc_attr( $severity_label ), $title, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped $body // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ); } /** * Build the banner title. * * The generic "last email was unsuccessful" string is the unconditional * fallback; specific framings override for primary-rescued, additional-failed, * and additional-rescued. The test-context variants substitute "test email" * (bolded) for "email". * * @since 4.9.0 * * @param array $record Failure record. * @param string $role Role: 'primary' or 'additional'. * @param string $connection_name Display name for the failing connection. * * @return string Title HTML (with bolded interpolations). */ private function get_title( $record, $role, $connection_name ) { $is_test = ( isset( $record['context'] ) && $record['context'] === 'test' ); $rescued = ( isset( $record['status'] ) && $record['status'] === 'sent' ); $is_primary = ( $role === 'primary' ); $name_html = '<strong>' . esc_html( $connection_name ) . '</strong>'; if ( $is_primary && $rescued ) { return $is_test ? esc_html__( 'Heads up! Your primary connection failed to send the last test email. Your backup connection sent it successfully', 'wp-mail-smtp' ) : esc_html__( 'Heads up! Your primary connection failed to send the last email. Your backup connection sent it successfully', 'wp-mail-smtp' ); } if ( ! $is_primary && $rescued ) { return $is_test ? sprintf( /* translators: %s: the connection name, bolded. */ __( 'Heads up! %s failed to send the last test email. Your backup connection sent it successfully', 'wp-mail-smtp' ), $name_html ) : sprintf( /* translators: %s: the connection name, bolded. */ __( 'Heads up! %s failed to send the last email. Your backup connection sent it successfully', 'wp-mail-smtp' ), $name_html ); } if ( ! $is_primary && ! $rescued ) { return $is_test ? sprintf( /* translators: %s: connection name, bolded. */ __( 'Heads up! The last test email your site attempted to send via %s was unsuccessful', 'wp-mail-smtp' ), $name_html ) : sprintf( /* translators: %s: the additional connection name, bolded. */ __( 'Heads up! The last email your site attempted to send via %s was unsuccessful', 'wp-mail-smtp' ), $name_html ); } if ( $is_test ) { return esc_html__( 'Heads up! The last test email your site attempted to send was unsuccessful', 'wp-mail-smtp' ); } return esc_html__( 'Heads up! The last email your site attempted to send was unsuccessful', 'wp-mail-smtp' ); } /** * Classify the banner body branch from the registry lookup. * * Returns one of three states: * - `doc_present` — Registry has a doc URL for the (mailer, error_key) pair. * - `doc_not_present` — Registry has no doc URL (mailer/error_key recognized but not yet documented). * - `unmatched` — No structured error_key recorded. * * @since 4.9.0 * * @param array $record Failure record. * * @return array Shape: [ 'state' => string, 'doc_url' => string|null ] */ private function resolve_doc_state( $record ) { $mailer = isset( $record['mailer'] ) ? (string) $record['mailer'] : ''; $error_key = isset( $record['error_key'] ) ? (string) $record['error_key'] : ''; if ( $error_key === '' ) { return [ 'state' => 'unmatched', 'doc_url' => null, ]; } $url = $this->registry->doc_url_for( $mailer, $error_key ); if ( $url === null ) { return [ 'state' => 'doc_not_present', 'doc_url' => null, ]; } return [ 'state' => 'doc_present', 'doc_url' => $url, ]; } /** * Print the body branch for the full banner. * * Three branches: * - doc_present — known-issue copy + "View Troubleshoot Guide" CTA. * - doc_not_present — fallback copy + "Send Test Email" CTA (Lite) / * "Need hands-on help?" upsell (Pro). * - unmatched — inline diagnostic from $record['error_message']. * * @since 4.9.0 * * @param array $record Failure record. * @param array $doc_state Output of {@see resolve_doc_state()}. * @param string $connection_id Connection id this banner belongs to. When not * 'primary', the "Send Test Email" CTA carries * `connection_id=<id>` so the Test tab pre-selects * that connection. */ private function print_banner_body( $record, $doc_state, $connection_id ) { $test_tab_url_args = [ 'tab' => 'test', 'auto-start' => 1, ]; if ( $connection_id !== 'primary' ) { $test_tab_url_args['connection_id'] = $connection_id; } $test_tab_url = add_query_arg( $test_tab_url_args, wp_mail_smtp()->get_admin()->get_admin_page_url( 'wp-mail-smtp-tools' ) ); switch ( $doc_state['state'] ) { case 'doc_present': printf( '<p>%s</p>', esc_html__( "We've identified your error as a known issue and have a clear, step-by-step solution ready for you! Just refer our troubleshoot guide to get started.", 'wp-mail-smtp' ) ); $actions = [ sprintf( '<a href="%1$s" class="wp-mail-smtp-btn wp-mail-smtp-btn-md wp-mail-smtp-btn-orange" target="_blank" rel="noopener noreferrer">%2$s</a>', esc_url( wp_mail_smtp()->get_utm_url( $doc_state['doc_url'], [ 'medium' => 'email-sending-errors-banner', 'content' => 'Troubleshoot Guide' ] ) ), // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound esc_html__( 'Open Troubleshoot Guide', 'wp-mail-smtp' ) ), $this->build_error_log_toggle_button( $record ), sprintf( '<a href="%1$s" class="wp-mail-smtp-btn wp-mail-smtp-btn-link">%2$s</a>', esc_url( $test_tab_url ), esc_html__( 'Send Test Email', 'wp-mail-smtp' ) ), ]; $this->print_actions_row( $actions ); $this->print_error_log_panel( $record ); $this->print_need_help( $record, $doc_state ); break; case 'doc_not_present': $this->print_failure_cause( $record ); $actions = [ $this->build_error_log_toggle_button( $record ), sprintf( '<a href="%1$s" class="wp-mail-smtp-btn wp-mail-smtp-btn-link">%2$s</a>', esc_url( $test_tab_url ), esc_html__( 'Send Test Email', 'wp-mail-smtp' ) ), ]; $this->print_actions_row( $actions ); $this->print_error_log_panel( $record ); $this->print_need_help( $record, $doc_state ); break; case 'unmatched': default: $diagnostic = isset( $record['error_message'] ) ? (string) $record['error_message'] : ''; printf( '<p>%1$s</p>', esc_html__( 'No structured error code was recorded for this failure. The diagnostic from the mailer is below.', 'wp-mail-smtp' ) ); if ( $diagnostic !== '' ) { printf( '<pre class="wpms-email-sending-errors-banner__diagnostic">%s</pre>', esc_html( $diagnostic ) ); } break; } } /** * Render the actions row inside the banner body. * * Wraps the supplied markup chunks in a flex container so they sit on one * row per the design. Empty strings are skipped so callers can pass * conditional actions without extra branching. * * @since 4.9.0 * * @param string[] $actions Pre-rendered action markup (buttons, links). */ private function print_actions_row( $actions ) { $actions = array_filter( $actions ); if ( empty( $actions ) ) { return; } $allowed_tags = [ 'a' => [ 'href' => [], 'rel' => [], 'target' => [], 'class' => [], ], 'button' => [ 'type' => [], 'class' => [], 'data-show-label' => [], 'data-hide-label' => [], ], 'span' => [ 'class' => [], ], ]; echo '<div class="wpms-email-sending-errors-banner__actions">'; foreach ( $actions as $action ) { echo wp_kses( $action, $allowed_tags ); } echo '</div>'; } /** * Build the "View Error Log" toggle-button markup for inclusion in the * banner's actions row. Returns an empty string when the record has no * error message to display. * * @since 4.9.0 * * @param array $record Failure record. * * @return string */ private function build_error_log_toggle_button( $record ) { if ( empty( $record['error_message'] ) ) { return ''; } return sprintf( '<button type="button" class="wp-mail-smtp-btn wp-mail-smtp-btn-md wp-mail-smtp-btn-light-grey wpms-email-sending-errors-banner__error-log-toggle" data-show-label="%1$s" data-hide-label="%2$s">%1$s</button>', esc_attr__( 'View Error Log', 'wp-mail-smtp' ), esc_attr__( 'Hide Error Log', 'wp-mail-smtp' ) ); } /** * Render the inline collapsible error-log panel (initially hidden). * * Paired with the toggle button produced by * {@see build_error_log_toggle_button()}. * * @since 4.9.0 * * @param array $record Failure record. */ private function print_error_log_panel( $record ) { if ( empty( $record['error_message'] ) ) { return; } $allowed_tags = [ 'a' => [ 'href' => [], 'rel' => [], 'target' => [], ], 'p' => [], 'strong' => [], 'b' => [], 'i' => [], 'br' => [], 'code' => [], 'ul' => [], 'ol' => [], 'li' => [], 'pre' => [], ]; printf( '<div class="wpms-email-sending-errors-banner__error-log wpms-email-sending-errors-error-log" hidden>' . '<div class="wpms-email-sending-errors-error-log__inner">' . '<button type="button" class="wpms-email-sending-errors-error-log__copy-icon" aria-label="%1$s" title="%1$s">' . '<span class="wpms-email-sending-errors-error-log__copy-icon-default wpms:icon-[fa6-regular--copy] wpms:w-[16px] wpms:h-[16px]"></span>' . '<span class="wpms-email-sending-errors-error-log__copy-icon-done wpms:icon-[fa6-solid--check] wpms:text-success wpms:w-[16px] wpms:h-[16px]" hidden></span>' . '</button>' . '<span class="wpms-email-sending-errors-error-log__copy-tooltip" hidden>%2$s</span>' . '%3$s' . '</div>' . '</div>', esc_attr__( 'Copy error log', 'wp-mail-smtp' ), esc_html__( 'Copied!', 'wp-mail-smtp' ), wp_kses( $this->build_error_log( $record ), $allowed_tags ) ); } /** * Render the full error log from a stored failure record. * * Reads the snapshot data captured at write-time (see * {@see \WPMailSMTP\MailCatcherTrait::record_send_failure()}) instead of consulting * live request state, so the bundle reflects the moment the failure occurred even * if the admin views it later. * * @since 1.0.0 * * @param array $record EmailSendingDebug record for a single connection. * * @return string Pre-formatted HTML block. */ private function build_error_log( $record ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded -- Linear assembly of optional debug sections; splitting would scatter the bundle layout. $mailer_slug = isset( $record['mailer'] ) ? $record['mailer'] : ''; $is_smtp = ( $mailer_slug === 'smtp' ); /* * Versions Debug. */ $versions_text = '<strong>Versions:</strong><br>'; $versions_text .= '<strong>WordPress:</strong> ' . esc_html( isset( $record['wp_version'] ) ? $record['wp_version'] : '' ) . '<br>'; $versions_text .= '<strong>WordPress MS:</strong> ' . ( ! empty( $record['is_multisite'] ) ? 'Yes' : 'No' ) . '<br>'; $versions_text .= '<strong>PHP:</strong> ' . esc_html( isset( $record['php_version'] ) ? $record['php_version'] : '' ) . '<br>'; $versions_text .= '<strong>WP Mail SMTP:</strong> ' . esc_html( isset( $record['plugin_version'] ) ? $record['plugin_version'] : '' ) . '<br>'; /* * Mailer Debug. */ $mailer_text = '<strong>Params:</strong><br>'; $mailer_text .= '<strong>Mailer:</strong> ' . esc_html( $mailer_slug ) . '<br>'; if ( $record['context'] !== 'test' ) { $mailer_text .= '<strong>Source:</strong> ' . esc_html( isset( $record['initiator_name'] ) ? $record['initiator_name'] : '' ) . ' - ' . esc_html( isset( $record['initiator_file'] ) ? $record['initiator_file'] : '' ) . '<br>'; } $mailer_text .= '<strong>Constants:</strong> ' . ( ! empty( $record['constants_enabled'] ) ? 'Yes' : 'No' ) . '<br>'; if ( ! empty( $record['conflicts'] ) ) { $mailer_text .= '<strong>Conflicts:</strong> ' . esc_html( implode( ', ', (array) $record['conflicts'] ) ) . '<br>'; } if ( ! empty( $record['mailer_debug_info'] ) ) { $mailer_text .= $record['mailer_debug_info']; } if ( ! empty( $record['error_message'] ) ) { $mailer_text .= '<br><br><strong>Error:</strong><br>' . wp_strip_all_tags( $record['error_message'] ) . '<br>'; } /* * SMTP Debug. */ $smtp_text = ''; if ( $is_smtp ) { $smtp_text = '<strong>SMTP Debug:</strong><br>'; $smtp_text .= ! empty( $record['smtp_debug_info'] ) ? '<pre>' . $record['smtp_debug_info'] . '</pre>' : '[empty]'; } /** * Filters the sections rendered in the failure-record error log block. * * Each section is an already-formatted HTML string; the final block is * `<pre>` + sections joined by `<br>` (empty sections dropped). * * @since 1.0.0 * * @param string[] $sections { * Indexed array of section HTML strings. * * @type string $0 Versions section. * @type string $1 Mailer params + provider debug + (non-SMTP) error. * @type string $2 Debug section. Reserved for backward-compat with the * historical signature; no longer populated here. * @type string $3 SMTP debug section (empty for non-SMTP mailers). * } */ $errors = apply_filters( // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName,WPForms.Comments.ParamTagHooks.InvalidParamTagsQuantity -- Public hook name + 1-arg signature preserved for backward compatibility after relocation from TestTab. 'wp_mail_smtp_admin_test_get_debug_messages', [ $versions_text, $mailer_text, // Reserved slot to preserve the historical filter signature. '', $smtp_text, ] ); return '<pre>' . implode( '<br>', array_filter( $errors ) ) . '</pre>'; } /** * Render the `description` block from `get_local_failure_info()` as the cause * statement at the top of a Doc Not Present banner body. * * @since 4.9.0 * * @param array $record Failure record. */ private function print_failure_cause( $record ) { $info = $this->get_local_failure_info( $record ); $description = isset( $info['description'] ) && is_array( $info['description'] ) ? $info['description'] : []; if ( empty( $description ) ) { return; } $allowed_tags = [ 'a' => [ 'href' => [], 'rel' => [], 'target' => [], ], 'strong' => [], 'b' => [], 'em' => [], 'i' => [], 'code' => [], ]; echo '<div class="wpms-email-sending-errors-banner__failure-cause">'; printf( '<p><strong>%s</strong></p>', esc_html__( 'Typically this error is returned for one of the following reasons:', 'wp-mail-smtp' ) ); echo '<ul>'; foreach ( $description as $cause ) { echo '<li>' . wp_kses( $cause, $allowed_tags ) . '</li>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } echo '</ul>'; echo '</div>'; } /** * Returns debug information for detection, processing, and display. * * Pattern-matches the failure record's error text against a registry of known * issue signatures and returns the matched (title, description, steps) bundle, * or a generic default when nothing matches. * * @since 4.9.0 * * @param array $record EmailSendingDebug record for a single connection. * * @return array */ private function get_local_failure_info( $record ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded,Generic.Metrics.NestingLevel.MaxExceeded -- Pattern-matching switch over a static registry of failure signatures; flattening would scatter the registry. $details = [ // [any] - cURL error 60/77. [ 'mailer' => 'any', 'errors' => [ [ 'cURL error 60' ], [ 'cURL error 77' ], ], 'title' => esc_html__( 'SSL certificate issue.', 'wp-mail-smtp' ), 'description' => [ esc_html__( 'Your server cannot establish secure HTTPS connections.', 'wp-mail-smtp' ), esc_html__( 'Your server\'s CA certificate bundle is outdated.', 'wp-mail-smtp' ), ], 'steps' => [ esc_html__( 'Contact your web hosting provider and inform them your site has an issue with SSL certificates.', 'wp-mail-smtp' ), esc_html__( 'Share the Error Log below with them so they can resolve the issue.', 'wp-mail-smtp' ), ], ], // [any] - cURL error 6/7. [ 'mailer' => 'any', 'errors' => [ [ 'cURL error 6' ], [ 'cURL error 7' ], ], 'title' => esc_html__( 'Could not connect to host.', 'wp-mail-smtp' ), 'description' => [ esc_html__( 'Your web server is blocking outbound connections.', 'wp-mail-smtp' ), esc_html__( 'Your SMTP host is rejecting the connection.', 'wp-mail-smtp' ), ], 'steps' => [ esc_html__( 'Contact your web hosting provider and ask them to verify your server can make outbound connections, and whether a firewall or security policy may be blocking them.', 'wp-mail-smtp' ), esc_html__( 'If using the "Other SMTP" Mailer, triple-check your SMTP settings (host, email, password) and confirm with your SMTP host that they accept outside connections using those settings.', 'wp-mail-smtp' ), ], ], // [sendgrid] - cURL error 18 - potential incorrect API key. [ 'mailer' => 'sendgrid', 'errors' => [ [ 'cURL error 18' ], ], 'title' => esc_html__( 'Invalid SendGrid API key.', 'wp-mail-smtp' ), 'description' => [ esc_html__( 'Your SendGrid API key is invalid or has been revoked.', 'wp-mail-smtp' ), ], 'steps' => [ esc_html__( 'Go to the WP Mail SMTP plugin Settings page.', 'wp-mail-smtp' ), esc_html__( 'Make sure your API Key in the SendGrid mailer settings is correct and valid.', 'wp-mail-smtp' ), esc_html__( 'Save the plugin settings.', 'wp-mail-smtp' ), esc_html__( 'If updating the API Key doesn\'t resolve this issue, please contact our support.', 'wp-mail-smtp' ), ], ], // [any] - cURL error XX (other). [ 'mailer' => 'any', 'errors' => [ [ 'cURL error' ], ], 'title' => esc_html__( 'Could not connect to your host.', 'wp-mail-smtp' ), 'description' => [ esc_html__( 'Your server is unable to make outbound HTTP connections.', 'wp-mail-smtp' ), esc_html__( 'Your hosting provider is blocking the request.', 'wp-mail-smtp' ), ], 'steps' => [ esc_html__( 'Contact your web hosting provider and inform them you are having issues making outbound connections.', 'wp-mail-smtp' ), esc_html__( 'Share the Error Log below with them so they can resolve the issue.', 'wp-mail-smtp' ), ], ], // [smtp] - SMTP Error: Count not authenticate. [ 'mailer' => 'smtp', 'errors' => [ [ 'SMTP Error: Could not authenticate.' ], ], 'title' => esc_html__( 'Could not authenticate your SMTP account.', 'wp-mail-smtp' ), 'description' => [ esc_html__( 'Your SMTP username or password is incorrect.', 'wp-mail-smtp' ), esc_html__( 'Your SMTP host requires a different authentication method.', 'wp-mail-smtp' ), ], 'steps' => [ esc_html__( 'Triple-check your SMTP settings including host address, email, and password. If you have recently reset your password you will need to update the settings.', 'wp-mail-smtp' ), esc_html__( 'Contact your SMTP host to confirm you are using the correct username and password.', 'wp-mail-smtp' ), esc_html__( 'Verify with your SMTP host that your account has permissions to send emails using outside connections.', 'wp-mail-smtp' ), sprintf( wp_kses( /* translators: %s - URL to the wpmailsmtp.com doc page. */ __( 'Visit <a href="%s" target="_blank" rel="noopener noreferrer">our documentation</a> for additional tips on how to resolve this error.', 'wp-mail-smtp' ), [ 'a' => [ 'href' => [], 'target' => [], 'rel' => [], ], ] ), esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/how-to-set-up-the-other-smtp-mailer-in-wp-mail-smtp/#auth-type', [ 'medium' => 'email-test', 'content' => 'Other SMTP auth debug - our documentation' ] ) ) // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound ), ], ], // [smtp] - Sending bulk email, hitting rate limit. [ 'mailer' => 'smtp', 'errors' => [ [ 'We do not authorize the use of this system to transport unsolicited' ], ], 'title' => esc_html__( 'Error due to unsolicited and/or bulk e-mail.', 'wp-mail-smtp' ), 'description' => [ esc_html__( 'You are sending more emails than your SMTP host allows.', 'wp-mail-smtp' ), esc_html__( 'Your message was flagged as spam by the SMTP host.', 'wp-mail-smtp' ), ], 'steps' => [ esc_html__( 'Reduce the recipient count per email: keep single emails to under 10 recipients.', 'wp-mail-smtp' ), esc_html__( 'Install an email-logging plugin to inspect your TO, CC, and BCC recipients.', 'wp-mail-smtp' ), esc_html__( 'Contact your SMTP host to ask about sending and rate limits.', 'wp-mail-smtp' ), esc_html__( 'Verify with them that your SMTP account is in good standing and has not been flagged.', 'wp-mail-smtp' ), ], ], // [smtp] - Unauthenticated senders not allowed. [ 'mailer' => 'smtp', 'errors' => [ [ 'Unauthenticated senders not allowed' ], ], 'title' => esc_html__( 'Unauthenticated senders are not allowed.', 'wp-mail-smtp' ), 'description' => [ esc_html__( 'Your SMTP host requires authentication, but it is disabled in the plugin settings.', 'wp-mail-smtp' ), ], 'steps' => [ esc_html__( 'Go to the WP Mail SMTP plugin Settings page.', 'wp-mail-smtp' ), esc_html__( 'Enable Authentication.', 'wp-mail-smtp' ), esc_html__( 'Enter the correct SMTP Username (usually an email address) and Password in the appropriate fields.', 'wp-mail-smtp' ), ], ], // [smtp] - certificate verify failed. // Has to be defined before "SMTP connect() failed" error, since this is a more specific error, // which contains the "SMTP connect() failed" error message as well. [ 'mailer' => 'smtp', 'errors' => [ [ 'certificate verify failed' ], ], 'title' => esc_html__( 'Misconfigured server certificate.', 'wp-mail-smtp' ), 'description' => [ esc_html__( 'The SMTP host\'s SSL certificate is misconfigured or expired.', 'wp-mail-smtp' ), esc_html__( 'Your server\'s OpenSSL CA bundle is outdated.', 'wp-mail-smtp' ), ], 'steps' => [ esc_html__( 'Verify that the host\'s SSL certificate is valid.', 'wp-mail-smtp' ), sprintf( wp_kses( /* translators: %s - URL to the PHP openssl manual. */ __( 'Contact your hosting support and share the Error Log below along with this <a href="%s" target="_blank" rel="noopener noreferrer">link</a>.', 'wp-mail-smtp' ), [ 'a' => [ 'href' => [], 'target' => [], 'rel' => [], ], ] ), 'https://www.php.net/manual/en/migration56.openssl.php' ), ], ], // [smtp] - SMTP connect() failed. [ 'mailer' => 'smtp', 'errors' => [ [ 'SMTP connect() failed' ], ], 'title' => esc_html__( 'Could not connect to the SMTP host.', 'wp-mail-smtp' ), 'description' => [ esc_html__( 'Your SMTP settings are incorrect (wrong port, security setting, or host).', 'wp-mail-smtp' ), esc_html__( 'Your web server is blocking the outbound connection.', 'wp-mail-smtp' ), esc_html__( 'Your SMTP host is rejecting the connection.', 'wp-mail-smtp' ), ], 'steps' => [ esc_html__( 'Triple-check your SMTP settings: host, email, password, port, and security.', 'wp-mail-smtp' ), wp_kses( __( 'Contact your web hosting provider and ask them to verify your server can connect to your SMTP host on the configured port and encryption, and whether a firewall or security policy may be blocking the connection. Many shared hosts block certain ports. <strong>Note: this is the most common cause of this issue.</strong>', 'wp-mail-smtp' ), [ 'strong' => [], ] ), esc_html__( 'Contact your SMTP host to confirm you are using the correct username and password.', 'wp-mail-smtp' ), esc_html__( 'Verify with your SMTP host that your account has permissions to send emails using outside connections.', 'wp-mail-smtp' ), ], ], // [mailgun] - Please activate your Mailgun account. [ 'mailer' => 'mailgun', 'errors' => [ [ 'Please activate your Mailgun account' ], ], 'title' => esc_html__( 'Mailgun failed.', 'wp-mail-smtp' ), 'description' => [ esc_html__( 'Your Mailgun account has not been activated.', 'wp-mail-smtp' ), ], 'steps' => [ esc_html__( 'Check the inbox you used to create your Mailgun account and click the activation link in the email from Mailgun.', 'wp-mail-smtp' ), esc_html__( 'If you do not see the activation email, go to your Mailgun control panel and resend it.', 'wp-mail-smtp' ), ], ], // [mailgun] - Forbidden. [ 'mailer' => 'mailgun', 'errors' => [ [ 'Forbidden' ], ], 'title' => esc_html__( 'Mailgun failed.', 'wp-mail-smtp' ), 'description' => [ esc_html__( 'Your Mailgun API key is incorrect.', 'wp-mail-smtp' ), esc_html__( 'Your Mailgun domain name is incorrect.', 'wp-mail-smtp' ), esc_html__( 'Your Mailgun region is incorrect.', 'wp-mail-smtp' ), ], 'steps' => [ sprintf( wp_kses( /* translators: %1$s - Mailgun API Key area URL. */ __( 'Go to your Mailgun account and verify that your <a href="%1$s" target="_blank" rel="noopener noreferrer">Mailgun API Key</a> is correct.', 'wp-mail-smtp' ), [ 'a' => [ 'href' => [], 'rel' => [], 'target' => [], ], ] ), 'https://app.mailgun.com/settings/api_security' ), sprintf( wp_kses( /* translators: %1$s - Mailgun domains area URL. */ __( 'Verify your <a href="%1$s" target="_blank" rel="noopener noreferrer">Domain Name</a> is correct.', 'wp-mail-smtp' ), [ 'a' => [ 'href' => [], 'rel' => [], 'target' => [], ], ] ), 'https://app.mailgun.com/mg/sending/domains' ), esc_html__( 'Verify your domain Region is correct.', 'wp-mail-smtp' ), ], ], // [mailgun] - Free accounts are for test purposes only. [ 'mailer' => 'mailgun', 'errors' => [ [ 'Free accounts are for test purposes only' ], ], 'title' => esc_html__( 'Mailgun failed.', 'wp-mail-smtp' ), 'description' => [ esc_html__( 'Your Mailgun domain has not been verified.', 'wp-mail-smtp' ), esc_html__( 'Your Mailgun account is still on the free trial plan.', 'wp-mail-smtp' ), ], 'steps' => [ sprintf( wp_kses( /* translators: %s - Mailgun documentation URL. */ __( 'Go to our how-to guide for setting up <a href="%s" target="_blank" rel="noopener noreferrer">Mailgun with WP Mail SMTP</a>.', 'wp-mail-smtp' ), [ 'a' => [ 'href' => [], 'rel' => [], 'target' => [], ], ] ), esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/how-to-set-up-the-mailgun-mailer-in-wp-mail-smtp/', [ 'medium' => 'email-test', 'content' => 'Mailgun with WP Mail SMTP' ] ) ) // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound ), esc_html__( 'Complete the steps in section "2. Verify Your Domain".', 'wp-mail-smtp' ), ], ], // [gmail] - 401: Login Required. [ 'mailer' => 'gmail', 'errors' => [ [ '401', 'Login Required' ], ], 'title' => esc_html__( 'Google API Error.', 'wp-mail-smtp' ), 'description' => [ esc_html__( 'Gmail authorization has not been completed.', 'wp-mail-smtp' ), ], 'steps' => [ esc_html__( 'Go to the WP Mail SMTP plugin Settings page.', 'wp-mail-smtp' ), esc_html__( 'Click the "Allow plugin to send emails using your Google account" button.', 'wp-mail-smtp' ), esc_html__( 'On the Google authorization screen, click "Allow" to grant the plugin permission to send emails on your behalf.', 'wp-mail-smtp' ), ], ], // [gmail] - 400: Recipient address required. [ 'mailer' => 'gmail', 'errors' => [ [ '400', 'Recipient address required' ], ], 'title' => esc_html__( 'Google API Error.', 'wp-mail-smtp' ), 'description' => [ esc_html__( 'The recipient email address is empty or invalid.', 'wp-mail-smtp' ), ], 'steps' => [ esc_html__( 'Check the "Send To" email address and confirm it is valid and not empty.', 'wp-mail-smtp' ), sprintf( /* translators: 1 - correct email address example. 2 - incorrect email address example. */ esc_html__( 'It should look like %1$s. These are invalid: %2$s.', 'wp-mail-smtp' ), '<code>info@example.com</code>', '<code>info@localhost</code>, <code>info@192.168.1.1</code>' ), esc_html__( 'If you are generating the email yourself, make sure it includes a TO header.', 'wp-mail-smtp' ), ], ], // [gmail] - Token has been expired or revoked. [ 'mailer' => 'gmail', 'errors' => [ [ 'invalid_grant', 'Token has been expired or revoked' ], ], 'title' => esc_html__( 'Google API Error.', 'wp-mail-smtp' ), 'description' => [ esc_html__( 'Your Gmail OAuth refresh token has expired.', 'wp-mail-smtp' ), esc_html__( 'Access was revoked from your Google account.', 'wp-mail-smtp' ), ], 'steps' => [ esc_html__( 'Go to the WP Mail SMTP plugin Settings page and click the "Remove OAuth Connection" button.', 'wp-mail-smtp' ), esc_html__( 'Then click the "Allow plugin to send emails using your Google account" button and re-enable access.', 'wp-mail-smtp' ), ], ], // [gmail] - Code was already redeemed. [ 'mailer' => 'gmail', 'errors' => [ [ 'invalid_grant', 'Code was already redeemed' ], ], 'title' => esc_html__( 'Google API Error.', 'wp-mail-smtp' ), 'description' => [ esc_html__( 'The Google authorization code has already been used.', 'wp-mail-smtp' ), ], 'steps' => [ esc_html__( 'Do not manually clean up the plugin options to retry the "Allow..." step.', 'wp-mail-smtp' ), esc_html__( 'Reinstall the plugin with clean plugin data enabled on the Misc page. This will remove all plugin options so you can safely retry.', 'wp-mail-smtp' ), esc_html__( 'Make sure there is no aggressive caching on admin pages, or clear the cache between attempts.', 'wp-mail-smtp' ), ], ], // [gmail] - 400: Mail service not enabled. [ 'mailer' => 'gmail', 'errors' => [ [ '400', 'Mail service not enabled' ], ], 'title' => esc_html__( 'Google API Error.', 'wp-mail-smtp' ), 'description' => [ esc_html__( 'Your Google Workspace trial period has expired.', 'wp-mail-smtp' ), esc_html__( 'Gmail is not enabled in your Google Workspace account.', 'wp-mail-smtp' ), esc_html__( 'The Gmail API is not enabled in your Google Cloud project.', 'wp-mail-smtp' ), ], 'steps' => [ sprintf( wp_kses( /* translators: %s - Google Workspace Admin area URL. */ __( 'Verify in <a href="%s" target="_blank" rel="noopener noreferrer">Google Workspace Admin</a> that your trial period has not expired.', 'wp-mail-smtp' ), [ 'a' => [ 'href' => [], 'rel' => [], 'target' => [], ], ] ), 'https://admin.google.com' ), sprintf( wp_kses( /* translators: %s - Google Workspace Admin area URL. */ __( 'In the Apps list of <a href="%s" target="_blank" rel="noopener noreferrer">Google Workspace Admin</a>, confirm that Gmail is enabled.', 'wp-mail-smtp' ), [ 'a' => [ 'href' => [], 'rel' => [], 'target' => [], ], ] ), 'https://admin.google.com' ), sprintf( wp_kses( /* translators: %s - Google Cloud Console URL. */ __( 'Enable the Gmail API in your project from <a href="%s" target="_blank" rel="noopener noreferrer">Google Cloud Console</a>.', 'wp-mail-smtp' ), [ 'a' => [ 'href' => [], 'rel' => [], 'target' => [], ], ] ), 'https://console.cloud.google.com/' ), ], ], // [gmail] - 403: Project X is not found and cannot be used for API calls. [ 'mailer' => 'gmail', 'errors' => [ [ '403', 'is not found and cannot be used for API calls' ], ], 'title' => esc_html__( 'Google API Error.', 'wp-mail-smtp' ), 'description' => [ esc_html__( 'The Google Cloud project linked to your Client ID/Secret no longer exists.', 'wp-mail-smtp' ), esc_html__( 'The Gmail API is not enabled on the project.', 'wp-mail-smtp' ), ], 'steps' => [ esc_html__( 'Make sure the Client ID/Secret in your settings corresponds to a project that has the Gmail API enabled.', 'wp-mail-smtp' ), sprintf( wp_kses( /* translators: %s - Gmail documentation URL. */ esc_html__( 'Follow our <a href="%s" target="_blank" rel="noopener noreferrer">Gmail tutorial</a> to ensure your project and credentials are configured correctly.', 'wp-mail-smtp' ), [ 'a' => [ 'href' => [], 'rel' => [], 'target' => [], ], ] ), esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/how-to-set-up-the-gmail-mailer-in-wp-mail-smtp/', [ 'medium' => 'email-test', 'content' => 'Gmail tutorial' ] ) ) // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound ), ], ], // [gmail] - The OAuth client was disabled. [ 'mailer' => 'gmail', 'errors' => [ [ 'disabled_client', 'The OAuth client was disabled' ], ], 'title' => esc_html__( 'Google API Error.', 'wp-mail-smtp' ), 'description' => [ esc_html__( 'Your Google OAuth client has been disabled.', 'wp-mail-smtp' ), ], 'steps' => [ esc_html__( 'Make sure your Client ID/Secret corresponds to a project that has the Gmail API enabled.', 'wp-mail-smtp' ), esc_html__( 'In Google Cloud Console, re-enable the disabled OAuth client, or create a new OAuth client and update the plugin settings with the new Client ID/Secret.', 'wp-mail-smtp' ), ], ], // [SMTP.com] - The "channel - not found" issue. [ 'mailer' => 'smtpcom', 'errors' => [ [ 'channel - not found' ], ], 'title' => esc_html__( 'SMTP.com API Error.', 'wp-mail-smtp' ), 'description' => [ esc_html__( 'Your SMTP.com Sender Name is incorrect.', 'wp-mail-smtp' ), ], 'steps' => [ esc_html__( 'Please make sure you entered an accurate Sender Name in WP Mail SMTP plugin settings.', 'wp-mail-smtp' ), ], ], // [gmail] - GuzzleHttp requires cURL, the allow_url_fopen ini setting, or a custom HTTP handler. [ 'mailer' => 'gmail', 'errors' => [ [ 'GuzzleHttp requires cURL, the allow_url_fopen ini setting, or a custom HTTP handler' ], ], 'title' => esc_html__( 'GuzzleHttp requirements.', 'wp-mail-smtp' ), 'description' => [ esc_html__( 'The cURL PHP extension is disabled on your server.', 'wp-mail-smtp' ), esc_html__( 'The allow_url_fopen PHP setting is disabled on your server.', 'wp-mail-smtp' ), ], 'steps' => [ esc_html__( 'Edit your php.ini file on your hosting server.', 'wp-mail-smtp' ), esc_html__( '(Recommended) Enable the cURL PHP extension by adding extension=curl to php.ini.', 'wp-mail-smtp' ), esc_html__( '(Or) Enable allow_url_fopen by adding allow_url_fopen = On to php.ini.', 'wp-mail-smtp' ), esc_html__( 'If you cannot edit php.ini yourself, share the Error Log below with your hosting support and ask them to make this change.', 'wp-mail-smtp' ), ], ], // [sparkpost] - Forbidden. [ 'mailer' => 'sparkpost', 'errors' => [ [ 'Forbidden' ], ], 'title' => esc_html__( 'SparkPost API failed.', 'wp-mail-smtp' ), 'description' => [ esc_html__( 'Your SparkPost API key is incorrect.', 'wp-mail-smtp' ), esc_html__( 'Your SparkPost API key is missing the "Transmissions: Read/Write" permission.', 'wp-mail-smtp' ), ], 'steps' => [ sprintf( wp_kses( /* translators: %1$s - SparkPost API Keys area URL, %2$s - SparkPost EU API Keys area URL. */ __( 'Go to your <a href="%1$s" target="_blank" rel="noopener noreferrer">SparkPost account</a> or <a href="%2$s" target="_blank" rel="noopener noreferrer">SparkPost EU account</a> and verify that your API key is correct.', 'wp-mail-smtp' ), [ 'a' => [ 'href' => [], 'rel' => [], 'target' => [], ], ] ), 'https://app.sparkpost.com/account/api-keys', 'https://app.eu.sparkpost.com/account/api-keys' ), esc_html__( 'Verify that your API key has "Transmissions: Read/Write" permission.', 'wp-mail-smtp' ), ], ], // [sparkpost] - Unauthorized. [ 'mailer' => 'sparkpost', 'errors' => [ [ 'Unauthorized' ], ], 'title' => esc_html__( 'SparkPost API failed.', 'wp-mail-smtp' ), 'description' => [ esc_html__( 'Your SparkPost account region is incorrect.', 'wp-mail-smtp' ), ], 'steps' => [ esc_html__( 'Verify that your SparkPost account region is selected in WP Mail SMTP settings.', 'wp-mail-smtp' ), ], ], ]; /** * [any] - PHP 7.4.x and PCRE library issues. * * @see https://wordpress.org/support/topic/cant-send-emails-using-php-7-4/ */ if ( version_compare( phpversion(), '7.4', '>=' ) && defined( 'PCRE_VERSION' ) && version_compare( PCRE_VERSION, '10.0', '>' ) && version_compare( PCRE_VERSION, '10.32', '<=' ) ) { $details[] = [ 'mailer' => 'any', 'errors' => [ [ 'Invalid address: (setFrom)' ], ], 'title' => esc_html__( 'PCRE library issue.', 'wp-mail-smtp' ), 'description' => [ esc_html__( 'Your server is running PHP 7.4.x with an outdated libpcre2 library (version 10.32 or earlier) that fails email address validation.', 'wp-mail-smtp' ), ], 'steps' => [ esc_html__( 'Contact your web hosting provider and inform them you are having issues with the libpcre2 library on PHP 7.4. They should be able to resolve this.', 'wp-mail-smtp' ), esc_html__( 'As a temporary workaround, you can downgrade to PHP 7.3 on your server.', 'wp-mail-smtp' ), ], ]; } $mailer_slug = isset( $record['mailer'] ) ? $record['mailer'] : ''; $haystack = implode( "\n", array_filter( [ isset( $record['error_message'] ) ? $record['error_message'] : '', isset( $record['mailer_debug_info'] ) ? $record['mailer_debug_info'] : '', isset( $record['smtp_debug_info'] ) ? $record['smtp_debug_info'] : '', ] ) ); // Error detection logic. foreach ( $details as $data ) { // Check for appropriate mailer. if ( $data['mailer'] !== 'any' && $mailer_slug !== $data['mailer'] ) { continue; } $match = false; // Attempt to detect errors. foreach ( $data['errors'] as $error_group ) { foreach ( $error_group as $error_message ) { $match = strpos( $haystack, $error_message ) !== false; if ( ! $match ) { break; } } if ( $match ) { break; } } if ( $match ) { return $data; } } // Return defaults. return [ 'title' => esc_html__( 'An issue was detected.', 'wp-mail-smtp' ), 'description' => [ esc_html__( 'Your plugin settings are incorrect (wrong SMTP settings, invalid Mailer configuration, etc.).', 'wp-mail-smtp' ), esc_html__( 'Your web server is blocking the connection.', 'wp-mail-smtp' ), esc_html__( 'Your host is rejecting the connection.', 'wp-mail-smtp' ), ], 'steps' => [ esc_html__( 'Triple-check your plugin settings, reconfigure if needed to make sure everything is correct.', 'wp-mail-smtp' ), wp_kses( __( 'Contact your web hosting provider and ask them to verify your server can make outside connections, and whether a firewall or security policy may be blocking them. Many shared hosts block certain ports. <strong>Note: this is the most common cause of this issue.</strong>', 'wp-mail-smtp' ), [ 'strong' => [], ] ), ], ]; } /** * Render the "Need Help?" sub-section inside the banner body. * * Renders the matched issue's `steps` (or the default bundle's steps when * nothing matched), followed by the SendLayer Quick Connect upsell (Lite * only), the White Glove Setup bullet, and an edition-specific support * line. Steps are suppressed when a troubleshoot doc URL is available, * since the doc covers the same ground. * * @since 4.9.0 * * @param array $record Failure record. * @param array $doc_state Output of {@see resolve_doc_state()}. */ private function print_need_help( $record, $doc_state ) { $bullets = []; $allowed_tags = [ 'a' => [ 'href' => [], 'rel' => [], 'target' => [], 'class' => [], ], 'strong' => [], 'b' => [], 'em' => [], 'i' => [], 'br' => [], 'code' => [], 'ul' => [], 'li' => [], 'span' => [ 'class' => [], ], ]; if ( $doc_state['state'] !== 'doc_present' ) { $info = $this->get_local_failure_info( $record ); $steps = isset( $info['steps'] ) && is_array( $info['steps'] ) ? $info['steps'] : []; foreach ( $steps as $step ) { $bullets[] = wp_kses( $step, $allowed_tags ); } } $primary_mailer = (string) Options::init()->get( 'mail', 'mailer' ); $record_mailer = isset( $record['mailer'] ) ? (string) $record['mailer'] : ''; if ( $primary_mailer !== 'sendlayer' && $record_mailer !== 'sendlayer' ) { $bullets[] = wp_kses( __( 'Resolve your email sending issues with <a href="#" class="js-wp-mail-smtp-sendlayer-quick-connect-link wpms:focus:outline-none! wpms:focus:shadow-none!">SendLayer</a>, and send your <strong>first 200 emails for free</strong> with just a few clicks!', 'wp-mail-smtp' ), $allowed_tags ); } $bullets[] = sprintf( wp_kses( /* translators: %s - White Glove Setup URL. */ __( 'Need hands-on help? Our <a href="%s" target="_blank" rel="noopener noreferrer">White Glove Setup</a> handles everything for you, just share your site details and we’ll do the rest.', 'wp-mail-smtp' ), $allowed_tags ), // TODO: Verify that this White Glove Setup URL is correct and up to date before release. esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/white-glove-setup/', [ 'medium' => 'email-sending-errors-banner', 'content' => 'White Glove Setup' ] ) ) // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound ); if ( $doc_state['state'] !== 'doc_present' ) { if ( wp_mail_smtp()->is_pro() ) { $bullets[] = sprintf( wp_kses( /* translators: %s - WPMailSMTP.com support URL. */ __( 'Still stuck? Please log in to your <a href="%s" target="_blank" rel="noopener noreferrer">WP Mail SMTP account</a> and submit a support ticket with the error log.', 'wp-mail-smtp' ), $allowed_tags ), esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/account/support/', [ 'medium' => 'email-sending-errors-banner', 'content' => 'submit a support ticket' ] ) ) // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound ); } else { $bullets[] = sprintf( wp_kses( /* translators: %1$s - wordpress.org support forum URL, %2$s - upgrade-to-Pro URL. */ __( 'Still stuck? Get priority support by <a href="%1$s" target="_blank" rel="noopener noreferrer">Upgrading to Pro</a> or <a href="%2$s" target="_blank" rel="noopener noreferrer">create a support thread</a> along with the error log on WordPress.org forums. ', 'wp-mail-smtp' ), $allowed_tags ), esc_url( wp_mail_smtp()->get_upgrade_link( [ 'medium' => 'email-sending-errors-banner', 'content' => 'Upgrading to Pro' ] ) ), // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound 'https://wordpress.org/support/plugin/wp-mail-smtp/', ); } } echo '<div class="wpms-email-sending-errors-banner__help">'; printf( '<p class="wpms-email-sending-errors-banner__help-title"><span aria-hidden="true" class="wpms:icon-[fa6-solid--circle-question] wpms:text-utility-yellow-50 wpms:w-[14px] wpms:h-[14px] wpms:shrink-0"></span><span>%s</span></p>', esc_html__( 'Need Help?', 'wp-mail-smtp' ) ); echo '<ul>'; foreach ( $bullets as $bullet ) { echo '<li>' . $bullet . '</li>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } echo '</ul>'; echo '</div>'; } /** * Render the verbose one-liner notice for an additional connection. On the * General Settings page these stack vertically under the primary banner. * * @since 4.9.0 * * @param array $record Failure record. * @param ConnectionInterface $connection Additional connection object. */ private function print_additional_connection_note( $record, $connection ) { $connection_id = $connection->get_id(); $connection_name = $connection->get_name(); if ( $connection_name === '' ) { $connection_name = esc_html__( 'an additional connection', 'wp-mail-smtp' ); } $severity_class = ( isset( $record['status'] ) && $record['status'] === 'sent' ) ? 'notice-warning' : 'notice-error'; $edit_url = add_query_arg( [ 'page' => 'wp-mail-smtp', 'tab' => 'connections', 'mode' => 'edit', 'connection_id' => $connection_id, ], admin_url( 'admin.php' ) ); $is_backup = $this->is_backup_connection( $connection_id ); $name_html = '<strong>' . esc_html( $connection_name ) . '</strong>'; $rescued = ( isset( $record['status'] ) && $record['status'] === 'sent' ); $is_test = ( isset( $record['context'] ) && $record['context'] === 'test' ); $test_word = '<strong>' . esc_html__( 'test', 'wp-mail-smtp' ) . '</strong>'; $backup_prefix = $is_backup ? sprintf( /* translators: %s: connection name, bolded. */ __( 'your backup connection %s', 'wp-mail-smtp' ), $name_html ) : $name_html; if ( $rescued ) { $copy = sprintf( /* translators: %s: connection display name. */ __( '%s failed to send the last email. Your backup connection sent it successfully.', 'wp-mail-smtp' ), $backup_prefix ); } elseif ( $is_test ) { $copy = sprintf( /* translators: %1$s: literal word "test", bolded. %2$s: connection display name. */ __( 'The last %1$s email your site attempted to send via %2$s was unsuccessful.', 'wp-mail-smtp' ), $test_word, $name_html ); } else { $copy = sprintf( /* translators: %s: connection display name. */ __( 'The last email your site attempted to send via %s was unsuccessful.', 'wp-mail-smtp' ), $backup_prefix ); } $manage_html = wp_kses( sprintf( /* translators: %s - link to the Additional Connection Settings page. */ __( 'Manage it on the %s page.', 'wp-mail-smtp' ), sprintf( '<a href="%s">%s</a>', esc_url( $edit_url ), esc_html__( 'Additional Connection Settings', 'wp-mail-smtp' ) ) ), [ 'a' => [ 'href' => [] ] ] ); printf( '<div class="notice %1$s is-dismissible wpms-email-sending-errors-one-liner" data-connection-id="%2$s">' . '<p>%3$s %4$s</p>' . '</div>', esc_attr( $severity_class ), esc_attr( $connection_id ), wp_kses( $copy, [ 'strong' => [] ] ), $manage_html // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ); } /** * Whether a given additional-connection id is currently configured as the * backup connection. Returns false when Pro is inactive or no backup is set. * * @since 4.9.0 * * @param string $connection_id Connection ID. * * @return bool */ private function is_backup_connection( $connection_id ) { $backup_id = Options::init()->get( 'backup_connection', 'connection_id' ); return ! empty( $backup_id ) && $backup_id === $connection_id; } /** * Whether we're on Tools > Email Test tab. * * Handles the implicit-default case: {@see \WPMailSMTP\Admin\Pages\Tools::$default_tab} * is `'test'`, so a missing `?tab=…` param on the tools page still means we're * on the test tab. `Area::is_admin_page( 'test' )` can't detect this because * `'test'` isn't a top-level registered page, and `Area::get_current_tab()` * only resolves the tab for the general page. * * @since 4.9.0 * * @return bool */ private function is_test_tab() { if ( ! wp_mail_smtp()->get_admin()->is_admin_page( 'tools' ) ) { return false; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended $tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : ''; return $tab === '' || $tab === 'test'; } /** * Whether we're currently viewing the Additional Connections tab on Settings. * * Settings → Connections is `page=wp-mail-smtp&tab=connections`; the AC tab is * NOT a registered WP admin page slug, so `is_admin_page('connections')` falls * back to matching the Settings page (any tab). This helper does the * tab-aware check. * * @since 4.9.0 * * @return bool */ private function is_connections_tab() { if ( ! wp_mail_smtp()->get_admin()->is_admin_page( 'general' ) ) { return false; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended $tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : ''; return $tab === 'connections'; } } Notifications.php 0000644 00000032450 15252526345 0010103 0 ustar 00 <?php namespace WPMailSMTP\Admin; use WPMailSMTP\Helpers\Helpers; use WPMailSMTP\Options; use WPMailSMTP\WP; /** * Notifications. * * @since 2.3.0 */ class Notifications { /** * Source of notifications content. * * @since 2.3.0 * * @var string */ const SOURCE_URL = 'https://wpmailsmtpapi.com/feeds/v1/notifications'; /** * The WP option key for storing the notification options. * * @since 2.3.0 * * @var string */ const OPTION_KEY = 'wp_mail_smtp_notifications'; /** * Option value. * * @since 2.3.0 * * @var bool|array */ public $option = false; /** * Initialize class. * * @since 2.3.0 */ public function init() { $this->hooks(); } /** * Register hooks. * * @since 2.3.0 */ public function hooks() { add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] ); add_action( 'wp_mail_smtp_admin_pages_before_content', [ $this, 'output' ] ); add_action( 'wp_ajax_wp_mail_smtp_notification_dismiss', [ $this, 'dismiss' ] ); } /** * Check if notifications are enabled. * * @since 4.3.0 * * @return bool */ public function is_enabled() { return ! Options::init()->get( 'general', 'am_notifications_hidden' ); } /** * Check if user has access and is enabled. * * @since 2.3.0 * * @return bool */ public function has_access() { $access = false; if ( current_user_can( wp_mail_smtp()->get_capability_manage_options() ) && $this->is_enabled() ) { $access = true; } return apply_filters( 'wp_mail_smtp_admin_notifications_has_access', $access ); } /** * Get option value. * * @since 2.3.0 * * @param bool $cache Reference property cache if available. * * @return array */ public function get_option( $cache = true ) { if ( $this->option && $cache ) { return $this->option; } $option = get_option( self::OPTION_KEY, [] ); $this->option = [ 'update' => ! empty( $option['update'] ) ? $option['update'] : 0, 'events' => ! empty( $option['events'] ) ? $option['events'] : [], 'feed' => ! empty( $option['feed'] ) ? $option['feed'] : [], 'dismissed' => ! empty( $option['dismissed'] ) ? $option['dismissed'] : [], ]; return $this->option; } /** * Fetch notifications from feed. * * @since 2.3.0 * * @return array */ protected function fetch_feed() { $feed_url = self::SOURCE_URL . '/' . wp_mail_smtp()->get_license_type(); $response = wp_remote_get( $feed_url, [ 'user-agent' => Helpers::get_default_user_agent(), ] ); if ( is_wp_error( $response ) ) { return []; } $body = wp_remote_retrieve_body( $response ); if ( empty( $body ) ) { return []; } return $this->verify( json_decode( $body, true ) ); } /** * Verify notification data before it is saved. * * @since 2.3.0 * * @param array $notifications Array of notification items to verify. * * @return array */ protected function verify( $notifications ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh $data = []; if ( ! is_array( $notifications ) || empty( $notifications ) ) { return $data; } $option = $this->get_option(); foreach ( $notifications as $notification ) { // The message and license should never be empty, if they are, ignore. if ( empty( $notification['content'] ) || empty( $notification['type'] ) ) { continue; } // Ignore if license type does not match. if ( ! in_array( wp_mail_smtp()->get_license_type(), $notification['type'], true ) ) { continue; } // Ignore if expired. if ( ! empty( $notification['end'] ) && time() > strtotime( $notification['end'] ) ) { continue; } // Ignore if notification has already been dismissed. if ( ! empty( $option['dismissed'] ) && in_array( $notification['id'], $option['dismissed'] ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict continue; } // Ignore if notification existed before installing WPForms. // Prevents bombarding the user with notifications after activation. $activated = get_option( 'wp_mail_smtp_activated_time' ); if ( ! empty( $activated ) && ! empty( $notification['start'] ) && $activated > strtotime( $notification['start'] ) ) { continue; } $data[] = $notification; } return $data; } /** * Verify saved notification data for active notifications. * * @since 2.3.0 * * @param array $notifications Array of notification items to verify. * * @return array */ protected function verify_active( $notifications ) { if ( ! is_array( $notifications ) || empty( $notifications ) ) { return []; } // Remove notifications that are not active. foreach ( $notifications as $key => $notification ) { if ( ( ! empty( $notification['start'] ) && time() < strtotime( $notification['start'] ) ) || ( ! empty( $notification['end'] ) && time() > strtotime( $notification['end'] ) ) ) { unset( $notifications[ $key ] ); } } return $notifications; } /** * Get notification data. * * @since 2.3.0 * @since 3.9.0 Make the AS a recurring task. * * @return array */ public function get() { if ( ! $this->has_access() ) { return []; } $option = $this->get_option(); $events = ! empty( $option['events'] ) ? $this->verify_active( $option['events'] ) : []; $feed = ! empty( $option['feed'] ) ? $this->verify_active( $option['feed'] ) : []; return array_merge( $events, $feed ); } /** * Get the update notifications interval. * * @since 3.9.0 * * @return int */ public function get_notification_update_task_interval() { /** * Filters the interval for the notifications update task. * * @since 3.9.0 * * @param int $interval The interval in seconds. Default to a day (in seconds). */ return (int) apply_filters( 'wp_mail_smtp_admin_notifications_get_notification_update_task_interval', DAY_IN_SECONDS ); } /** * Get notification count. * * @since 2.3.0 * * @return int */ public function get_count() { return count( $this->get() ); } /** * Add a manual notification event. * * @since 2.3.0 * * @param array $notification Notification data. */ public function add( $notification ) { if ( empty( $notification['id'] ) ) { return; } $option = $this->get_option(); if ( in_array( $notification['id'], $option['dismissed'] ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict return; } foreach ( $option['events'] as $item ) { if ( $item['id'] === $notification['id'] ) { return; } } $notification = $this->verify( [ $notification ] ); update_option( self::OPTION_KEY, [ 'update' => $option['update'], 'feed' => $option['feed'], 'events' => array_merge( $notification, $option['events'] ), 'dismissed' => $option['dismissed'], ] ); } /** * Update notification data from feed. * * @since 2.3.0 */ public function update() { $option = $this->get_option(); // Bail if feed was updated less than an interval ago. if ( time() - (int) $option['update'] < $this->get_notification_update_task_interval() ) { return; } $feed = $this->fetch_feed(); update_option( self::OPTION_KEY, [ 'update' => time(), 'feed' => $feed, 'events' => $option['events'], 'dismissed' => $option['dismissed'], ] ); } /** * Admin area assets. * * @since 2.3.0 * * @param string $hook Hook suffix for the current admin page. */ public function enqueue_assets( $hook ) { if ( strpos( $hook, Area::SLUG ) === false ) { return; } if ( ! $this->has_access() ) { return; } $notifications = $this->get(); if ( empty( $notifications ) ) { return; } wp_enqueue_style( 'wp-mail-smtp-admin-notifications', wp_mail_smtp()->assets_url . '/css/admin-notifications.min.css', [], WPMS_PLUGIN_VER ); wp_enqueue_script( 'wp-mail-smtp-admin-notifications', wp_mail_smtp()->assets_url . '/js/smtp-notifications' . WP::asset_min() . '.js', [ 'jquery' ], WPMS_PLUGIN_VER, true ); } /** * Output notifications. * * @since 2.3.0 */ public function output() { // phpcs:ignore Generic.Metrics.NestingLevel.MaxExceeded $notifications = $this->get(); if ( empty( $notifications ) ) { return; } $notifications_html = ''; $current_class = ' current'; $content_allowed_tags = [ 'em' => [], 'i' => [], 'strong' => [], 'span' => [ 'style' => [], ], 'a' => [ 'href' => [], 'target' => [], 'rel' => [], ], 'br' => [], 'p' => [ 'id' => [], 'class' => [], ], ]; foreach ( $notifications as $notification ) { // Buttons HTML. $buttons_html = ''; if ( ! empty( $notification['btns'] ) && is_array( $notification['btns'] ) ) { foreach ( $notification['btns'] as $btn_type => $btn ) { if ( empty( $btn['text'] ) ) { continue; } $buttons_html .= sprintf( '<a href="%1$s" class="button button-%2$s"%3$s>%4$s</a>', ! empty( $btn['url'] ) ? esc_url( $btn['url'] ) : '', $btn_type === 'main' ? 'primary' : 'secondary', ! empty( $btn['target'] ) && $btn['target'] === '_blank' ? ' target="_blank" rel="noopener noreferrer"' : '', sanitize_text_field( $btn['text'] ) ); } $buttons_html = ! empty( $buttons_html ) ? '<div class="wp-mail-smtp-notifications-buttons">' . $buttons_html . '</div>' : ''; } // Notification HTML. $notifications_html .= sprintf( '<div class="wp-mail-smtp-notifications-message%5$s" data-message-id="%4$s"> <h3 class="wp-mail-smtp-notifications-title">%1$s</h3> <div class="wp-mail-smtp-notifications-content">%2$s</div> %3$s </div>', ! empty( $notification['title'] ) ? sanitize_text_field( $notification['title'] ) : '', ! empty( $notification['content'] ) ? wp_kses( wpautop( $notification['content'] ), $content_allowed_tags ) : '', $buttons_html, ! empty( $notification['id'] ) ? esc_attr( sanitize_text_field( $notification['id'] ) ) : 0, $current_class ); // Only first notification is current. $current_class = ''; } ?> <div id="wp-mail-smtp-notifications"> <div class="wp-mail-smtp-notifications-header"> <div class="wp-mail-smtp-notifications-bell"> <svg width="16" height="20" viewBox="0 0 16 20" fill="none" xmlns="http://www.w3.org/2000/svg"> <path fill-rule="evenodd" clip-rule="evenodd" d="M12.8173 8.92686C12.4476 9.01261 12.0624 9.05794 11.6666 9.05794C8.86542 9.05794 6.59455 6.78729 6.59419 3.98616C4.22974 4.54931 2.51043 6.64293 2.51043 9.23824C2.51043 12.4695 1.48985 13.6147 0.849845 14.3328C0.796894 14.3922 0.746548 14.4487 0.699601 14.5032C0.505584 14.707 0.408575 14.9787 0.440912 15.2165C0.440912 15.7939 0.828946 16.3034 1.47567 16.3034H13.8604C14.5071 16.3034 14.8952 15.7939 14.9275 15.2165C14.9275 14.9787 14.8305 14.707 14.6365 14.5032C14.5895 14.4487 14.5392 14.3922 14.4862 14.3328C13.8462 13.6147 12.8257 12.4695 12.8257 9.23824C12.8257 9.13361 12.8229 9.02979 12.8173 8.92686ZM9.72139 17.3904C9.72139 18.6132 8.81598 19.5643 7.68421 19.5643C6.52011 19.5643 5.6147 18.6132 5.6147 17.3904H9.72139Z" fill="#777777"/> <path d="M11.6666 7.60868C13.6677 7.60868 15.2898 5.98653 15.2898 3.9855C15.2898 1.98447 13.6677 0.36232 11.6666 0.36232C9.66561 0.36232 8.04346 1.98447 8.04346 3.9855C8.04346 5.98653 9.66561 7.60868 11.6666 7.60868Z" fill="#d63638"/> </svg> </div> <div class="wp-mail-smtp-notifications-title"><?php esc_html_e( 'Notifications', 'wp-mail-smtp' ); ?></div> </div> <div class="wp-mail-smtp-notifications-body"> <a class="dismiss" title="<?php echo esc_attr__( 'Dismiss this message', 'wp-mail-smtp' ); ?>"><i class="dashicons dashicons-dismiss" aria-hidden="true"></i></a> <?php if ( count( $notifications ) > 1 ) : ?> <div class="navigation"> <a class="prev"> <span class="screen-reader-text"><?php esc_attr_e( 'Previous message', 'wp-mail-smtp' ); ?></span> <span aria-hidden="true">‹</span> </a> <a class="next"> <span class="screen-reader-text"><?php esc_attr_e( 'Next message', 'wp-mail-smtp' ); ?>"></span> <span aria-hidden="true">›</span> </a> </div> <?php endif; ?> <div class="wp-mail-smtp-notifications-messages"> <?php echo $notifications_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> </div> </div> </div> <?php } /** * Dismiss notification via AJAX. * * @since 2.3.0 */ public function dismiss() { // Run a security check. check_ajax_referer( 'wp-mail-smtp-admin', 'nonce' ); // Check for access and required param. if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_options() ) || empty( $_POST['id'] ) ) { wp_send_json_error(); } $id = sanitize_text_field( wp_unslash( $_POST['id'] ) ); $option = $this->get_option(); $type = is_numeric( $id ) ? 'feed' : 'events'; $option['dismissed'][] = $id; $option['dismissed'] = array_unique( $option['dismissed'] ); // Remove notification. if ( is_array( $option[ $type ] ) && ! empty( $option[ $type ] ) ) { foreach ( $option[ $type ] as $key => $notification ) { if ( $notification['id'] == $id ) { // phpcs:ignore WordPress.PHP.StrictComparisons unset( $option[ $type ][ $key ] ); break; } } } update_option( self::OPTION_KEY, $option ); wp_send_json_success(); } } Area.php 0000644 00000147110 15252526345 0006142 0 ustar 00 <?php namespace WPMailSMTP\Admin; use WPMailSMTP\Admin\Recommendations\RecommendedPlugins; use WPMailSMTP\Options; use WPMailSMTP\WP; /** * Class Area registers and process all wp-admin display functionality. * * @since 1.0.0 */ class Area { /** * Slug of the admin area page. * * @since 1.0.0 * * @var string */ const SLUG = 'wp-mail-smtp'; /** * Admin page unique hook. * * @since 1.0.0 * * @var string */ public $hook; /** * List of admin area pages. * * @since 1.0.0 * * @var PageAbstract[] */ private $pages; /** * Recommended-plugins feature controller. * * @since 4.9.0 * * @var RecommendedPlugins */ private $recommended_plugins; /** * List of official registered pages. * * @since 1.5.0 * * @var array */ public static $pages_registered = [ 'general', 'logs', 'about', 'tools', 'reports', 'alerts' ]; /** * Area constructor. * * @since 1.0.0 */ public function __construct() {} /** * Assign all hooks to proper places. * * @since 1.0.0 * @since 4.0.0 Changed visibility to public. */ public function hooks() { // Add the Settings link to a plugin on Plugins page. add_filter( 'plugin_action_links_' . plugin_basename( WPMS_PLUGIN_FILE ), [ $this, 'add_plugin_action_link' ], 10, 1 ); // Add the options page. add_action( 'admin_menu', [ $this, 'add_admin_options_page' ] ); // Add inline styles for "Upgrade to Pro" left sidebar menu item. add_action( 'admin_head', [ $this, 'style_upgrade_pro_link' ] ); // Add WPMS network-wide setting page for product education. add_action( 'network_admin_menu', [ $this, 'add_wpms_network_wide_setting_product_education_page' ] ); // Register on load Email Log admin menu hook. add_action( 'load-wp-mail-smtp_page_wp-mail-smtp-logs', [ $this, 'maybe_redirect_email_log_menu_to_email_log_settings_tab' ] ); // Admin footer text. add_filter( 'admin_footer_text', [ $this, 'get_admin_footer' ], 1, 2 ); // Enqueue admin area scripts and styles. add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] ); // Process the admin page forms actions. add_action( 'admin_init', [ $this, 'process_actions' ] ); // Display custom notices based on the error/success codes. add_action( 'admin_init', [ $this, 'display_custom_auth_notices' ] ); // Display notice instructing the user to complete plugin setup. add_action( 'admin_init', [ $this, 'display_setup_notice' ] ); // Display notice explaining removal of "Email Test" tab. add_action( 'admin_init', [ $this, 'display_email_test_tab_removal_notice' ] ); // Outputs the plugin admin header. add_action( 'in_admin_header', [ $this, 'display_admin_header' ], 100 ); // Outputs the plugin promotional admin footer. add_action( 'in_admin_footer', [ $this, 'display_admin_footer' ] ); // Outputs the plugin version in the admin footer. add_filter( 'update_footer', [ $this, 'display_update_footer' ], PHP_INT_MAX ); // Hide all unrelated to the plugin notices on the plugin admin pages. add_action( 'admin_print_scripts', [ $this, 'hide_unrelated_notices' ] ); // Process all AJAX requests. add_action( 'wp_ajax_wp_mail_smtp_ajax', [ $this, 'process_ajax' ] ); if ( WP::in_wp_admin() || WP::is_doing_self_ajax() ) { if ( did_action( 'init' ) ) { $this->get_parent_pages(); } else { add_action( 'init', [ $this, 'get_parent_pages' ] ); } } ( new Review() )->hooks(); ( new Education() )->hooks(); ( new SetupWizard() )->hooks(); ( new FlyoutMenu() )->hooks(); $this->recommended_plugins = new RecommendedPlugins(); $this->recommended_plugins->hooks(); ( new WooCommerceActiveLayerEducation() )->hooks(); } /** * Display custom notices based on the error/success codes. * * @since 1.0.0 */ public function display_custom_auth_notices() { $error = isset( $_GET['error'] ) ? sanitize_key( $_GET['error'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $success = isset( $_GET['success'] ) ? sanitize_key( $_GET['success'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( empty( $error ) && empty( $success ) ) { return; } if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_options() ) ) { return; } switch ( $error ) { case 'oauth_invalid_state': WP::add_admin_notice_with_debug( esc_html__( 'There was an error while processing the authentication request. The state key is invalid. Please try again.', 'wp-mail-smtp' ), WP::ADMIN_NOTICE_ERROR ); break; case 'google_invalid_nonce': WP::add_admin_notice_with_debug( esc_html__( 'There was an error while processing the authentication request. The nonce is invalid. Please try again.', 'wp-mail-smtp' ), WP::ADMIN_NOTICE_ERROR ); break; case 'google_access_denied': WP::add_admin_notice_with_debug( /* translators: %s - error code, returned by Google API. */ sprintf( esc_html__( 'There was an error while processing the authentication request: %s. Please try again.', 'wp-mail-smtp' ), '<code>' . $error . '</code>' ), WP::ADMIN_NOTICE_ERROR ); break; case 'google_no_code_scope': WP::add_admin_notice_with_debug( esc_html__( 'There was an error while processing the authentication request. Please try again.', 'wp-mail-smtp' ), WP::ADMIN_NOTICE_ERROR ); break; case 'google_no_clients': WP::add_admin_notice_with_debug( esc_html__( 'There was an error while processing the authentication request. Please make sure that you have Client ID and Client Secret both valid and saved.', 'wp-mail-smtp' ), WP::ADMIN_NOTICE_ERROR ); break; case 'google_unsuccessful_oauth': WP::add_admin_notice_with_debug( esc_html__( 'There was an error while processing the authentication request.', 'wp-mail-smtp' ), WP::ADMIN_NOTICE_ERROR ); break; } switch ( $success ) { case 'google_site_linked': WP::add_admin_notice_with_debug( esc_html__( 'You have successfully linked the current site with your Google API project. Now you can start sending emails through Gmail.', 'wp-mail-smtp' ), WP::ADMIN_NOTICE_SUCCESS ); break; } } /** * Display notice instructing the user to complete plugin setup. * * @since 1.3.0 */ public function display_setup_notice() { // Bail if we're not on a plugin page. if ( ! $this->is_admin_page( 'general' ) ) { return; } $default_options = wp_json_encode( Options::get_defaults() ); $current_options = wp_json_encode( Options::init()->get_all() ); // Check if the current settings are the same as the default settings. if ( $current_options !== $default_options ) { return; } // Display notice informing user further action is needed. WP::add_admin_notice( sprintf( wp_kses( /* translators: %s - Mailer anchor link. */ __( 'Thanks for using WP Mail SMTP! To complete the plugin setup and start sending emails, <strong>please select and configure your <a href="%s">Mailer</a></strong>.', 'wp-mail-smtp' ), [ 'a' => [ 'href' => [], ], 'strong' => [], ] ), wp_mail_smtp()->get_admin()->get_admin_page_url( self::SLUG . '#wp-mail-smtp-setting-row-mailer' ) ), WP::ADMIN_NOTICE_INFO ); } /** * Display notice explaining removal of "Email Test" tab. * * @since 3.9.0 */ public function display_email_test_tab_removal_notice() { // Bail if we aren't on a "Settings" page. if ( ! $this->is_admin_page( self::SLUG ) ) { return; } // Bail if the notice has been dismissed. if ( metadata_exists( 'user', get_current_user_id(), 'wp_mail_smtp_email_test_tab_removal_notice_dismissed' ) ) { return; } /* * Don't display the notice if the user installed a plugin with a new "Email Test" * location (starting from v3.9.0) and is not aware of the old one. Also, don't display * the notice if the `wp_mail_smtp_initial_version` option is not set (it can happen if * the plugin was activated network wise in the multisite installation and plugin * activation hook was not performed on the subsite level). */ if ( version_compare( get_option( 'wp_mail_smtp_initial_version', '3.9.0' ), '3.9.0', '>=' ) ) { return; } WP::add_admin_notice( sprintf( wp_kses( /* translators: %s: Tools page URL. */ __( 'The Email Test tab was moved to <a href="%s">WP Mail SMTP > Tools</a>.', 'wp-mail-smtp' ), [ 'a' => [ 'href' => [] ] ] ), $this->get_admin_page_url( self::SLUG . '-tools' ) ), implode( ' ', [ WP::ADMIN_NOTICE_INFO, 'email_test_tab_removal_notice' ] ) ); } /** * Get menu item position. * * @since 2.8.0 * * @return int */ public function get_menu_item_position() { /** * Filters menu item position. * * @since 2.8.0 * * @param int $position Position number. */ return apply_filters( 'wp_mail_smtp_admin_area_get_menu_item_position', 98 ); } /** * Add admin area menu item. * * @since 1.0.0 * @since 1.5.0 Moved the menu to the top level. Added several more pages. */ public function add_admin_options_page() { // Options pages access capability. $access_capability = wp_mail_smtp()->get_capability_manage_options(); $this->hook = add_menu_page( esc_html__( 'WP Mail SMTP', 'wp-mail-smtp' ), esc_html__( 'WP Mail SMTP', 'wp-mail-smtp' ), $access_capability, self::SLUG, [ $this, 'display' ], 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9IiM5ZWEzYTgiIHdpZHRoPSI2NCIgaGVpZ2h0PSI2NCIgdmlld0JveD0iMCAwIDQzIDM0Ij48cGF0aCBkPSJNMC4wMDcsMy41ODVWMjAuNDIxcTAsMy41ODYsMy43NTEsMy41ODVMMjAsMjRWMTlIMzBWMTQuMDE0bDAuOTkxLTFMMzQsMTNWMy41ODVRMzQsMCwzMC4yNDksMEgzLjc1OFEwLjAwNywwLC4wMDcsMy41ODVoMFpNMy41MjQsNi4xNTdhMS40OSwxLjQ5LDAsMCwxLS41MDgtMC45MzUsMS41ODEsMS41ODEsMCwwLDEsLjI3NC0xLjIwOCwxLjQ0OSwxLjQ0OSwwLDAsMSwxLjA5NC0uNjYzLDEuNzU2LDEuNzU2LDAsMCwxLDEuMjUuMzEybDExLjQwOSw3LjcxNkwyOC4zNzQsMy42NjNhMS45NiwxLjk2LDAsMCwxLDEuMjg5LS4zMTIsMS41NDYsMS41NDYsMCwwLDEsMS4wOTQuNjYzLDEuNCwxLjQsMCwwLDEsLjI3MywxLjIwOCwxLjY3LDEuNjcsMCwwLDEtLjU0Ny45MzVMMTcuMDQzLDE3LjIyNVoiLz48cGF0aCBkPSJNMjIsMjhIMzJsLTAuMDA5LDQuNjI0YTEuMTI2LDEuMTI2LDAsMCwwLDEuOTIyLjhsOC4yNS04LjIzNmExLjEyNiwxLjEyNiwwLDAsMCwwLTEuNTk0bC04LjI1LTguMjQxYTEuMTI2LDEuMTI2LDAsMCwwLTEuOTIyLjh2NC44NjZMMjIsMjF2N1oiLz48L3N2Zz4=', $this->get_menu_item_position() ); add_submenu_page( self::SLUG, $this->get_current_tab_title() . ' ‹ ' . \esc_html__( 'Settings', 'wp-mail-smtp' ), esc_html__( 'Settings', 'wp-mail-smtp' ), $access_capability, self::SLUG, [ $this, 'display' ] ); add_submenu_page( self::SLUG, esc_html__( 'Email Log', 'wp-mail-smtp' ), esc_html__( 'Email Log', 'wp-mail-smtp' ), $this->get_logs_access_capability(), self::SLUG . '-logs', [ $this, 'display' ] ); foreach ( $this->get_parent_pages() as $slug => $page ) { // Surface the rotating recommended-plugins item just before "About Us". if ( $slug === 'about' ) { $this->recommended_plugins->add_submenu_item( $access_capability ); } add_submenu_page( self::SLUG, esc_html( $page->get_title() ), esc_html( $page->get_label() ), $access_capability, self::SLUG . '-' . $page->get_slug(), [ $this, 'display' ] ); } if ( ! wp_mail_smtp()->is_pro() ) { add_submenu_page( self::SLUG, esc_html__( 'Upgrade to Pro', 'wp-mail-smtp' ), esc_html__( 'Upgrade to Pro', 'wp-mail-smtp' ), $access_capability, // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound esc_url( wp_mail_smtp()->get_upgrade_link( [ 'medium' => 'admin-menu', 'content' => 'Upgrade to Pro' ] ) ) ); } } /** * Add network admin settings page for the WPMS product education. * * @since 2.5.0 */ public function add_wpms_network_wide_setting_product_education_page() { add_menu_page( esc_html__( 'WP Mail SMTP', 'wp-mail-smtp' ), esc_html__( 'WP Mail SMTP', 'wp-mail-smtp' ), wp_mail_smtp()->get_capability_manage_global_options(), self::SLUG, [ $this, 'display_network_product_education_page' ], 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9IiM5ZWEzYTgiIHdpZHRoPSI2NCIgaGVpZ2h0PSI2NCIgdmlld0JveD0iMCAwIDQzIDM0Ij48cGF0aCBkPSJNMC4wMDcsMy41ODVWMjAuNDIxcTAsMy41ODYsMy43NTEsMy41ODVMMjAsMjRWMTlIMzBWMTQuMDE0bDAuOTkxLTFMMzQsMTNWMy41ODVRMzQsMCwzMC4yNDksMEgzLjc1OFEwLjAwNywwLC4wMDcsMy41ODVoMFpNMy41MjQsNi4xNTdhMS40OSwxLjQ5LDAsMCwxLS41MDgtMC45MzUsMS41ODEsMS41ODEsMCwwLDEsLjI3NC0xLjIwOCwxLjQ0OSwxLjQ0OSwwLDAsMSwxLjA5NC0uNjYzLDEuNzU2LDEuNzU2LDAsMCwxLDEuMjUuMzEybDExLjQwOSw3LjcxNkwyOC4zNzQsMy42NjNhMS45NiwxLjk2LDAsMCwxLDEuMjg5LS4zMTIsMS41NDYsMS41NDYsMCwwLDEsMS4wOTQuNjYzLDEuNCwxLjQsMCwwLDEsLjI3MywxLjIwOCwxLjY3LDEuNjcsMCwwLDEtLjU0Ny45MzVMMTcuMDQzLDE3LjIyNVoiLz48cGF0aCBkPSJNMjIsMjhIMzJsLTAuMDA5LDQuNjI0YTEuMTI2LDEuMTI2LDAsMCwwLDEuOTIyLjhsOC4yNS04LjIzNmExLjEyNiwxLjEyNiwwLDAsMCwwLTEuNTk0bC04LjI1LTguMjQxYTEuMTI2LDEuMTI2LDAsMCwwLTEuOTIyLjh2NC44NjZMMjIsMjF2N1oiLz48L3N2Zz4=', $this->get_menu_item_position() ); } /** * HTML output for the network admin settings page (for the WPMS product education). * * @since 2.5.0 */ public function display_network_product_education_page() { // Skip if not on multisite and not on network admin site. if ( ! is_multisite() || ! is_network_admin() ) { return; } ?> <div class="wrap" id="wp-mail-smtp"> <div class="wp-mail-smtp-page wp-mail-smtp-page-general wp-mail-smtp-page-nw-product-edu wp-mail-smtp-tab-settings"> <div class="wp-mail-smtp-page-title"> <a href="#" class="tab active"> <?php esc_html_e( 'General', 'wp-mail-smtp' ); ?> </a> </div> <div class="wp-mail-smtp-page-content"> <h1 class="screen-reader-text"> <?php esc_html_e( 'General', 'wp-mail-smtp' ); ?> </h1> <?php do_action( 'wp_mail_smtp_admin_pages_before_content' ); ?> <!-- Multisite Section Title --> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-content wp-mail-smtp-clear section-heading no-desc" id="wp-mail-smtp-setting-row-multisite-heading"> <div class="wp-mail-smtp-setting-field"> <h2><?php esc_html_e( 'Multisite', 'wp-mail-smtp' ); ?></h2> <img src="<?php echo esc_url( wp_mail_smtp()->assets_url . '/images/pro-badge.svg' ); ?>" class="badge" alt="<?php esc_attr_e( 'Pro+ badge icon', 'wp-mail-smtp' ); ?>"> </div> <p> <?php esc_html_e( 'Simply enable network-wide settings and every site on your network will inherit the same SMTP settings. Save time and only configure your SMTP provider once.', 'wp-mail-smtp' ); ?> </p> </div> <!-- Network wide setting --> <div id="wp-mail-smtp-setting-row-multisite" class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-multisite wp-mail-smtp-clear"> <div class="wp-mail-smtp-setting-label"> <label for="wp-mail-smtp-setting-multisite-settings-control"><?php esc_html_e( 'Settings control', 'wp-mail-smtp' ); ?></label> </div> <div class="wp-mail-smtp-setting-field"> <input name="wp-mail-smtp[general][nw_product_edu]" type="checkbox" value="true" id="wp-mail-smtp-setting-nw-product-edu" disabled> <label for="wp-mail-smtp-setting-nw-product-edu"> <?php esc_html_e( 'Make the plugin settings global network-wide', 'wp-mail-smtp' ); ?> </label> <p class="desc"> <?php esc_html_e( 'If disabled, each subsite of this multisite will have its own WP Mail SMTP settings page that has to be configured separately.', 'wp-mail-smtp' ); ?> <br> <?php esc_html_e( 'If enabled, these global settings will manage email sending for all subsites of this multisite.', 'wp-mail-smtp' ); ?> </p> </div> </div> <div class="wp-mail-smtp-setting-row-no-setting"> <a href="<?php echo esc_url( wp_mail_smtp()->get_upgrade_link( [ 'medium' => 'network-settings', 'content' => '' ] ) ); // phpcs:ignore ?>" target="_blank" rel="noopener noreferrer" class="wp-mail-smtp-btn wp-mail-smtp-btn-lg wp-mail-smtp-btn-orange"> <?php esc_html_e( 'Upgrade to WP Mail SMTP Pro', 'wp-mail-smtp' ); ?> </a> </div> </div> </div> </div> <?php } /** * Redirect the "Email Log" WP menu link to the "Email Log" setting tab for lite version of the plugin. * * @since 2.1.0 */ public function maybe_redirect_email_log_menu_to_email_log_settings_tab() { /** * The Email Logs object to be used for loading the Email Log page. * * @var \WPMailSMTP\Admin\PageAbstract $logs */ $logs = $this->generate_display_logs_object(); if ( $logs instanceof \WPMailSMTP\Admin\Pages\Logs ) { wp_safe_redirect( $logs->get_link() ); exit; } } /** * Enqueue admin area scripts and styles. * * @since 1.0.0 * @since 1.5.0 Added new assets for new pages. * @since 1.7.0 Added jQuery Confirm library css/js files. * * @param string $hook Current hook. */ public function enqueue_assets( $hook ) { if ( strpos( $hook, self::SLUG ) === false ) { return; } // Set general body class. add_filter( 'admin_body_class', function ( $classes ) { $classes .= ' wp-mail-smtp-admin-page-body'; if ( wp_mail_smtp()->is_pro() ) { $classes .= ' wp-mail-smtp-pro'; } else { $classes .= ' wp-mail-smtp-lite'; } return $classes; } ); wp_register_style( 'wp-mail-smtp-admin-lity', wp_mail_smtp()->assets_url . '/css/vendor/lity.min.css', [], '2.4.1' ); wp_register_script( 'wp-mail-smtp-admin-lity', wp_mail_smtp()->assets_url . '/js/vendor/lity.min.js', [], '2.4.1' ); // General styles and js. wp_enqueue_style( 'wp-mail-smtp-admin', wp_mail_smtp()->assets_url . '/css/smtp-admin.min.css', false, WPMS_PLUGIN_VER ); wp_enqueue_script( 'underscore' ); wp_enqueue_script( 'wp-mail-smtp-admin', wp_mail_smtp()->assets_url . '/js/smtp-admin' . WP::asset_min() . '.js', [ 'jquery', 'underscore' ], WPMS_PLUGIN_VER, false ); $script_data = [ 'text_provider_remove' => esc_html__( 'Are you sure you want to reset the current provider connection? You will need to immediately create a new one to be able to send emails.', 'wp-mail-smtp' ), 'text_settings_not_saved' => esc_html__( 'Changes that you made to the settings are not saved!', 'wp-mail-smtp' ), 'default_mailer_notice' => [ 'title' => esc_html__( 'Heads up!', 'wp-mail-smtp' ), 'content' => wp_kses( __( '<p>The Default (PHP) mailer is currently selected, but is not recommended because in most cases it does not resolve email delivery issues.</p><p>Please consider selecting and configuring one of the other mailers.</p>', 'wp-mail-smtp' ), [ 'p' => [] ] ), 'save_button' => esc_html__( 'Save Settings', 'wp-mail-smtp' ), 'cancel_button' => esc_html__( 'Cancel', 'wp-mail-smtp' ), 'icon_alt' => esc_html__( 'Warning icon', 'wp-mail-smtp' ), ], 'plugin_url' => wp_mail_smtp()->plugin_url, 'education' => [ 'upgrade_icon_lock' => '<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="lock" class="svg-inline--fa fa-lock fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M400 224h-24v-72C376 68.2 307.8 0 224 0S72 68.2 72 152v72H48c-26.5 0-48 21.5-48 48v192c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V272c0-26.5-21.5-48-48-48zm-104 0H152v-72c0-39.7 32.3-72 72-72s72 32.3 72 72v72z"></path></svg>', 'upgrade_title' => esc_html__( '%name% is a PRO Feature', 'wp-mail-smtp' ), 'upgrade_content' => esc_html__( 'We\'re sorry, the %name% mailer is not available on your plan. Please upgrade to the PRO plan to unlock all these awesome features.', 'wp-mail-smtp' ), 'upgrade_button' => esc_html__( 'Upgrade to Pro', 'wp-mail-smtp' ), 'upgrade_url' => add_query_arg( 'discount', 'SMTPLITEUPGRADE', wp_mail_smtp()->get_upgrade_link( '' ) ), 'upgrade_bonus' => '<p>' . sprintf( wp_kses( /* Translators: %s - discount value $50. */ __( '<strong>Bonus:</strong> WP Mail SMTP users get <span>%s off</span> regular price,<br>applied at checkout.', 'wp-mail-smtp' ), [ 'strong' => [], 'span' => [], 'br' => [], ] ), '$50' ) . '</p>', 'upgrade_doc' => sprintf( '<a href="%1$s" target="_blank" rel="noopener noreferrer" class="already-purchased">%2$s</a>', // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/how-to-upgrade-wp-mail-smtp-to-pro-version/', [ 'medium' => 'plugin-settings', 'content' => 'Pro Mailer Popup - Already purchased' ] ) ), esc_html__( 'Already purchased?', 'wp-mail-smtp' ) ), 'gmail' => [ 'one_click_setup_upgrade_title' => wp_kses( __( 'One-Click Setup for Google Mailer <br> is a Pro Feature', 'wp-mail-smtp' ), [ 'br' => [] ] ), 'one_click_setup_upgrade_content' => esc_html__( 'We\'re sorry, One-Click Setup for Google Mailer is not available on your plan. Please upgrade to the Pro plan to unlock all these awesome features.', 'wp-mail-smtp' ), ], 'rate_limit' => [ 'upgrade_title' => wp_kses( __( 'Email Rate Limiting <br> is a Pro Feature', 'wp-mail-smtp' ), [ 'br' => [] ] ), 'upgrade_content' => esc_html__( 'We\'re sorry, Email Rate Limiting is not available on your plan. Please upgrade to the Pro plan to unlock all these awesome features.', 'wp-mail-smtp' ), ], ], 'all_mailers_supports' => wp_mail_smtp()->get_providers()->get_supports_all(), 'nonce' => wp_create_nonce( 'wp-mail-smtp-admin' ), 'is_network_admin' => is_network_admin(), 'ajax_url' => admin_url( 'admin-ajax.php' ), 'lang_code' => sanitize_key( WP::get_language_code() ), 'sendlayer' => [ 'connect_nonce' => wp_create_nonce( 'wp-mail-smtp-sendlayer-connect' ), 'return_url' => $this->get_admin_page_url(), 'error_title' => esc_html__( 'Error', 'wp-mail-smtp' ), 'error_text' => esc_html__( 'An error occurred. Please try again.', 'wp-mail-smtp' ), 'server_error' => esc_html__( 'A server error occurred. Please try again.', 'wp-mail-smtp' ), 'connecting_text' => esc_html__( 'Connecting...', 'wp-mail-smtp' ), ], 'plugin_install' => [ 'processing' => esc_html__( 'Processing...', 'wp-mail-smtp' ), 'installed' => esc_html__( 'Installed', 'wp-mail-smtp' ), 'activate' => esc_html__( 'Activate', 'wp-mail-smtp' ), /* translators: %s - plugin name (e.g. WPConsent). */ 'activate_with_name' => esc_html__( 'Activate %s', 'wp-mail-smtp' ), 'setup_now' => esc_html__( 'Setup Now', 'wp-mail-smtp' ), 'error' => esc_html__( 'Could not install a plugin. Please download from WordPress.org and install manually.', 'wp-mail-smtp' ), 'error_title' => esc_html__( 'Error', 'wp-mail-smtp' ), 'btn_ok' => esc_html__( 'OK', 'wp-mail-smtp' ), ], 'dismiss_error' => esc_html__( 'Could not dismiss the notice. Please try again.', 'wp-mail-smtp' ), 'hide_delivery_errors' => [ 'title' => esc_html__( 'Are you sure?', 'wp-mail-smtp' ), 'content' => wp_kses( __( '<p>Hiding email delivery errors means you will no longer be warned when emails fail to send. This is not recommended and should only be used on staging or development sites.</p>', 'wp-mail-smtp' ), [ 'p' => [], ] ), 'confirm_button' => esc_html__( 'Yes', 'wp-mail-smtp' ), 'cancel_button' => esc_html__( 'Cancel', 'wp-mail-smtp' ), ], ]; /** * Filters plugin script data. * * @since 2.9.0 * * @param array $script_data Data. * @param string $hook Current hook. */ $script_data = apply_filters( 'wp_mail_smtp_admin_area_enqueue_assets_scripts_data', $script_data, $hook ); wp_localize_script( 'wp-mail-smtp-admin', 'wp_mail_smtp', $script_data ); /* * jQuery Confirm library v3.3.4. */ wp_enqueue_style( 'wp-mail-smtp-admin-jconfirm', wp_mail_smtp()->assets_url . '/css/vendor/jquery-confirm.min.css', [ 'wp-mail-smtp-admin' ], '3.3.4' ); wp_enqueue_script( 'wp-mail-smtp-admin-jconfirm', wp_mail_smtp()->assets_url . '/js/vendor/jquery-confirm.min.js', [ 'wp-mail-smtp-admin' ], '3.3.4', false ); /* * Logs page. */ if ( $this->is_admin_page( 'logs' ) ) { wp_enqueue_style( 'wp-mail-smtp-admin-logs', apply_filters( 'wp_mail_smtp_admin_enqueue_assets_logs_css', '' ), [ 'wp-mail-smtp-admin' ], WPMS_PLUGIN_VER ); wp_enqueue_script( 'wp-mail-smtp-admin-logs', apply_filters( 'wp_mail_smtp_admin_enqueue_assets_logs_js', '' ), [ 'wp-mail-smtp-admin' ], WPMS_PLUGIN_VER, false ); } /* * About page. */ if ( $this->is_admin_page( 'about' ) ) { wp_enqueue_style( 'wp-mail-smtp-admin-about', wp_mail_smtp()->assets_url . '/css/smtp-about.min.css', [ 'wp-mail-smtp-admin' ], WPMS_PLUGIN_VER ); wp_enqueue_script( 'wp-mail-smtp-admin-about', wp_mail_smtp()->assets_url . '/js/smtp-about' . WP::asset_min() . '.js', [ 'wp-mail-smtp-admin' ], '0.7.2', false ); $settings = [ 'ajax_url' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'wp-mail-smtp-admin' ), // Strings. 'plugin_activate' => esc_html__( 'Activate', 'wp-mail-smtp' ), 'plugin_activated' => esc_html__( 'Activated', 'wp-mail-smtp' ), 'plugin_active' => esc_html__( 'Active', 'wp-mail-smtp' ), 'plugin_inactive' => esc_html__( 'Inactive', 'wp-mail-smtp' ), 'plugin_processing' => esc_html__( 'Processing...', 'wp-mail-smtp' ), 'plugin_visit' => esc_html__( 'Visit Site', 'wp-mail-smtp' ), 'plugin_install_error' => esc_html__( 'Could not install a plugin. Please download from WordPress.org and install manually.', 'wp-mail-smtp' ), 'plugin_install_activate_btn' => esc_html__( 'Install and Activate', 'wp-mail-smtp' ), 'plugin_activate_btn' => esc_html__( 'Activate', 'wp-mail-smtp' ), 'plugin_download_btn' => esc_html__( 'Download', 'wp-mail-smtp' ), ]; wp_localize_script( 'wp-mail-smtp-admin-about', 'wp_mail_smtp_about', $settings ); wp_enqueue_script( 'wp-mail-smtp-admin-about-matchheight', wp_mail_smtp()->assets_url . '/js/vendor/jquery.matchHeight.min.js', [ 'wp-mail-smtp-admin' ], '0.7.2', false ); } if ( $this->is_admin_page( 'general' ) && ! wp_mail_smtp()->is_pro() && ! get_user_meta( get_current_user_id(), 'wp_mail_smtp_pro_banner_dismissed', true ) ) { wp_enqueue_style( 'wp-mail-smtp-admin-lity' ); wp_enqueue_script( 'wp-mail-smtp-admin-lity' ); } /** * Fires after enqueue plugin assets. * * @since 1.5.0 * * @param string $hook Current hook. */ do_action( 'wp_mail_smtp_admin_area_enqueue_assets', $hook ); } /** * Outputs the plugin admin header. * * @since 1.0.0 */ public function display_admin_header() { // Bail if we're not on a plugin page. if ( ! $this->is_admin_page() ) { return; } do_action( 'wp_mail_smtp_admin_header_before' ); ?> <div id="wp-mail-smtp-header-temp"></div> <div id="wp-mail-smtp-header"> <!--suppress HtmlUnknownTarget --> <img class="wp-mail-smtp-header-logo" src="<?php echo esc_url( wp_mail_smtp()->assets_url ); ?>/images/logo<?php echo wp_mail_smtp()->is_white_labeled() ? '-whitelabel' : ''; ?>.svg" alt="WP Mail SMTP"/> </div> <?php } /** * Display a text to ask users to review the plugin on WP.org. * * @since 1.0.0 * * @param string $text The default text to display in admin plugin page footer. * * @return string */ public function get_admin_footer( $text ) { if ( $this->is_admin_page() ) { $url = 'https://wpmailsmtp.com/wpmailsmtp-wordpress-review/'; $text = sprintf( wp_kses( /* translators: %1$s - WP.org link; %2$s - same WP.org link. */ __( 'Please rate <strong>WP Mail SMTP</strong> <a href="%1$s" target="_blank" rel="noopener noreferrer">★★★★★</a> on <a href="%2$s" target="_blank" rel="noopener noreferrer">WordPress.org</a> to help us spread the word. Thank you from the WP Mail SMTP team!', 'wp-mail-smtp' ), array( 'strong' => array(), 'a' => array( 'href' => array(), 'target' => array(), 'rel' => array(), ), ) ), $url, $url ); } return $text; } /** * Display content of the admin area page. * * @since 1.0.0 * @since 1.5.0 Rewrite to distinguish between General tabs and separate pages. */ public function display() { // phpcs:ignore Generic.Metrics.NestingLevel.MaxExceeded // Bail if we're not on a plugin page. if ( ! $this->is_admin_page() ) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended $page = ! empty( $_GET['page'] ) ? \sanitize_key( $_GET['page'] ) : ''; ?> <div class="wrap" id="wp-mail-smtp"> <?php switch ( $page ) { case self::SLUG: ?> <div class="wp-mail-smtp-page wp-mail-smtp-page-general wp-mail-smtp-tab-<?php echo esc_attr( $this->get_current_tab() ); ?>"> <?php $this->display_tabs(); ?> </div> <?php break; case self::SLUG . '-logs': /** * The Email Logs object to be used for loading the Email Log page. * * @var \WPMailSMTP\Admin\PageAbstract $logs */ $logs = $this->generate_display_logs_object(); $is_archive = wp_mail_smtp()->is_pro() && wp_mail_smtp()->pro->get_logs()->is_archive(); ?> <div class="wp-mail-smtp-page wp-mail-smtp-page-logs <?php echo $is_archive ? 'wp-mail-smtp-page-logs-archive' : 'wp-mail-smtp-page-logs-single'; ?>"> <?php $logs->display(); ?> </div> <?php break; default: foreach ( $this->get_parent_pages() as $parent_page ) { if ( $page === self::SLUG . '-' . $parent_page->get_slug() ) { ?> <div class="wp-mail-smtp-page wp-mail-smtp-page-<?php echo esc_attr( $parent_page->get_slug() ); ?> wp-mail-smtp-tab-<?php echo esc_attr( $parent_page->get_slug() ); ?>-<?php echo esc_attr( $parent_page->get_current_tab() ); ?>"> <?php $parent_page->display(); ?> </div> <?php break; } } } ?> </div> <?php } /** * Generate the appropriate Email Log page object used for displaying the Email Log page. * * @since 2.1.0 * * @return \WPMailSMTP\Admin\PageAbstract */ public function generate_display_logs_object() { // Store generated object to make sure that it's created only once. static $logs_object = null; $logs_class = apply_filters( 'wp_mail_smtp_admin_display_get_logs_fqcn', \WPMailSMTP\Admin\Pages\Logs::class ); if ( $logs_object === null ) { $logs_object = new $logs_class(); } return $logs_object; } /** * Get email logs access capability. * * @since 2.8.0 * * @return string */ public function get_logs_access_capability() { /** * Filter email logs access capability. * * @since 2.8.0 * * @param string $capability Email logs access capability. */ return apply_filters( 'wp_mail_smtp_admin_area_get_logs_access_capability', wp_mail_smtp()->get_capability_manage_options() ); } /** * Display General page tabs. * * @since 1.5.0 */ protected function display_tabs() { ?> <div class="wp-mail-smtp-page-title"> <?php foreach ( $this->get_pages() as $page_slug => $page ) : $label = $page->get_label(); if ( empty( $label ) ) { continue; } $class = $page_slug === $this->get_current_tab() ? 'active' : ''; $tab_class = self::SLUG . '-tab-' . str_replace( '_', '-', sanitize_title( $page_slug ) ); $class = trim( $class . ' ' . $tab_class ); ?> <a href="<?php echo esc_url( $page->get_link() ); ?>" class="tab <?php echo esc_attr( $class ); ?>"> <?php echo esc_html( $label ); ?> </a> <?php endforeach; ?> </div> <div class="wp-mail-smtp-page-content"> <h1 class="screen-reader-text"> <?php echo esc_html( $this->get_current_tab_title() ); ?> </h1> <?php do_action( 'wp_mail_smtp_admin_pages_before_content' ); ?> <?php $this->display_current_tab_content(); ?> </div> <?php } /** * Get the current tab content. * * @since 1.0.0 */ public function display_current_tab_content() { $pages = $this->get_pages(); if ( ! array_key_exists( $this->get_current_tab(), $pages ) ) { return; } $pages[ $this->get_current_tab() ]->display(); } /** * Get the current admin area tab. * * @since 1.0.0 * * @return string */ public function get_current_tab() { $current = ''; if ( $this->is_admin_page( 'general' ) ) { $current = ! empty( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : 'settings'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended } return $current; } /** * Get admin parent pages. * * @since 2.8.0 * * @return ParentPageAbstract[] */ public function get_parent_pages() { static $pages = null; if ( $pages === null ) { $pages = [ 'reports' => new Pages\EmailReports( [ 'reports' => Pages\EmailReportsTab::class, ] ), 'tools' => new Pages\Tools( $this->get_tools_tabs() ), ]; if ( ! wp_mail_smtp()->is_white_labeled() ) { $about_tabs = [ 'about' => Pages\AboutTab::class, ]; $pages['about'] = new Pages\About( $about_tabs ); } } /** * Filters admin parent pages. * * @since 2.8.0 * * @param ParentPageAbstract[] $pages Parent pages. */ return apply_filters( 'wp_mail_smtp_admin_area_get_parent_pages', $pages ); } /** * Tabs registered under the Tools parent page. * * The AI MCP tab registers only on WP 6.9+, where the Abilities API exists * and the plugin's read-only abilities are available for AI clients to use. * * @since 4.9.0 * * @return array */ private function get_tools_tabs() { $tabs = [ 'test' => Pages\TestTab::class, 'export' => Pages\ExportTab::class, 'action-scheduler' => Pages\ActionSchedulerTab::class, 'debug-events' => Pages\DebugEventsTab::class, 'code-snippets' => Pages\CodeSnippetsTab::class, ]; if ( function_exists( 'wp_register_ability' ) ) { $tabs['ai-mcp'] = Pages\AiMcpTab::class; } return $tabs; } /** * Get the array of default registered tabs for General page admin area. * * @since 1.0.0 * * @return PageAbstract[] */ public function get_pages() { if ( empty( $this->pages ) ) { $this->pages = [ 'settings' => new Pages\SettingsTab(), 'logs' => new Pages\LogsTab(), 'alerts' => new Pages\AlertsTab(), 'connections' => new Pages\AdditionalConnectionsTab(), 'routing' => new Pages\SmartRoutingTab(), 'control' => new Pages\ControlTab(), 'misc' => new Pages\MiscTab(), 'auth' => new Pages\AuthTab(), ]; // Product-education upsell tab, shown to non-Pro users. if ( ! wp_mail_smtp()->is_pro() ) { $this->pages['get-pro'] = new Pages\GetProTab(); } } return apply_filters( 'wp_mail_smtp_admin_get_pages', $this->pages ); } /** * Get the current tab title. * * @since 1.0.0 * * @return string */ public function get_current_tab_title() { $pages = $this->get_pages(); if ( ! array_key_exists( $this->get_current_tab(), $pages ) ) { return ''; } return $pages[ $this->get_current_tab() ]->get_title(); } /** * Check whether we are on an admin page. * * @since 1.0.0 * @since 1.5.0 Added support for new pages. * * @param array|string $slug ID(s) of a plugin page. Possible values: 'general', 'logs', 'about' or array of them. * * @return bool */ public function is_admin_page( $slug = array() ) { // phpcs:ignore Generic.Metrics.NestingLevel.MaxExceeded // phpcs:ignore WordPress.Security.NonceVerification.Recommended $cur_page = isset( $_GET['page'] ) ? sanitize_key( $_GET['page'] ) : ''; $check = self::SLUG; $pages_equal = false; if ( is_string( $slug ) ) { $slug = sanitize_key( $slug ); if ( in_array( $slug, self::$pages_registered, true ) && $slug !== 'general' ) { $check = self::SLUG . '-' . $slug; } $pages_equal = $cur_page === $check; } elseif ( is_array( $slug ) ) { if ( empty( $slug ) ) { $slug = array_map( function ( $v ) { if ( $v === 'general' ) { return Area::SLUG; } return Area::SLUG . '-' . $v; }, self::$pages_registered ); } else { $slug = array_map( function ( $v ) { if ( $v === 'general' ) { return Area::SLUG; } return Area::SLUG . '-' . sanitize_key( $v ); }, $slug ); } $pages_equal = in_array( $cur_page, $slug, true ); } return is_admin() && $pages_equal; } /** * Give ability to use either admin area option or a filter to hide error notices about failed email delivery. * Filter has higher priority and overrides an option. * * @since 1.6.0 * * @return bool */ public function is_error_delivery_notice_enabled() { $is_hard_enabled = (bool) apply_filters( 'wp_mail_smtp_admin_is_error_delivery_notice_enabled', true ); // If someone changed the value to false using a filter - disable completely. if ( ! $is_hard_enabled ) { return false; } return ! (bool) Options::init()->get( 'general', 'email_delivery_errors_hidden' ); } /** * All possible plugin forms manipulation will be done here. * * @since 1.0.0 */ public function process_actions() { // Bail if we're not on a plugin General page. if ( ! $this->is_admin_page( 'general' ) ) { return; } $pages = $this->get_pages(); // Allow to process only own tabs. if ( ! array_key_exists( $this->get_current_tab(), $pages ) ) { return; } // Process POST only if it exists. // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash if ( ! empty( $_POST ) && isset( $_POST['wp-mail-smtp-post'] ) ) { $current_tab = $pages[ $this->get_current_tab() ]; // Verify nonce. $current_tab->check_admin_referer(); // Verify capability. if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) { wp_die( esc_html__( 'You don\'t have the capability to perform this action.', 'wp-mail-smtp' ) ); } if ( ! empty( $_POST['wp-mail-smtp'] ) ) { $post = $_POST['wp-mail-smtp']; } else { $post = []; } /** * Before process post. * * @since 3.3.0 * * @param array $post POST data. * @param string $page_slug Current page slug. */ do_action( 'wp_mail_smtp_admin_area_process_actions_process_post_before', $post, $current_tab->get_slug() ); $current_tab->process_post( $post ); } // phpcs:enable // This won't do anything for most pages. // Works for plugin page only, when GET params are allowed. $pages[ $this->get_current_tab() ]->process_auth(); } /** * Process all AJAX requests. * * @since 1.3.0 * @since 1.5.0 Added tasks to process plugins management. */ public function process_ajax() { $data = []; // Only admins can fire these ajax requests. if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_options() ) ) { wp_send_json_error( $data ); } // Verify nonce for all AJAX requests. check_ajax_referer( 'wp-mail-smtp-admin', 'nonce' ); if ( empty( $_POST['task'] ) ) { wp_send_json_error( $data ); } $task = sanitize_key( $_POST['task'] ); switch ( $task ) { case 'pro_banner_dismiss': update_user_meta( get_current_user_id(), 'wp_mail_smtp_pro_banner_dismissed', true ); $data['message'] = esc_html__( 'WP Mail SMTP Pro related message was successfully dismissed.', 'wp-mail-smtp' ); break; case 'about_plugin_install': Pages\AboutTab::ajax_plugin_install(); break; case 'about_plugin_activate': Pages\AboutTab::ajax_plugin_activate(); break; case 'notice_dismiss': $dismissal_response = $this->dismiss_notice_via_ajax(); if ( empty( $dismissal_response ) ) { break; } $data['message'] = $dismissal_response; break; case 'email_test_tab_removal_notice_dismiss': update_user_meta( get_current_user_id(), 'wp_mail_smtp_email_test_tab_removal_notice_dismissed', true ); break; default: // Allow custom tasks data processing being added here. $data = apply_filters( 'wp_mail_smtp_admin_process_ajax_' . $task . '_data', $data ); } // Final ability to rewrite all the data, just in case. $data = (array) apply_filters( 'wp_mail_smtp_admin_process_ajax_data', $data, $task ); if ( empty( $data ) ) { wp_send_json_error( $data ); } wp_send_json_success( $data ); } /** * Process the notice dismissal via AJAX call (Post request). * * @since 3.3.0 * * @return false|string */ private function dismiss_notice_via_ajax() { // Nonce already verified in process_ajax(). if ( empty( $_POST['notice'] ) ) { return false; } $notice = sanitize_key( $_POST['notice'] ); if ( ! empty( $_POST['mailer'] ) ) { $mailer = sanitize_key( $_POST['mailer'] ); update_user_meta( get_current_user_id(), "wp_mail_smtp_notice_{$notice}_for_{$mailer}_dismissed", true ); return esc_html__( 'Educational notice for this mailer was successfully dismissed.', 'wp-mail-smtp' ); } else { update_user_meta( get_current_user_id(), "wp_mail_smtp_notice_{$notice}_dismissed", true ); return esc_html__( 'Notice was successfully dismissed.', 'wp-mail-smtp' ); } } /** * Add plugin action links on Plugins page (lite version only). * * @since 1.0.0 * @since 1.5.0 Added a link to Email Log. * @since 2.0.0 Adjusted links. Process only the Lite plugin. * * @param array $links Existing plugin action links. * * @return array */ public function add_plugin_action_link( $links ) { // Do not register lite plugin action links if on pro version. if ( wp_mail_smtp()->is_pro() ) { return $links; } $custom['wp-mail-smtp-pro'] = sprintf( '<a href="%1$s" aria-label="%2$s" target="_blank" rel="noopener noreferrer" style="color: #00a32a; font-weight: 700;" onmouseover="this.style.color=\'#008a20\';" onmouseout="this.style.color=\'#00a32a\';" >%3$s</a>', // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound esc_url( wp_mail_smtp()->get_upgrade_link( [ 'medium' => 'all-plugins', 'content' => 'Get WP Mail SMTP Pro' ] ) ), esc_attr__( 'Upgrade to WP Mail SMTP Pro', 'wp-mail-smtp' ), esc_html__( 'Get WP Mail SMTP Pro', 'wp-mail-smtp' ) ); $custom['wp-mail-smtp-settings'] = sprintf( '<a href="%s" aria-label="%s">%s</a>', esc_url( $this->get_admin_page_url() ), esc_attr__( 'Go to WP Mail SMTP Settings page', 'wp-mail-smtp' ), esc_html__( 'Settings', 'wp-mail-smtp' ) ); $custom['wp-mail-smtp-docs'] = sprintf( '<a href="%1$s" target="_blank" aria-label="%2$s" rel="noopener noreferrer">%3$s</a>', // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/', [ 'medium' => 'all-plugins', 'content' => 'Documentation' ] ) ), esc_attr__( 'Go to WPMailSMTP.com documentation page', 'wp-mail-smtp' ), esc_html__( 'Docs', 'wp-mail-smtp' ) ); return array_merge( $custom, (array) $links ); } /** * Get plugin admin area page URL. * * @since 1.0.0 * @since 1.5.0 URL is changed to support the top level position of the plugin admin area. * * @param string $page The page slug to add as the page query parameter. * * @return string */ public function get_admin_page_url( $page = '' ) { if ( empty( $page ) ) { $page = self::SLUG; } return add_query_arg( 'page', $page, WP::admin_url( 'admin.php' ) ); } /** * Remove all non-WP Mail SMTP plugin notices from our plugin pages. * * @since 1.0.0 */ public function hide_unrelated_notices() { // Bail if we're not on our screen or page. if ( ! $this->is_admin_page() ) { return; } $this->remove_unrelated_actions( 'user_admin_notices' ); $this->remove_unrelated_actions( 'admin_notices' ); $this->remove_unrelated_actions( 'all_admin_notices' ); $this->remove_unrelated_actions( 'network_admin_notices' ); } /** * Remove all non-WP Mail SMTP notices from the our plugin pages based on the provided action hook. * * @since 3.0.0 * * @param string $action The name of the action. */ private function remove_unrelated_actions( $action ) { global $wp_filter; if ( empty( $wp_filter[ $action ]->callbacks ) || ! is_array( $wp_filter[ $action ]->callbacks ) ) { return; } foreach ( $wp_filter[ $action ]->callbacks as $priority => $hooks ) { foreach ( $hooks as $name => $arr ) { if ( ( // Cover object method callback case. is_array( $arr['function'] ) && isset( $arr['function'][0] ) && is_object( $arr['function'][0] ) && strpos( strtolower( get_class( $arr['function'][0] ) ), 'wpmailsmtp' ) !== false ) || ( // Cover class static method callback case. ! empty( $name ) && strpos( strtolower( $name ), 'wpmailsmtp' ) !== false ) ) { continue; } unset( $wp_filter[ $action ]->callbacks[ $priority ][ $name ] ); } } } /** * Maybe redirect to "Tools -> Email Test" page if old direct URL to "Settings -> Email Test" is accessed. * * @deprecated 3.9.0 * * @since 2.8.0 */ public function maybe_redirect_test_tab() { _deprecated_function( __METHOD__, '3.9.0' ); if ( $this->is_admin_page( 'general' ) && $this->get_current_tab() === 'test' ) { wp_safe_redirect( add_query_arg( 'tab', 'test', $this->get_admin_page_url( self::SLUG . '-tools' ) ) ); } } /** * Define inline styles for "Upgrade to Pro" left sidebar menu item. * * @since 3.4.0 */ public function style_upgrade_pro_link() { global $submenu; // Bail if plugin menu is not registered. if ( ! isset( $submenu[ self::SLUG ] ) ) { return; } $upgrade_link_position = key( array_filter( $submenu[ self::SLUG ], function ( $item ) { return strpos( urldecode( $item[2] ), 'wpmailsmtp.com/lite-upgrade' ) !== false; } ) ); // Bail if "Upgrade to Pro" menu item is not registered. if ( is_null( $upgrade_link_position ) ) { return; } // Prepare a HTML class. // phpcs:disable WordPress.WP.GlobalVariablesOverride.Prohibited if ( isset( $submenu[ self::SLUG ][ $upgrade_link_position ][4] ) ) { $submenu[ self::SLUG ][ $upgrade_link_position ][4] .= ' wp-mail-smtp-sidebar-upgrade-pro'; } else { $submenu[ self::SLUG ][ $upgrade_link_position ][] = 'wp-mail-smtp-sidebar-upgrade-pro'; } $current_screen = get_current_screen(); $upgrade_utm_content = $current_screen === null ? 'Upgrade to Pro' : 'Upgrade to Pro - ' . $current_screen->base; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $upgrade_utm_content = empty( $_GET['tab'] ) ? $upgrade_utm_content : $upgrade_utm_content . ' -- ' . sanitize_key( $_GET['tab'] ); // Add the correct utm_content to the menu item. $submenu[ self::SLUG ][ $upgrade_link_position ][2] = esc_url( wp_mail_smtp()->get_upgrade_link( [ 'medium' => 'admin-menu', 'content' => $upgrade_utm_content ] ) ); // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound // phpcs:enable WordPress.WP.GlobalVariablesOverride.Prohibited // Output inline styles. echo '<style>a.wp-mail-smtp-sidebar-upgrade-pro { background-color: #00a32a !important; color: #fff !important; font-weight: 600 !important; }</style>'; } /** * Display the promotional footer in our plugin pages. * * @since 3.10.0 */ public function display_admin_footer() { //phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh // Bail early on non-plugin pages. if ( ! $this->is_admin_page() ) { return; } $title = esc_html__( 'Made with ♥ by the WP Mail SMTP team', 'wp-mail-smtp' ); $links = [ [ 'url' => wp_mail_smtp()->is_pro() ? wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/account/support/', [ 'medium' => 'Plugin Footer', 'content' => 'Contact Support', ] ) : 'https://wordpress.org/support/plugin/wp-mail-smtp/', 'text' => esc_html__( 'Support', 'wp-mail-smtp' ), 'target' => '_blank', ], [ 'url' => wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/', [ 'medium' => 'Plugin Footer', 'content' => 'Plugin Documentation', ] ), 'text' => esc_html__( 'Docs', 'wp-mail-smtp' ), 'target' => '_blank', ], ]; if ( ! wp_mail_smtp()->is_white_labeled() ) { $links[] = [ 'url' => $this->get_admin_page_url( self::SLUG . '-about' ), 'text' => esc_html__( 'Free Plugins', 'wp-mail-smtp' ), ]; } $links_count = count( $links ); ?> <div class="wp-mail-smtp-footer-promotion"> <p><?php echo esc_html( $title ); ?></p> <ul class="wp-mail-smtp-footer-promotion-links"> <?php foreach ( $links as $key => $item ) : ?> <li> <?php $attrs = 'href="' . esc_url( $item['url'] ) . '"'; if ( isset( $item['target'] ) ) { $attrs .= ' target="' . esc_attr( $item['target'] ) . '"'; $attrs .= ' rel="noopener noreferrer"'; } $text = esc_html( $item['text'] ); $divider = $links_count !== $key + 1 ? '<span>/</span>' : ''; printf( '<a %1$s>%2$s</a>%3$s', // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped $attrs, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped $text, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped $divider ); ?> </li> <?php endforeach; ?> </ul> <ul class="wp-mail-smtp-footer-promotion-social"> <li> <a href="https://www.facebook.com/wpmailsmtp" target="_blank" rel="noopener noreferrer"> <svg width="16" height="16" aria-hidden="true"> <path fill="#A7AAAD" d="M16 8.05A8.02 8.02 0 0 0 8 0C3.58 0 0 3.6 0 8.05A8 8 0 0 0 6.74 16v-5.61H4.71V8.05h2.03V6.3c0-2.02 1.2-3.15 3-3.15.9 0 1.8.16 1.8.16v1.98h-1c-1 0-1.31.62-1.31 1.27v1.49h2.22l-.35 2.34H9.23V16A8.02 8.02 0 0 0 16 8.05Z"/> </svg> <span class="screen-reader-text"><?php echo esc_html( 'Facebook' ); ?></span> </a> </li> <li> <a href="https://x.com/wpmailsmtp" target="_blank" rel="noopener noreferrer"> <svg width="17" height="17" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> <path fill="#A7AAAD" d="M389.2 48h70.6L305.6 224.2 487 464H345L233.7 318.6 106.5 464H35.8L200.7 275.5 26.8 48H172.4L272.9 180.9 389.2 48zM364.4 421.8h39.1L151.1 88h-42L364.4 421.8z"/> </svg> <span class="screen-reader-text"><?php echo esc_html( 'X' ); ?></span> </a> </li> <li> <a href="https://youtube.com/playlist?list=PLt2XcSO7dFmCUMO0ky46Od6U2oSaiNodP" target="_blank" rel="noopener noreferrer"> <svg width="17" height="16" aria-hidden="true"> <path fill="#A7AAAD" d="M16.63 3.9a2.12 2.12 0 0 0-1.5-1.52C13.8 2 8.53 2 8.53 2s-5.32 0-6.66.38c-.71.18-1.3.78-1.49 1.53C0 5.2 0 8.03 0 8.03s0 2.78.37 4.13c.19.75.78 1.3 1.5 1.5C3.2 14 8.51 14 8.51 14s5.28 0 6.62-.34c.71-.2 1.3-.75 1.49-1.5.37-1.35.37-4.13.37-4.13s0-2.81-.37-4.12Zm-9.85 6.66V5.5l4.4 2.53-4.4 2.53Z"/> </svg> <span class="screen-reader-text"><?php echo esc_html( 'YouTube' ); ?></span> </a> </li> </ul> </div> <?php } /** * Display the plugin version in the footer of our plugin pages. * * @since 3.10.0 * * @param string $text Text of the footer. */ public function display_update_footer( $text ) { if ( $this->is_admin_page() ) { return 'WP Mail SMTP ' . WPMS_PLUGIN_VER; } return $text; } } PageInterface.php 0000644 00000001047 15252526345 0007765 0 ustar 00 <?php namespace WPMailSMTP\Admin; /** * Class PageInterface defines what should be in each page class. * * @since 1.0.0 */ interface PageInterface { /** * URL to a tab. * * @since 1.0.0 * * @return string */ public function get_link(); /** * Title of a tab. * * @since 1.0.0 * * @return string */ public function get_title(); /** * Link label of a tab. * * @since 1.0.0 * * @return string */ public function get_label(); /** * Tab content. * * @since 1.0.0 */ public function display(); } WooCommerceActiveLayerEducation.php 0000644 00000026662 15252526345 0013506 0 ustar 00 <?php namespace WPMailSMTP\Admin; use WPMailSMTP\WP; /** * ActiveLayer education section on the WooCommerce Accounts & Privacy settings tab. * * Renders a WooCommerce-native card after the account settings block that helps * store owners install, activate, and connect the free ActiveLayer anti-spam * plugin. Install and activation reuse the shared plugin installer exposed by * the wp_mail_smtp_ajax dispatcher (Pages\AboutTab), so no install plumbing is * duplicated here. * * @since 4.9.0 */ class WooCommerceActiveLayerEducation { /** * ActiveLayer plugin basename (folder/file), used for install and activation checks. * * @since 4.9.0 */ const PLUGIN_BASENAME = 'activelayer-anti-spam-spam-protection-for-forms-comments/activelayer-anti-spam-spam-protection-for-forms-comments.php'; /** * ActiveLayer WordPress.org download URL. Whitelisted in Pages\AboutTab::get_am_plugins(). * * @since 4.9.0 */ const DOWNLOAD_URL = 'https://downloads.wordpress.org/plugin/activelayer-anti-spam-spam-protection-for-forms-comments.zip'; /** * ActiveLayer WordPress.org plugin page (manual fallback). * * @since 4.9.0 */ const WPORG_URL = 'https://wordpress.org/plugins/activelayer-anti-spam-spam-protection-for-forms-comments/'; /** * ActiveLayer settings page slug, the connect-account CTA target. * * @since 4.9.0 */ const SETTINGS_PAGE = 'activelayer-settings'; /** * ActiveLayer integrations page slug, the connected-state link target. * * @since 4.9.0 */ const INTEGRATIONS_PAGE = 'activelayer-integrations'; /** * User meta key persisting the per-user dismissal. * * @since 4.9.0 */ const DISMISS_META = 'wp_mail_smtp_activelayer_wc_education_dismissed'; /** * Register hooks. * * @since 4.9.0 */ public function hooks() { add_action( 'woocommerce_settings_account_registration_options_after', [ $this, 'output_section' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] ); add_action( 'wp_ajax_wp_mail_smtp_activelayer_wc_dismiss', [ $this, 'ajax_dismiss' ] ); } /** * Whether the ActiveLayer plugin is active. * * @since 4.9.0 * * @return bool */ private function is_activelayer_active() { if ( ! function_exists( 'is_plugin_active' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } return is_plugin_active( self::PLUGIN_BASENAME ); } /** * Whether the ActiveLayer plugin is installed (present, active or not). * * @since 4.9.0 * * @return bool */ private function is_activelayer_installed() { if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } return array_key_exists( self::PLUGIN_BASENAME, get_plugins() ); } /** * Whether the current user can install plugins on this site. * * @since 4.9.0 * * @return bool */ private function can_install() { return current_user_can( 'install_plugins' ) && wp_is_file_mod_allowed( 'wp_mail_smtp_can_install' ); } /** * Whether ActiveLayer has a validated API key connected. * * Mirrors ActiveLayer's own validation checks via cheap option reads, * without booting any ActiveLayer classes. * * @since 4.9.0 * * @return bool */ private function is_api_key_connected() { $settings = get_option( 'activelayer_global_settings', [] ); $api_key = ''; if ( is_array( $settings ) && isset( $settings['api_key'] ) && is_string( $settings['api_key'] ) ) { $api_key = trim( $settings['api_key'] ); } if ( empty( $api_key ) ) { return false; } $validation = get_option( 'activelayer_api_key_validated', [] ); return is_array( $validation ) && ! empty( $validation['is_valid'] ) && ! empty( $validation['key'] ) && $validation['key'] === $api_key; } /** * Absolute URL of the ActiveLayer settings page (connect-account CTA). * * @since 4.9.0 * * @return string */ private function get_settings_url() { return admin_url( 'admin.php?page=' . self::SETTINGS_PAGE ); } /** * Absolute URL of the ActiveLayer integrations page (connected-state link). * * @since 4.9.0 * * @return string */ private function get_integrations_url() { return admin_url( 'admin.php?page=' . self::INTEGRATIONS_PAGE ); } /** * Resolve the render state for the current site and user. * * @since 4.9.0 * * @return string One of 'install', 'activate', 'goto-url', 'connect', 'connected', or '' to hide. */ private function get_state() { if ( $this->is_activelayer_active() ) { return $this->is_api_key_connected() ? 'connected' : 'connect'; } $is_installed = $this->is_activelayer_installed(); if ( ! $is_installed && $this->can_install() ) { return 'install'; } if ( $is_installed && current_user_can( 'activate_plugins' ) ) { return 'activate'; } // Admins who cannot run the installer (file mods locked) can still follow a link. if ( current_user_can( 'install_plugins' ) ) { return 'goto-url'; } // Users who cannot act at all (e.g. shop managers) get no dead-end card. return ''; } /** * Whether the section should be displayed for the current user. * * @since 4.9.0 * * @return bool */ private function should_display() { /** * Filters whether the ActiveLayer education section is rendered on the * WooCommerce Accounts & Privacy settings tab. * * @since 4.9.0 * * @param bool $should_display Whether the section should be displayed. */ if ( ! apply_filters( 'wp_mail_smtp_admin_woo_commerce_active_layer_education_should_display', true ) ) { return false; } if ( get_user_meta( get_current_user_id(), self::DISMISS_META, true ) ) { return false; } return $this->get_state() !== ''; } /** * Output the section after the WooCommerce account settings block. * * Fires on woocommerce_settings_account_registration_options_after, which * WooCommerce runs after the section's closing table tag, so free-form * markup is safe here. * * @since 4.9.0 */ public function output_section() { if ( ! $this->should_display() ) { return; } $state = $this->get_state(); ?> <section id="wpms-activelayer-wc" class="wpms-activelayer-wc" aria-labelledby="wpms-activelayer-wc-heading"> <button type="button" class="wpms-activelayer-wc__dismiss wp-mail-smtp-activelayer-wc-dismiss" aria-label="<?php echo esc_attr__( 'Dismiss this section', 'wp-mail-smtp' ); ?>"> <span aria-hidden="true">×</span> </button> <div class="wpms-activelayer-wc__icon"> <img src="<?php echo esc_url( wp_mail_smtp()->assets_url . '/images/activelayer/activelayer.svg' ); ?>" alt=""> </div> <div class="wpms-activelayer-wc__body"> <h3 id="wpms-activelayer-wc-heading" class="wpms-activelayer-wc__heading"> <?php echo esc_html__( 'Stop Fake Customer Registrations and Review Spam', 'wp-mail-smtp' ); ?> </h3> <?php $this->output_state( $state ); ?> </div> </section> <?php } /** * Output the state-dependent description and call to action. * * @since 4.9.0 * * @param string $state Render state. */ private function output_state( $state ) { if ( $state === 'connect' ) { ?> <p class="wpms-activelayer-wc__description"> <?php echo esc_html__( 'ActiveLayer is active. Connect your free account to start blocking fake registrations and review spam.', 'wp-mail-smtp' ); ?> </p> <?php if ( current_user_can( 'manage_activelayer' ) ) { ?> <button type="button" class="wpms-activelayer-wc__cta wp-mail-smtp-activelayer-button" data-action="goto-settings" data-url="<?php echo esc_url( $this->get_settings_url() ); ?>"> <?php echo esc_html__( 'Connect Your Free Account', 'wp-mail-smtp' ); ?> </button> <?php } return; } if ( $state === 'connected' ) { ?> <p class="wpms-activelayer-wc__description"> <?php echo esc_html__( 'ActiveLayer is protecting customer registration and product reviews.', 'wp-mail-smtp' ); ?> <?php if ( current_user_can( 'manage_activelayer' ) ) : ?> <a href="<?php echo esc_url( $this->get_integrations_url() ); ?>"> <?php echo esc_html__( 'View ActiveLayer Settings', 'wp-mail-smtp' ); ?> </a> <?php endif; ?> </p> <?php return; } if ( $state === 'install' ) { $action = 'install'; $button_text = esc_html__( 'Install & Activate ActiveLayer', 'wp-mail-smtp' ); $button_url = ''; } elseif ( $state === 'activate' ) { $action = 'activate'; $button_text = esc_html__( 'Activate ActiveLayer', 'wp-mail-smtp' ); $button_url = ''; } else { $action = 'goto-url'; $button_text = esc_html__( 'Get ActiveLayer', 'wp-mail-smtp' ); $button_url = self::WPORG_URL; } ?> <p class="wpms-activelayer-wc__description"> <?php echo esc_html__( 'Blocks bot signups and review spam on My Account, checkout, and product reviews. No CAPTCHA anywhere on the path to purchase. Free plugin from WordPress.org, with 1,000 free spam checks and no credit card required.', 'wp-mail-smtp' ); ?> </p> <button type="button" class="wpms-activelayer-wc__cta wp-mail-smtp-activelayer-button" data-action="<?php echo esc_attr( $action ); ?>" data-url="<?php echo esc_url( $button_url ); ?>"> <?php echo esc_html( $button_text ); ?> </button> <?php } /** * Enqueue the section assets on the WooCommerce Accounts & Privacy tab only. * * @since 4.9.0 * * @param string $hook Current admin page hook. */ public function enqueue_assets( $hook ) { if ( $hook !== 'woocommerce_page_wc-settings' ) { return; } // Read-only context detection on a GET request; no nonce applies. // phpcs:ignore WordPress.Security.NonceVerification.Recommended $tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : ''; if ( $tab !== 'account' || ! $this->should_display() ) { return; } wp_enqueue_style( 'wp-mail-smtp-activelayer-wc', wp_mail_smtp()->assets_url . '/css/smtp-activelayer-wc' . WP::asset_min() . '.css', [], WPMS_PLUGIN_VER ); wp_enqueue_script( 'wp-mail-smtp-activelayer-wc', wp_mail_smtp()->assets_url . '/js/smtp-activelayer-wc' . WP::asset_min() . '.js', [ 'jquery' ], WPMS_PLUGIN_VER, true ); wp_localize_script( 'wp-mail-smtp-activelayer-wc', 'wp_mail_smtp_activelayer_wc', [ 'ajax_url' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'wp-mail-smtp-admin' ), 'plugin' => self::PLUGIN_BASENAME, 'download_url' => self::DOWNLOAD_URL, 'settings_url' => $this->get_settings_url(), 'wporg_url' => self::WPORG_URL, 'installing' => esc_html__( 'Installing...', 'wp-mail-smtp' ), 'activating' => esc_html__( 'Activating...', 'wp-mail-smtp' ), 'goto_settings' => esc_html__( 'Connect Your Free Account', 'wp-mail-smtp' ), 'get_activelayer' => esc_html__( 'Get ActiveLayer', 'wp-mail-smtp' ), 'error_install' => esc_html__( 'Could not install ActiveLayer. Please download it from WordPress.org and install manually.', 'wp-mail-smtp' ), 'error_activate' => esc_html__( 'Could not activate ActiveLayer. Please activate it from the Plugins page.', 'wp-mail-smtp' ), ] ); } /** * AJAX: dismiss the section for the current user. * * @since 4.9.0 */ public function ajax_dismiss() { if ( check_ajax_referer( 'wp-mail-smtp-admin', 'nonce', false ) === false ) { wp_send_json_error( esc_html__( 'Could not dismiss the section. Please reload the page and try again.', 'wp-mail-smtp' ) ); } update_user_meta( get_current_user_id(), self::DISMISS_META, time() ); wp_send_json_success(); } } Education.php 0000644 00000005574 15252526345 0007214 0 ustar 00 <?php namespace WPMailSMTP\Admin; /** * WP Mail SMTP enhancements to admin pages to educate Lite users on what is available in WP Mail SMTP Pro. * * @since 2.3.0 */ class Education { /** * The dismissed notice bar user meta key. * * @since 2.3.0 */ const DISMISS_NOTICE_BAR_KEY = 'wp_mail_smtp_edu_notice_bar_dismissed'; /** * Hooks. * * @since 2.3.0 */ public function hooks() { if ( apply_filters( 'wp_mail_smtp_admin_education_notice_bar', true ) ) { add_action( 'admin_init', [ $this, 'notice_bar_init' ] ); } } /** * Notice bar init. * * @since 2.3.0 */ public function notice_bar_init() { add_action( 'wp_mail_smtp_admin_header_before', [ $this, 'notice_bar_display' ] ); add_action( 'wp_ajax_wp_mail_smtp_notice_bar_dismiss', [ $this, 'notice_bar_ajax_dismiss' ] ); } /** * Notice bar display message. * * @since 2.3.0 */ public function notice_bar_display() { // Bail if we're not on a plugin admin page. if ( ! wp_mail_smtp()->get_admin()->is_admin_page() ) { return; } $dismissed = get_user_meta( get_current_user_id(), self::DISMISS_NOTICE_BAR_KEY, true ); if ( ! empty( $dismissed ) ) { return; } $current_screen = get_current_screen(); $upgrade_utm_content = $current_screen === null ? 'Upgrade to Pro' : 'Upgrade to Pro - ' . $current_screen->base; $upgrade_utm_content = empty( $_GET['tab'] ) ? $upgrade_utm_content : $upgrade_utm_content . ' -- ' . sanitize_key( $_GET['tab'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended printf( '<div id="wp-mail-smtp-notice-bar"> <div class="wp-mail-smtp-notice-bar-container"> <span class="wp-mail-smtp-notice-bar-message">%s</span> <button type="button" class="dismiss" title="%s" /> </div> </div>', wp_kses( sprintf( /* translators: %s - WPMailSMTP.com Upgrade page URL. */ __( 'You’re using WP Mail SMTP Lite. To unlock more features, consider <a href="%1$s" target="_blank" rel="noopener noreferrer">upgrading to Pro</a> for <strong>%2$s OFF</strong>.', 'wp-mail-smtp' ), wp_mail_smtp()->get_upgrade_link( [ 'medium' => 'notice-bar', 'content' => $upgrade_utm_content ] ), // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound, '$50' ), [ 'a' => [ 'href' => [], 'rel' => [], 'target' => [], ], 'strong' => [], ] ), esc_attr__( 'Dismiss this message.', 'wp-mail-smtp' ), ); } /** * Ajax handler for dismissing notices. * * @since 2.3.0 */ public function notice_bar_ajax_dismiss() { // Run a security check. check_ajax_referer( 'wp-mail-smtp-admin', 'nonce' ); // Check for permissions. if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_options() ) ) { wp_send_json_error(); } update_user_meta( get_current_user_id(), self::DISMISS_NOTICE_BAR_KEY, time() ); wp_send_json_success(); } } PageAbstract.php 0000644 00000006146 15252526345 0007635 0 ustar 00 <?php namespace WPMailSMTP\Admin; use WPMailSMTP\WP; /** * Class PageAbstract. * * @since 1.0.0 */ abstract class PageAbstract implements PageInterface { /** * @var string Slug of a tab. */ protected $slug; /** * Tab priority. * * @since 2.8.0 * * @var int */ protected $priority = 999; /** * Tab parent page. * * @since 2.8.0 * * @var ParentPageAbstract */ protected $parent_page = null; /** * Constructor. * * @since 2.8.0 * * @param ParentPageAbstract $parent_page Tab parent page. */ public function __construct( $parent_page = null ) { $this->parent_page = $parent_page; } /** * @inheritdoc */ public function get_link() { $page = Area::SLUG; if ( $this->parent_page !== null ) { $page .= '-' . $this->parent_page->get_slug(); } return add_query_arg( 'tab', $this->slug, WP::admin_url( 'admin.php?page=' . $page ) ); } /** * Title of a tab. * * @since 3.7.0 * * @return string */ public function get_title() { return $this->get_label(); } /** * Get tab slug. * * @since 2.8.0 * * @return string */ public function get_slug() { return $this->slug; } /** * Get tab priority. * * @since 2.8.0 * * @return int */ public function get_priority() { return $this->priority; } /** * Get parent page. * * @since 2.8.0 * * @return ParentPageAbstract */ public function get_parent_page() { return $this->parent_page; } /** * Get parent page slug. * * @since 3.0.0 * * @return string */ public function get_parent_slug() { if ( is_null( $this->parent_page ) ) { return ''; } return $this->parent_page->get_slug(); } /** * Register tab related hooks. * * @since 2.8.0 */ public function hooks() {} /** * Register tab related ajax hooks. * * @since 3.0.0 */ public function ajax() {} /** * Process tab form submission ($_POST ). * * @since 1.0.0 * * @param array $data $_POST data specific for the plugin. */ public function process_post( $data ) {} /** * Process tab & mailer specific Auth actions. * * @since 1.0.0 */ public function process_auth() {} /** * Print the nonce field for a specific tab. * * @since 1.0.0 */ public function wp_nonce_field() { wp_nonce_field( Area::SLUG . '-' . $this->slug ); } /** * Make sure that a user was referred from plugin admin page. * To avoid security problems. * * @since 1.0.0 */ public function check_admin_referer() { check_admin_referer( Area::SLUG . '-' . $this->slug ); } /** * Save button to be reused on other tabs. * * @since 1.5.0 */ public function display_save_btn() { ?> <p class="wp-mail-smtp-submit"> <button type="submit" class="wp-mail-smtp-btn wp-mail-smtp-btn-md wp-mail-smtp-btn-orange"> <?php esc_html_e( 'Save Settings', 'wp-mail-smtp' ); ?> </button> </p> <?php $this->post_form_hidden_field(); } /** * Form hidden field for identifying plugin POST requests. * * @since 2.9.0 */ public function post_form_hidden_field() { echo '<input type="hidden" name="wp-mail-smtp-post" value="1">'; } } PluginsInstallSkin.php 0000644 00000002301 15252526345 0011057 0 ustar 00 <?php namespace WPMailSMTP\Admin; use Automatic_Upgrader_Skin; /** * WordPress class extended for on-the-fly plugin installations. * * @since 1.5.0 * @since 1.7.1 Removed feedback() method override to be compatible with PHP5.3+ and WP5.3. * @since 3.11.0 Updated to extend Automatic_Upgrader_Skin. */ class PluginsInstallSkin extends Automatic_Upgrader_Skin { /** * Empty out the header of its HTML content and only check to see if it has * been performed or not. * * @since 1.5.0 */ public function header() { } /** * Empty out the footer of its HTML contents. * * @since 1.5.0 */ public function footer() { } /** * Instead of outputting HTML for errors, json_encode the errors and send them * back to the Ajax script for processing. * * @since 1.5.0 * * @param array $errors Array of errors with the install process. */ public function error( $errors ) { if ( ! empty( $errors ) ) { wp_send_json_error( $errors ); } } /** * Empty out JavaScript output that calls function to decrement the update counts. * * @since 1.5.0 * * @param string $type Type of update count to decrement. */ public function decrement_update_count( $type ) { } } DebugEvents/EventsCollection.php 0000644 00000021364 15252526345 0012767 0 ustar 00 <?php namespace WPMailSMTP\Admin\DebugEvents; use WPMailSMTP\WP; /** * Debug Events Collection. * * @since 3.0.0 */ class EventsCollection implements \Countable, \Iterator { /** * Default number of log entries per page. * * @since 3.0.0 * * @var int */ const PER_PAGE = 10; /** * Number of log entries per page. * * @since 3.0.0 * * @var int */ public static $per_page; /** * List of all Event instances. * * @since 3.0.0 * * @var array */ private $list = []; /** * List of current collection instance parameters. * * @since 3.0.0 * * @var array */ private $params; /** * Used for \Iterator when iterating through Queue in loops. * * @since 3.0.0 * * @var int */ private $iterator_position = 0; /** * Collection constructor. * $events = new EventsCollection( [ 'type' => 0 ] ); * * @since 3.0.0 * * @param array $params The events collection parameters. */ public function __construct( array $params = [] ) { $this->set_per_page(); $this->params = $this->process_params( $params ); } /** * Set the per page attribute to the screen options value. * * @since 3.0.0 */ protected function set_per_page() { $per_page = (int) get_user_meta( get_current_user_id(), 'wp_mail_smtp_debug_events_per_page', true ); if ( $per_page < 1 ) { $per_page = self::PER_PAGE; } self::$per_page = $per_page; } /** * Verify, sanitize, and populate with default values * all the passed parameters, which participate in DB queries. * * @since 3.0.0 * * @param array $params The events collection parameters. * * @return array */ public function process_params( $params ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded $params = (array) $params; $processed = []; /* * WHERE. */ // Single ID. if ( ! empty( $params['id'] ) ) { $processed['id'] = (int) $params['id']; } // Multiple IDs. if ( ! empty( $params['ids'] ) && is_array( $params['ids'] ) ) { $processed['ids'] = array_unique( array_filter( array_map( 'intval', array_values( $params['ids'] ) ) ) ); } // Type. if ( isset( $params['type'] ) && in_array( $params['type'], array_keys( Event::get_types() ), true ) ) { $processed['type'] = (int) $params['type']; } // Search. if ( ! empty( $params['search'] ) ) { $processed['search'] = sanitize_text_field( $params['search'] ); } /* * LIMIT. */ if ( ! empty( $params['offset'] ) ) { $processed['offset'] = (int) $params['offset']; } if ( ! empty( $params['per_page'] ) ) { $processed['per_page'] = (int) $params['per_page']; } /* * Sent date. */ if ( ! empty( $params['date'] ) ) { if ( is_string( $params['date'] ) ) { $params['date'] = array_fill( 0, 2, $params['date'] ); } elseif ( is_array( $params['date'] ) && count( $params['date'] ) === 1 ) { $params['date'] = array_fill( 0, 2, $params['date'][0] ); } // We pass array and treat it as a range from:to. if ( is_array( $params['date'] ) && count( $params['date'] ) === 2 ) { $date_start = WP::get_day_period_date( 'start_of_day', strtotime( $params['date'][0] ), 'Y-m-d H:i:s', true ); $date_end = WP::get_day_period_date( 'end_of_day', strtotime( $params['date'][1] ), 'Y-m-d H:i:s', true ); if ( ! empty( $date_start ) && ! empty( $date_end ) ) { $processed['date'] = [ $date_start, $date_end ]; } } } // Merge missing values with defaults. return wp_parse_args( $processed, $this->get_default_params() ); } /** * Get the list of default params for a usual query. * * @since 3.0.0 * * @return array */ protected function get_default_params() { return [ 'offset' => 0, 'per_page' => self::$per_page, 'order' => 'DESC', 'orderby' => 'id', 'search' => '', ]; } /** * Get the SQL-ready string of WHERE part for a query. * * @since 3.0.0 * * @return string */ private function build_where() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh global $wpdb; $where = [ '1=1' ]; // Shortcut single ID or multiple IDs. if ( ! empty( $this->params['id'] ) || ! empty( $this->params['ids'] ) ) { if ( ! empty( $this->params['id'] ) ) { $where[] = $wpdb->prepare( 'id = %d', $this->params['id'] ); } elseif ( ! empty( $this->params['ids'] ) ) { $where[] = 'id IN (' . implode( ',', $this->params['ids'] ) . ')'; } // When some ID(s) defined - we should ignore all other possible filtering options. return implode( ' AND ', $where ); } // Type. if ( isset( $this->params['type'] ) ) { $where[] = $wpdb->prepare( 'event_type = %d', $this->params['type'] ); } // Search. if ( ! empty( $this->params['search'] ) ) { $where[] = '(' . $wpdb->prepare( 'content LIKE %s', '%' . $wpdb->esc_like( $this->params['search'] ) . '%' ) . ' OR ' . $wpdb->prepare( 'initiator LIKE %s', '%' . $wpdb->esc_like( $this->params['search'] ) . '%' ) . ')'; } // Sent date. if ( ! empty( $this->params['date'] ) && is_array( $this->params['date'] ) && count( $this->params['date'] ) === 2 ) { $where[] = $wpdb->prepare( '( created_at >= %s AND created_at <= %s )', $this->params['date'][0], $this->params['date'][1] ); } return implode( ' AND ', $where ); } /** * Get the SQL-ready string of ORDER part for a query. * Order is always in the params, as per our defaults. * * @since 3.0.0 * * @return string */ private function build_order() { return 'ORDER BY ' . $this->params['orderby'] . ' ' . $this->params['order']; } /** * Get the SQL-ready string of LIMIT part for a query. * Limit is always in the params, as per our defaults. * * @since 3.0.0 * * @return string */ private function build_limit() { return 'LIMIT ' . $this->params['offset'] . ', ' . $this->params['per_page']; } /** * Count the number of DB records according to filters. * Do not retrieve actual records. * * @since 3.0.0 * * @return int */ public function get_count() { $table = DebugEvents::get_table_name(); $where = $this->build_where(); // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared return (int) WP::wpdb()->get_var( "SELECT COUNT(id) FROM $table WHERE {$where}" ); // phpcs:enable } /** * Get the list of DB records. * You can either use array returned there OR iterate over the whole object, * as it implements Iterator interface. * * @since 3.0.0 * * @return EventsCollection */ public function get() { $table = DebugEvents::get_table_name(); $where = $this->build_where(); $limit = $this->build_limit(); $order = $this->build_order(); // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared $data = WP::wpdb()->get_results( "SELECT * FROM $table WHERE {$where} {$order} {$limit}" ); // phpcs:enable if ( ! empty( $data ) ) { // As we got raw data we need to convert each row to Event. foreach ( $data as $row ) { $this->list[] = new Event( $row ); } } return $this; } /********************************************************************************************* * ****************************** \Counter interface method. ********************************* *********************************************************************************************/ /** * Count number of Record in a Queue. * * @since 3.0.0 * * @return int */ #[\ReturnTypeWillChange] public function count() { return count( $this->list ); } /********************************************************************************************* * ****************************** \Iterator interface methods. ******************************* *********************************************************************************************/ /** * Rewind the Iterator to the first element. * * @since 3.0.0 */ #[\ReturnTypeWillChange] public function rewind() { $this->iterator_position = 0; } /** * Return the current element. * * @since 3.0.0 * * @return Event|null Return null when no items in collection. */ #[\ReturnTypeWillChange] public function current() { return $this->valid() ? $this->list[ $this->iterator_position ] : null; } /** * Return the key of the current element. * * @since 3.0.0 * * @return int */ #[\ReturnTypeWillChange] public function key() { return $this->iterator_position; } /** * Move forward to next element. * * @since 3.0.0 */ #[\ReturnTypeWillChange] public function next() { ++ $this->iterator_position; } /** * Checks if current position is valid. * * @since 3.0.0 * * @return bool */ #[\ReturnTypeWillChange] public function valid() { return isset( $this->list[ $this->iterator_position ] ); } } DebugEvents/Table.php 0000644 00000033666 15252526345 0010546 0 ustar 00 <?php namespace WPMailSMTP\Admin\DebugEvents; use WPMailSMTP\Helpers\Helpers; if ( ! class_exists( 'WP_List_Table', false ) ) { require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php'; } /** * Class Table that displays the list of debug events. * * @since 3.0.0 */ class Table extends \WP_List_Table { /** * Number of debug events by different types. * * @since 3.0.0 * * @var array */ public $counts; /** * Set up a constructor that references the parent constructor. * Using the parent reference to set some default configs. * * @since 3.0.0 */ public function __construct() { // Set parent defaults. parent::__construct( [ 'singular' => 'event', 'plural' => 'events', 'ajax' => false, ] ); // Include polyfill if mbstring PHP extension is not enabled. if ( ! function_exists( 'mb_substr' ) || ! function_exists( 'mb_strlen' ) ) { Helpers::include_mbstring_polyfill(); } } /** * Get the debug event types for filtering purpose. * * @since 3.0.0 * * @return array Associative array of debug event types StatusCode=>Name. */ public function get_types() { return Event::get_types(); } /** * Get the items counts for various types of debug logs. * * @since 3.0.0 */ public function get_counts() { $this->counts = []; // Base params with applied filters. $base_params = $this->get_filters_query_params(); $total_params = $base_params; unset( $total_params['type'] ); $this->counts['total'] = ( new EventsCollection( $total_params ) )->get_count(); foreach ( $this->get_types() as $type => $name ) { $collection = new EventsCollection( array_merge( $base_params, [ 'type' => $type ] ) ); $this->counts[ 'type_' . $type ] = $collection->get_count(); } /** * Filters items counts by various types of debug events. * * @since 3.0.0 * * @param array $counts { * Items counts by types. * * @type integer $total Total items count. * @type integer $status_{$type_key} Items count by type. * } */ $this->counts = apply_filters( 'wp_mail_smtp_admin_debug_events_table_get_counts', $this->counts ); } /** * Retrieve the view types. * * @since 3.0.0 */ public function get_views() { $base_url = $this->get_filters_base_url(); $current_type = $this->get_filtered_types(); $views = []; $views['all'] = sprintf( '<a href="%1$s" %2$s>%3$s <span class="count">(%4$d)</span></a>', esc_url( remove_query_arg( 'type', $base_url ) ), $current_type === false ? 'class="current"' : '', esc_html__( 'All', 'wp-mail-smtp' ), intval( $this->counts['total'] ) ); foreach ( $this->get_types() as $type => $type_label ) { $count = intval( $this->counts[ 'type_' . $type ] ); // Skipping types with no events. if ( $count === 0 && $current_type !== $type ) { continue; } $views[ $type ] = sprintf( '<a href="%1$s" %2$s>%3$s <span class="count">(%4$d)</span></a>', esc_url( add_query_arg( 'type', $type, $base_url ) ), $current_type === $type ? 'class="current"' : '', esc_html( $type_label ), $count ); } /** * Filters debug event item views. * * @since 3.0.0 * * @param array $views { * Debug event items views by types. * * @type string $all Total items view. * @type integer $status_key Items views by type. * } * @param array $counts { * Items counts by types. * * @type integer $total Total items count. * @type integer $status_{$status_key} Items count by types. * } */ return apply_filters( 'wp_mail_smtp_admin_debug_events_table_get_views', $views, $this->counts ); } /** * Define the table columns. * * @since 3.0.0 * * @return array Associative array of slug=>Name columns data. */ public function get_columns() { return [ 'event' => esc_html__( 'Event', 'wp-mail-smtp' ), 'type' => esc_html__( 'Type', 'wp-mail-smtp' ), 'content' => esc_html__( 'Content', 'wp-mail-smtp' ), 'initiator' => esc_html__( 'Source', 'wp-mail-smtp' ), 'created_at' => esc_html__( 'Date', 'wp-mail-smtp' ), ]; } /** * Display the main event title with a link to open event details. * * @since 3.0.0 * * @param Event $item Event object. * * @return string */ public function column_event( $item ) { return '<strong>' . '<a href="#" data-event-id="' . esc_attr( $item->get_id() ) . '"' . ' class="js-wp-mail-smtp-debug-event-preview row-title event-preview" title="' . esc_attr( $item->get_title() ) . '">' . esc_html( $item->get_title() ) . '</a>' . '</strong>'; } /** * Display event's type. * * @since 3.0.0 * * @param Event $item Event object. * * @return string */ public function column_type( $item ) { return esc_html( $item->get_type_name() ); } /** * Display event's content. * * @since 3.0.0 * * @param Event $item Event object. * * @return string */ public function column_content( $item ) { $content = $item->get_content(); if ( mb_strlen( $content ) > 100 ) { $content = mb_substr( $content, 0, 100 ) . '...'; } return wp_kses_post( $content ); } /** * Display event's wp_mail initiator. * * @since 3.0.0 * * @param Event $item Event object. * * @return string */ public function column_initiator( $item ) { return esc_html( $item->get_initiator() ); } /** * Display event's created date. * * @since 3.0.0 * * @param Event $item Event object. * * @return string */ public function column_created_at( $item ) { return $item->get_created_at_formatted(); } /** * Return type filter value or FALSE. * * @since 3.0.0 * * @return bool|integer */ public function get_filtered_types() { if ( ! isset( $_REQUEST['type'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return false; } return intval( $_REQUEST['type'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended } /** * Return date filter value or FALSE. * * @since 3.0.0 * * @return bool|array */ public function get_filtered_dates() { if ( empty( $_REQUEST['date'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return false; } $dates = (array) explode( ' - ', sanitize_text_field( wp_unslash( $_REQUEST['date'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended return array_map( 'sanitize_text_field', $dates ); } /** * Return search filter values or FALSE. * * @since 3.0.0 * * @return bool|array */ public function get_filtered_search() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( empty( $_REQUEST['search'] ) ) { return false; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended return sanitize_text_field( wp_unslash( $_REQUEST['search'] ) ); } /** * Whether the event log is filtered or not. * * @since 3.0.0 * * @return bool */ public function is_filtered() { $is_filtered = false; if ( $this->get_filtered_search() !== false || $this->get_filtered_dates() !== false || $this->get_filtered_types() !== false ) { $is_filtered = true; } return $is_filtered; } /** * Get current filters query parameters. * * @since 3.0.0 * * @return array */ public function get_filters_query_params() { $params = [ 'search' => $this->get_filtered_search(), 'type' => $this->get_filtered_types(), 'date' => $this->get_filtered_dates(), ]; return array_filter( $params, function ( $v ) { return $v !== false; } ); } /** * Get current filters base url. * * @since 3.0.0 * * @return string */ public function get_filters_base_url() { $base_url = DebugEvents::get_page_url(); $filters_params = $this->get_filters_query_params(); if ( isset( $filters_params['search'] ) ) { $base_url = add_query_arg( 'search', $filters_params['search'], $base_url ); } if ( isset( $filters_params['type'] ) ) { $base_url = add_query_arg( 'type', $filters_params['type'], $base_url ); } if ( isset( $filters_params['date'] ) ) { $base_url = add_query_arg( 'date', implode( ' - ', $filters_params['date'] ), $base_url ); } return $base_url; } /** * Get the data, prepare pagination, process bulk actions. * Prepare columns for display. * * @since 3.0.0 */ public function prepare_items() { // Retrieve count. $this->get_counts(); // Prepare all the params to pass to our Collection. All sanitization is done in that class. $params = $this->get_filters_query_params(); // Total amount for pagination with WHERE clause - super quick count DB request. $total_items = ( new EventsCollection( $params ) )->get_count(); // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( ! empty( $_REQUEST['orderby'] ) && in_array( $_REQUEST['orderby'], [ 'event', 'type', 'content', 'initiator', 'created_at' ], true ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $params['orderby'] = sanitize_key( $_REQUEST['orderby'] ); } // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( ! empty( $_REQUEST['order'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $params['order'] = strtoupper( sanitize_text_field( wp_unslash( $_REQUEST['order'] ) ) ) === 'DESC' ? 'DESC' : 'ASC'; } $params['offset'] = ( $this->get_pagenum() - 1 ) * EventsCollection::$per_page; // Get the data from the DB using parameters defined above. $collection = new EventsCollection( $params ); $this->items = $collection->get(); /* * Register our pagination options & calculations. */ $this->set_pagination_args( [ 'total_items' => $total_items, 'per_page' => EventsCollection::$per_page, ] ); } /** * Display the search box. * * @since 1.7.0 * * @param string $text The 'submit' button label. * @param string $input_id ID attribute value for the search input field. */ public function search_box( $text, $input_id ) { if ( ! $this->is_filtered() && ! $this->has_items() ) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended $search = ! empty( $_REQUEST['search'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['search'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( ! empty( $_REQUEST['orderby'] ) && in_array( $_REQUEST['orderby'], [ 'event', 'type', 'content', 'initiator', 'created_at' ], true ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $order_by = sanitize_text_field( wp_unslash( $_REQUEST['orderby'] ) ); echo '<input type="hidden" name="orderby" value="' . esc_attr( $order_by ) . '" />'; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( ! empty( $_REQUEST['order'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $order = strtoupper( sanitize_text_field( wp_unslash( $_REQUEST['order'] ) ) ) === 'DESC' ? 'DESC' : 'ASC'; echo '<input type="hidden" name="order" value="' . esc_attr( $order ) . '" />'; } ?> <p class="search-box"> <label class="screen-reader-text" for="<?php echo esc_attr( $input_id ); ?>"><?php echo esc_html( $text ); ?>:</label> <input type="search" id="<?php echo esc_attr( $input_id ); ?>" name="search" value="<?php echo esc_attr( $search ); ?>" /> <?php submit_button( $text, '', '', false, [ 'id' => 'search-submit' ] ); ?> </p> <?php } /** * Whether the table has items to display or not. * * @since 3.0.0 * * @return bool */ public function has_items() { return count( $this->items ) > 0; } /** * Message to be displayed when there are no items. * * @since 3.0.0 */ public function no_items() { if ( $this->is_filtered() ) { esc_html_e( 'No events found.', 'wp-mail-smtp' ); } else { esc_html_e( 'No events have been logged for now.', 'wp-mail-smtp' ); } } /** * Displays the table. * * @since 3.0.0 */ public function display() { $this->_column_headers = [ $this->get_columns(), [], [] ]; parent::display(); } /** * Hide the tablenav if there are no items in the table. * And remove the bulk action nonce and code. * * @since 3.0.0 * * @param string $which Which tablenav: top or bottom. */ protected function display_tablenav( $which ) { if ( ! $this->has_items() ) { return; } ?> <div class="tablenav <?php echo esc_attr( $which ); ?>"> <?php $this->extra_tablenav( $which ); $this->pagination( $which ); ?> <br class="clear" /> </div> <?php } /** * Extra controls to be displayed between bulk actions and pagination. * * @since 3.0.0 * * @param string $which Which tablenav: top or bottom. */ protected function extra_tablenav( $which ) { if ( $which !== 'top' || ! $this->has_items() ) { return; } $date = $this->get_filtered_dates() !== false ? implode( ' - ', $this->get_filtered_dates() ) : ''; ?> <div class="alignleft actions wp-mail-smtp-filter-date"> <input type="text" name="date" class="regular-text wp-mail-smtp-filter-date-selector wp-mail-smtp-filter-date__control" placeholder="<?php esc_attr_e( 'Select a date range', 'wp-mail-smtp' ); ?>" value="<?php echo esc_attr( $date ); ?>"> <button type="submit" name="action" value="filter_date" class="button wp-mail-smtp-filter-date__btn"> <?php esc_html_e( 'Filter', 'wp-mail-smtp' ); ?> </button> </div> <?php if ( current_user_can( wp_mail_smtp()->get_capability_manage_options() ) ) { wp_nonce_field( 'wp_mail_smtp_debug_events', 'wp-mail-smtp-debug-events-nonce', false ); printf( '<button id="wp-mail-smtp-delete-all-debug-events-button" type="button" class="button">%s</button>', esc_html__( 'Delete All Events', 'wp-mail-smtp' ) ); } } /** * Get the name of the primary column. * Important for the mobile view. * * @since 3.0.0 * * @return string The name of the primary column. */ protected function get_primary_column_name() { return 'event'; } } DebugEvents/Event.php 0000644 00000027672 15252526345 0010600 0 ustar 00 <?php namespace WPMailSMTP\Admin\DebugEvents; use WPMailSMTP\WP; /** * Debug Event class. * * @since 3.0.0 */ class Event { /** * This is an error event. * * @since 3.0.0 */ const TYPE_ERROR = 0; /** * This is a debug event. * * @since 3.0.0 */ const TYPE_DEBUG = 1; /** * The event's ID. * * @since 3.0.0 * * @var int */ protected $id = 0; /** * The event's content. * * @since 3.0.0 * * @var string */ protected $content = ''; /** * The event's initiator - who called the `wp_mail` function? * JSON encoded string. * * @since 3.0.0 * * @var string */ protected $initiator = ''; /** * The event's type. * * @since 3.0.0 * * @var int */ protected $event_type = 0; /** * The date and time when this event was created. * * @since 3.0.0 * * @var \DateTime */ protected $created_at; /** * Retrieve a particular event when constructing the object. * * @since 3.0.0 * * @param int|object $id_or_row The event ID or object with event attributes. */ public function __construct( $id_or_row = null ) { $this->populate_event( $id_or_row ); } /** * Get and prepare the event data. * * @since 3.0.0 * * @param int|object $id_or_row The event ID or object with event attributes. */ private function populate_event( $id_or_row ) { $event = null; if ( is_numeric( $id_or_row ) ) { // Get by ID. $collection = new EventsCollection( [ 'id' => (int) $id_or_row ] ); $events = $collection->get(); if ( $events->valid() ) { $event = $events->current(); } } elseif ( is_object( $id_or_row ) && isset( $id_or_row->id, $id_or_row->content, $id_or_row->initiator, $id_or_row->event_type, $id_or_row->created_at ) ) { $event = $id_or_row; } if ( $event !== null ) { foreach ( get_object_vars( $event ) as $key => $value ) { $this->{$key} = $value; } } } /** * Event ID as per our DB table. * * @since 3.0.0 * * @return int */ public function get_id() { return (int) $this->id; } /** * Get the event title. * * @since 3.0.0 * * @return string */ public function get_title() { /* translators: %d the event ID. */ return sprintf( esc_html__( 'Event #%d', 'wp-mail-smtp' ), $this->get_id() ); } /** * Get the content of the event. * * @since 3.0.0 * * @return string */ public function get_content() { return $this->content; } /** * Get the event's type. * * @since 3.0.0 * * @return int */ public function get_type() { return (int) $this->event_type; } /** * Get the list of all event types. * * @since 3.0.0 * * @return array */ public static function get_types() { return [ self::TYPE_ERROR => esc_html__( 'Error', 'wp-mail-smtp' ), self::TYPE_DEBUG => esc_html__( 'Debug', 'wp-mail-smtp' ), ]; } /** * Get human readable type name. * * @since 3.0.0 * * @return string */ public function get_type_name() { $types = self::get_types(); return isset( $types[ $this->get_type() ] ) ? $types[ $this->get_type() ] : ''; } /** * Get the date/time when this event was created. * * @since 3.0.0 * * @throws \Exception Emits exception on incorrect date. * * @return \DateTime */ public function get_created_at() { $timezone = new \DateTimeZone( 'UTC' ); $date = false; if ( ! empty( $this->created_at ) ) { $date = \DateTime::createFromFormat( WP::datetime_mysql_format(), $this->created_at, $timezone ); } if ( $date === false ) { $date = new \DateTime( 'now', $timezone ); } return $date; } /** * Get the date/time when this event was created in a nicely formatted string. * * @since 3.0.0 * * @return string */ public function get_created_at_formatted() { try { $date = $this->get_created_at(); } catch ( \Exception $e ) { $date = null; } if ( empty( $date ) ) { return esc_html__( 'N/A', 'wp-mail-smtp' ); } return esc_html( date_i18n( WP::datetime_format(), strtotime( get_date_from_gmt( $date->format( WP::datetime_mysql_format() ) ) ) ) ); } /** * Get the event's initiator raw data. * Who called the `wp_mail` function? * * @since 3.0.0 * * @return array */ public function get_initiator_raw() { return json_decode( $this->initiator, true ); } /** * Get the event's initiator name. * Which plugin/theme (or WP core) called the `wp_mail` function? * * @since 3.0.0 * * @return string */ public function get_initiator() { $initiator = (array) $this->get_initiator_raw(); if ( empty( $initiator['file'] ) ) { return ''; } return WP::get_initiator_name( $initiator['file'] ); } /** * Get the event's initiator file path. * * @since 3.0.0 * * @return string */ public function get_initiator_file_path() { $initiator = (array) $this->get_initiator_raw(); if ( empty( $initiator['file'] ) ) { return ''; } return $initiator['file']; } /** * Get the event's initiator file line. * * @since 3.0.0 * * @return string */ public function get_initiator_file_line() { $initiator = (array) $this->get_initiator_raw(); if ( empty( $initiator['line'] ) ) { return ''; } return $initiator['line']; } /** * Get the event's initiator backtrace. * * @since 3.6.0 * * @return array */ private function get_initiator_backtrace() { $initiator = (array) $this->get_initiator_raw(); if ( empty( $initiator['backtrace'] ) ) { return []; } return $initiator['backtrace']; } /** * Get the event preview HTML. * * @since 3.0.0 * * @return string */ public function get_details_html() { $initiator = $this->get_initiator(); $initiator_backtrace = $this->get_initiator_backtrace(); ob_start(); ?> <div class="wp-mail-smtp-debug-event-preview"> <div class="wp-mail-smtp-debug-event-preview-subtitle"> <span><?php esc_html_e( 'Debug Event Details', 'wp-mail-smtp' ); ?></span> </div> <div class="wp-mail-smtp-debug-event-row wp-mail-smtp-debug-event-preview-type"> <span class="debug-event-label"><?php esc_html_e( 'Type', 'wp-mail-smtp' ); ?></span> <span class="debug-event-value"><?php echo esc_html( $this->get_type_name() ); ?></span> </div> <div class="wp-mail-smtp-debug-event-row wp-mail-smtp-debug-event-preview-date"> <span class="debug-event-label"><?php esc_html_e( 'Date', 'wp-mail-smtp' ); ?></span> <span class="debug-event-value"><?php echo esc_html( $this->get_created_at_formatted() ); ?></span> </div> <div class="wp-mail-smtp-debug-event-row wp-mail-smtp-debug-event-preview-content"> <span class="debug-event-label"><?php esc_html_e( 'Content', 'wp-mail-smtp' ); ?></span> <div class="debug-event-value"> <?php echo wp_kses( str_replace( [ "\r\n", "\r", "\n" ], '<br>', $this->get_content() ), [ 'br' => [] ] ); ?> </div> </div> <?php if ( ! empty( $initiator ) ) : ?> <div class="wp-mail-smtp-debug-event-row wp-mail-smtp-debug-event-preview-caller"> <span class="debug-event-label"><?php esc_html_e( 'Source', 'wp-mail-smtp' ); ?></span> <div class="debug-event-value"> <span class="debug-event-initiator"><?php echo esc_html( $initiator ); ?></span> <p class="debug-event-code"> <?php printf( /* Translators: %1$s the path of a file, %2$s the line number in the file. */ esc_html__( '%1$s (line: %2$s)', 'wp-mail-smtp' ), esc_html( $this->get_initiator_file_path() ), esc_html( $this->get_initiator_file_line() ) ); ?> <?php if ( ! empty( $initiator_backtrace ) ) : ?> <br><br> <b><?php esc_html_e( 'Backtrace:', 'wp-mail-smtp' ); ?></b> <br> <?php foreach ( $initiator_backtrace as $i => $item ) { printf( /* translators: %1$d - index number; %2$s - function name; %3$s - file path; %4$s - line number. */ esc_html__( '[%1$d] %2$s called at [%3$s:%4$s]', 'wp-mail-smtp' ), $i, isset( $item['class'] ) ? esc_html( $item['class'] . $item['type'] . $item['function'] ) : esc_html( $item['function'] ), isset( $item['file'] ) ? esc_html( $item['file'] ) : '', // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped isset( $item['line'] ) ? esc_html( $item['line'] ) : '' // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ); echo '<br>'; } ?> <?php endif; ?> </p> </div> </div> <?php endif; ?> </div> <?php return ob_get_clean(); } /** * Get the short details about this event (event content and the initiator's name). * * @since 3.0.0 * * @return string */ public function get_short_details() { $result = []; if ( ! empty( $this->get_initiator() ) ) { $result[] = sprintf( /* Translators: %s - Email initiator/source name. */ esc_html__( 'Email Source: %s', 'wp-mail-smtp' ), esc_html( $this->get_initiator() ) ); } $result[] = esc_html( $this->get_content() ); return implode( WP::EOL, $result ); } /** * Save a new or modified event in DB. * * @since 3.0.0 * * @throws \Exception When event init fails. * * @return Event New or updated event class instance. */ public function save() { global $wpdb; $table = DebugEvents::get_table_name(); if ( (bool) $this->get_id() ) { // Update the existing DB table record. $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching $table, [ 'content' => $this->content, 'initiator' => $this->initiator, 'event_type' => $this->event_type, 'created_at' => $this->get_created_at()->format( WP::datetime_mysql_format() ), ], [ 'id' => $this->get_id(), ], [ '%s', // content. '%s', // initiator. '%s', // type. '%s', // created_at. ], [ '%d', ] ); $event_id = $this->get_id(); } else { // Create a new DB table record. $wpdb->insert( $table, [ 'content' => $this->content, 'initiator' => $this->initiator, 'event_type' => $this->event_type, 'created_at' => $this->get_created_at()->format( WP::datetime_mysql_format() ), ], [ '%s', // content. '%s', // initiator. '%s', // type. '%s', // created_at. ] ); $event_id = $wpdb->insert_id; } try { $event = new Event( $event_id ); } catch ( \Exception $e ) { $event = new Event(); } return $event; } /** * Set the content of this event. * * @since 3.0.0 * * @param string|array $content The event's content. */ public function set_content( $content ) { if ( ! is_string( $content ) ) { $this->content = wp_json_encode( $content ); } else { $this->content = wp_strip_all_tags( str_replace( '<br>', "\r\n", $content ), false ); } } /** * Set the initiator by checking the backtrace for the wp_mail function call. * * @since 3.0.0 */ public function set_initiator() { $initiator = wp_mail_smtp()->get_wp_mail_initiator(); if ( empty( $initiator->get_file() ) ) { return; } $data['file'] = $initiator->get_file(); if ( ! empty( $initiator->get_line() ) ) { $data['line'] = $initiator->get_line(); } if ( DebugEvents::is_debug_enabled() ) { $data['backtrace'] = $initiator->get_backtrace(); } $this->initiator = wp_json_encode( $data ); } /** * Set the type of this event. * * @since 3.0.0 * * @param int $type The event's type. */ public function set_type( $type ) { $this->event_type = (int) $type; } /** * Whether the event instance is a valid entity to work with. * * @since 3.0.0 */ public function is_valid() { return ! ( empty( $this->id ) || empty( $this->created_at ) ); } /** * Whether this is an error event. * * @since 3.0.0 * * @return bool */ public function is_error() { return self::TYPE_ERROR === $this->get_type(); } /** * Whether this is a debug event. * * @since 3.0.0 * * @return bool */ public function is_debug() { return self::TYPE_DEBUG === $this->get_type(); } } DebugEvents/DebugEvents.php 0000644 00000030306 15252526345 0011716 0 ustar 00 <?php namespace WPMailSMTP\Admin\DebugEvents; use WP_Error; use WPMailSMTP\Admin\Area; use WPMailSMTP\Options; use WPMailSMTP\Tasks\DebugEventsCleanupTask; use WPMailSMTP\WP; /** * Debug Events class. * * @since 3.0.0 */ class DebugEvents { /** * Transient name for the error debug events. * * @since 3.9.0 * * @var string */ const ERROR_DEBUG_EVENTS_TRANSIENT = 'wp_mail_smtp_error_debug_events_transient'; /** * Register hooks. * * @since 3.0.0 */ public function hooks() { // Process AJAX requests. add_action( 'wp_ajax_wp_mail_smtp_debug_event_preview', [ $this, 'process_ajax_debug_event_preview' ] ); add_action( 'wp_ajax_wp_mail_smtp_delete_all_debug_events', [ $this, 'process_ajax_delete_all_debug_events' ] ); // Initialize screen options for the Debug Events page. add_action( 'load-wp-mail-smtp_page_wp-mail-smtp-tools', [ $this, 'screen_options' ] ); add_filter( 'set-screen-option', [ $this, 'set_screen_options' ], 10, 3 ); add_filter( 'set_screen_option_wp_mail_smtp_debug_events_per_page', [ $this, 'set_screen_options' ], 10, 3 ); // Cancel previous debug events cleanup task if retention period option was changed. add_filter( 'wp_mail_smtp_options_set', [ $this, 'maybe_cancel_debug_events_cleanup_task' ] ); // Detect debug events log retention period constant change. if ( Options::init()->is_const_defined( 'debug_events', 'retention_period' ) ) { add_action( 'admin_init', [ $this, 'detect_debug_events_retention_period_constant_change' ] ); } } /** * Detect debug events retention period constant change. * * @since 3.6.0 */ public function detect_debug_events_retention_period_constant_change() { if ( ! WP::in_wp_admin() ) { return; } if ( Options::init()->is_const_changed( 'debug_events', 'retention_period' ) ) { ( new DebugEventsCleanupTask() )->cancel(); } } /** * Cancel previous debug events cleanup task if retention period option was changed. * * @since 3.6.0 * * @param array $options Currently processed options passed to a filter hook. * * @return array */ public function maybe_cancel_debug_events_cleanup_task( $options ) { if ( isset( $options['debug_events']['retention_period'] ) ) { // If this option has changed, cancel the recurring cleanup task and init again. if ( Options::init()->is_option_changed( $options['debug_events']['retention_period'], 'debug_events', 'retention_period' ) ) { ( new DebugEventsCleanupTask() )->cancel(); } } return $options; } /** * Process AJAX request for deleting all debug event entries. * * @since 3.0.0 */ public function process_ajax_delete_all_debug_events() { if ( empty( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['nonce'] ), 'wp_mail_smtp_debug_events' ) ) { wp_send_json_error( esc_html__( 'Access rejected.', 'wp-mail-smtp' ) ); } if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_options() ) ) { wp_send_json_error( esc_html__( 'You don\'t have the capability to perform this action.', 'wp-mail-smtp' ) ); } if ( ! self::is_valid_db() ) { wp_send_json_error( esc_html__( 'For some reason the database table was not installed correctly. Please contact plugin support team to diagnose and fix the issue.', 'wp-mail-smtp' ) ); } global $wpdb; $table = self::get_table_name(); $sql = "TRUNCATE TABLE `$table`;"; // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $result = $wpdb->query( $sql ); if ( $result !== false ) { wp_send_json_success( esc_html__( 'All debug event entries were deleted successfully.', 'wp-mail-smtp' ) ); } wp_send_json_error( sprintf( /* translators: %s - WPDB error message. */ esc_html__( 'There was an issue while trying to delete all debug event entries. Error message: %s', 'wp-mail-smtp' ), $wpdb->last_error ) ); } /** * Process AJAX request for debug event preview. * * @since 3.0.0 */ public function process_ajax_debug_event_preview() { if ( empty( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['nonce'] ), 'wp_mail_smtp_debug_events' ) ) { wp_send_json_error( esc_html__( 'Access rejected.', 'wp-mail-smtp' ) ); } if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_options() ) ) { wp_send_json_error( esc_html__( 'You don\'t have the capability to perform this action.', 'wp-mail-smtp' ) ); } if ( ! self::is_valid_db() ) { wp_send_json_error( esc_html__( 'For some reason the database table was not installed correctly. Please contact plugin support team to diagnose and fix the issue.', 'wp-mail-smtp' ) ); } $event_id = isset( $_POST['id'] ) ? intval( $_POST['id'] ) : false; if ( empty( $event_id ) ) { wp_send_json_error( esc_html__( 'No Debug Event ID provided!', 'wp-mail-smtp' ) ); } $event = new Event( $event_id ); wp_send_json_success( [ 'title' => $event->get_title(), 'content' => $event->get_details_html(), ] ); } /** * Add the debug event to the DB. * * @since 3.0.0 * * @param string $message The event's message. * @param int $type The event's type. * * @return bool|int */ public static function add( $message = '', $type = 0 ) { if ( ! self::is_valid_db() ) { return false; } if ( ! in_array( $type, array_keys( Event::get_types() ), true ) ) { return false; } if ( $type === Event::TYPE_DEBUG && ! self::is_debug_enabled() ) { return false; } try { $event = new Event(); $event->set_type( $type ); $event->set_content( $message ); $event->set_initiator(); return $event->save()->get_id(); } catch ( \Exception $exception ) { return false; } } /** * Save the debug message. * * @since 3.0.0 * @since 3.5.0 Returns Event ID. * * @param string $message The debug message. * * @return bool|int */ public static function add_debug( $message = '' ) { return self::add( $message, Event::TYPE_DEBUG ); } /** * Add a debug event subject to a throttle window — only logs if no other event * with the same throttle key has been logged within the TTL. * * Useful for background errors that can fire on every page load (e.g. expired * OAuth token refresh failures) to avoid flooding the events table. * * @since 4.9.0 * * @param string $message The event's message. * @param string $throttle_key Unique key identifying this event's throttle bucket. * Automatically prefixed with `wp_mail_smtp_` before * being used as a transient key. * @param int $ttl Throttle window in seconds. Default 5 minutes. * @param int $type The event's type. Default Event::TYPE_ERROR (0). * * @return bool|int Event ID on success, false if throttled or save failed. */ public static function add_throttled( $message, $throttle_key, $ttl = 5 * MINUTE_IN_SECONDS, $type = 0 ) { $transient_key = 'wp_mail_smtp_' . $throttle_key; if ( get_transient( $transient_key ) ) { return false; } $event_id = self::add( $message, $type ); if ( $event_id !== false ) { set_transient( $transient_key, time(), $ttl ); } return $event_id; } /** * Get the debug message from the provided debug event IDs. * * @since 3.0.0 * * @param array|string|int $ids A single or a list of debug event IDs. * * @return array */ public static function get_debug_messages( $ids ) { global $wpdb; if ( empty( $ids ) ) { return []; } if ( ! self::is_valid_db() ) { return []; } // Convert to a string. if ( is_array( $ids ) ) { $ids = implode( ',', $ids ); } $ids = explode( ',', (string) $ids ); $ids = array_map( 'intval', $ids ); $placeholders = implode( ', ', array_fill( 0, count( $ids ), '%d' ) ); $table = self::get_table_name(); // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare $events_data = $wpdb->get_results( $wpdb->prepare( "SELECT id, content, initiator, event_type, created_at FROM {$table} WHERE id IN ( {$placeholders} )", $ids ) ); // phpcs:enable if ( empty( $events_data ) ) { return []; } return array_map( function ( $event_item ) { $event = new Event( $event_item ); return $event->get_short_details(); }, $events_data ); } /** * Returns the number of error debug events in a given time span. * * By default it returns the number of error debug events in the last 30 days. * * @since 3.9.0 * * @param string $span_of_time The time span to count the events for. Default '-30 days'. * * @return int|WP_Error The number of error debug events or WP_Error on failure. */ public static function get_error_debug_events_count( $span_of_time = '-30 days' ) { $timestamp = strtotime( $span_of_time ); if ( ! $timestamp || $timestamp > time() ) { return new WP_Error( 'wp_mail_smtp_admin_debug_events_get_error_debug_events_count_invalid_time', 'Invalid time span.' ); } if ( ! self::is_valid_db() ) { return 0; } $transient_key = self::ERROR_DEBUG_EVENTS_TRANSIENT . '_' . sanitize_title_with_dashes( $span_of_time ); $cached_error_events_count = get_transient( $transient_key ); if ( $cached_error_events_count !== false ) { return (int) $cached_error_events_count; } global $wpdb; // phpcs:disable WordPress.DB.PreparedSQLPlaceholders.UnquotedComplexPlaceholder $sql = $wpdb->prepare( 'SELECT COUNT(*) FROM `%1$s` WHERE event_type = %2$d AND created_at >= "%3$s"', self::get_table_name(), Event::TYPE_ERROR, gmdate( WP::datetime_mysql_format(), $timestamp ) ); // phpcs:enable WordPress.DB.PreparedSQLPlaceholders.UnquotedComplexPlaceholder // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared $error_events_count = (int) $wpdb->get_var( $sql ); set_transient( $transient_key, $error_events_count, HOUR_IN_SECONDS ); return $error_events_count; } /** * Register the screen options for the debug events page. * * @since 3.0.0 */ public function screen_options() { $screen = get_current_screen(); if ( ! is_object( $screen ) || strpos( $screen->id, 'wp-mail-smtp_page_wp-mail-smtp-tools' ) === false || // phpcs:ignore WordPress.Security.NonceVerification.Recommended ! isset( $_GET['tab'] ) || $_GET['tab'] !== 'debug-events' ) { return; } add_screen_option( 'per_page', [ 'label' => esc_html__( 'Number of events per page:', 'wp-mail-smtp' ), 'option' => 'wp_mail_smtp_debug_events_per_page', 'default' => EventsCollection::PER_PAGE, ] ); } /** * Set the screen options for the debug events page. * * @since 3.0.0 * * @param bool $keep Whether to save or skip saving the screen option value. * @param string $option The option name. * @param int $value The number of items to use. * * @return bool|int */ public function set_screen_options( $keep, $option, $value ) { if ( 'wp_mail_smtp_debug_events_per_page' === $option ) { return (int) $value; } return $keep; } /** * Whether the email debug for debug events is enabled or not. * * @since 3.0.0 * * @return bool */ public static function is_debug_enabled() { return (bool) Options::init()->get( 'debug_events', 'email_debug' ); } /** * Get the debug events page URL. * * @since 3.0.0 * * @return string */ public static function get_page_url() { return add_query_arg( [ 'tab' => 'debug-events', ], wp_mail_smtp()->get_admin()->get_admin_page_url( Area::SLUG . '-tools' ) ); } /** * Get the DB table name. * * @since 3.0.0 * * @return string Table name, prefixed. */ public static function get_table_name() { global $wpdb; return $wpdb->prefix . 'wpmailsmtp_debug_events'; } /** * Whether the DB table exists. * * @since 3.0.0 * * @return bool */ public static function is_valid_db() { global $wpdb; static $is_valid = null; // Return cached value only if table already exists. if ( $is_valid === true ) { return true; } $table = self::get_table_name(); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching $is_valid = (bool) $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s;', $table ) ); return $is_valid; } } DebugEvents/Migration.php 0000644 00000003055 15252526345 0011435 0 ustar 00 <?php namespace WPMailSMTP\Admin\DebugEvents; use WPMailSMTP\MigrationAbstract; /** * Debug Events Migration Class * * @since 3.0.0 */ class Migration extends MigrationAbstract { /** * Version of the debug events database table. * * @since 3.0.0 */ const DB_VERSION = 1; /** * Option key where we save the current debug events DB version. * * @since 3.0.0 */ const OPTION_NAME = 'wp_mail_smtp_debug_events_db_version'; /** * Option key where we save any errors while creating the debug events DB table. * * @since 3.0.0 */ const ERROR_OPTION_NAME = 'wp_mail_smtp_debug_events_db_error'; /** * Create the debug events DB table structure. * * @since 3.0.0 */ protected function migrate_to_1() { global $wpdb; $table = DebugEvents::get_table_name(); $charset_collate = $wpdb->get_charset_collate(); $sql = "CREATE TABLE IF NOT EXISTS `$table` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `content` TEXT DEFAULT NULL, `initiator` TEXT DEFAULT NULL, `event_type` TINYINT UNSIGNED NOT NULL DEFAULT '0', `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINE='InnoDB' {$charset_collate};"; $result = $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared if ( ! empty( $wpdb->last_error ) ) { update_option( self::ERROR_OPTION_NAME, $wpdb->last_error, false ); } // Save the current version to DB. if ( $result !== false ) { $this->update_db_ver( 1 ); } } } Recommendations/RecommendedPlugins.php 0000644 00000016320 15252526345 0014203 0 ustar 00 <?php namespace WPMailSMTP\Admin\Recommendations; use WPMailSMTP\Admin\Area; /** * Recommended-plugins rotating menu: catalog, rotation state, sidebar item. * * Surfaces one sister AM product at a time, advancing through the priority * list as each is adopted (a product is "adopted" 7 days after its plugin is * first activated). Once every product is adopted the item is hidden. * * @since 4.9.0 */ class RecommendedPlugins { /** * First-activation timestamps keyed by product slug. * * @since 4.9.0 */ const ACTIVATED_OPTION = 'wp_mail_smtp_recommended_plugins_activated'; /** * A product is rotated past this long after its plugin was first activated. * * @since 4.9.0 */ const ADOPTED_AFTER = 7 * DAY_IN_SECONDS; /** * Register hooks for the recommended-plugins menu. * * @since 4.9.0 */ public function hooks() { add_action( 'admin_init', [ $this, 'register_pages' ] ); add_action( 'activated_plugin', [ $this, 'record_activation' ], 10, 2 ); } /** * Instantiate every product landing page so each can register its own * AJAX and asset hooks. * * Runs on `admin_init` rather than synchronously: the catalog labels are * translated, and building it before `init` triggers a textdomain-too-early * notice on Pro, where the email log boots the admin during `plugins_loaded`. * * @since 4.9.0 */ public function register_pages() { foreach ( $this->get_products() as $product ) { if ( class_exists( $product['page_class'] ) ) { new $product['page_class'](); } } } /** * Register the rotating submenu item for the currently surfaced product. * * Called by Area during its menu build so the item lands in the right * position; does nothing once every product is adopted. * * @since 4.9.0 * * @param string $access_capability Capability required to view the page. */ public function add_submenu_item( $access_capability ) { $product = $this->get_current_product(); if ( $product === null ) { return; } add_submenu_page( Area::SLUG, $product['label'], $product['label'], $access_capability, $product['page_class']::SLUG, [ $this, 'render_current_page' ] ); $short_slug = substr( $product['page_class']::SLUG, strlen( Area::SLUG . '-' ) ); if ( ! in_array( $short_slug, Area::$pages_registered, true ) ) { Area::$pages_registered[] = $short_slug; } } /** * Render the landing page for the currently surfaced product. * * @since 4.9.0 */ public function render_current_page() { $product = $this->get_current_product(); if ( $product === null ) { return; } $page = new $product['page_class'](); // The id is required: all `wpms:` Tailwind utilities are build-scoped // under `#wp-mail-smtp`, so the landing markup must live beneath it. echo '<div class="wrap wp-mail-smtp-page" id="wp-mail-smtp">'; $page->output(); echo '</div>'; } /** * Ordered product catalog. * * @since 4.9.0 * * @return array<int, array<string, mixed>> */ public function get_products() { return [ [ 'slug' => 'wpconsent', 'name' => 'WPConsent', 'label' => esc_html__( 'Privacy Compliance', 'wp-mail-smtp' ), 'page_class' => Pages\WPConsent::class, 'plugin' => 'wpconsent-cookies-banner-privacy-suite/wpconsent.php', 'plugin_pro' => 'wpconsent-premium/wpconsent-premium.php', 'activation_option' => 'wpconsent_activated', ], [ 'slug' => 'activelayer', 'name' => 'ActiveLayer', 'label' => esc_html__( 'Spam Protection', 'wp-mail-smtp' ), 'page_class' => Pages\ActiveLayer::class, 'plugin' => 'activelayer-anti-spam-spam-protection-for-forms-comments/activelayer-anti-spam-spam-protection-for-forms-comments.php', 'activation_option' => '', // No activation-time option exposed by the plugin. ], [ 'slug' => 'duplicator', 'name' => 'Duplicator', 'label' => esc_html__( 'Backups', 'wp-mail-smtp' ), 'page_class' => Pages\Duplicator::class, 'plugin' => 'duplicator/duplicator.php', 'plugin_pro' => 'duplicator-pro/duplicator-pro.php', 'activation_option' => 'duplicator_install_info', // migration reads ['time']. ], [ 'slug' => 'wpvibe', 'name' => 'WPVibe', 'label' => esc_html__( 'AI MCP', 'wp-mail-smtp' ), 'page_class' => Pages\WPVibe::class, 'plugin' => 'vibe-ai/vibe-ai.php', 'activation_option' => '', // No activation-time option exposed by the plugin. ], [ 'slug' => 'universally', 'name' => 'Universally', 'label' => esc_html__( 'Translations', 'wp-mail-smtp' ), 'page_class' => Pages\Universally::class, 'plugin' => 'universally-language-translation-multilingual-tool/universally.php', 'activation_option' => '', // No activation-time option exposed by the plugin. ], [ 'slug' => 'wpcode', 'name' => 'WPCode', 'label' => esc_html__( 'Code Snippets', 'wp-mail-smtp' ), 'page_class' => Pages\WPCode::class, 'plugin' => 'insert-headers-and-footers/ihaf.php', 'plugin_pro' => 'wpcode-premium/wpcode.php', 'activation_option' => 'ihaf_activated', // migration reads ['wpcode']. ], ]; } /** * Map of catalog plugin files (lite + pro) to product slug. * * @since 4.9.0 * * @return array<string, string> */ private function get_plugin_slug_map() { $map = []; foreach ( $this->get_products() as $product ) { if ( ! empty( $product['plugin'] ) ) { $map[ $product['plugin'] ] = $product['slug']; } if ( ! empty( $product['plugin_pro'] ) ) { $map[ $product['plugin_pro'] ] = $product['slug']; } } return $map; } /** * Record first activation time of a catalog plugin (write-once). * * @since 4.9.0 * * @param string $plugin Plugin file relative to the plugins dir. * @param bool $network_wide Whether activation is network-wide. */ public function record_activation( $plugin, $network_wide ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed $slug = $this->get_plugin_slug_map()[ $plugin ] ?? ''; if ( $slug === '' ) { return; } $activated = (array) get_option( self::ACTIVATED_OPTION, [] ); if ( isset( $activated[ $slug ] ) ) { return; } $activated[ $slug ] = time(); update_option( self::ACTIVATED_OPTION, $activated ); } /** * The product to surface right now, or null once every product is adopted. * * @since 4.9.0 * * @return array<string, mixed>|null */ public function get_current_product() { $products = $this->get_products(); $activated = (array) get_option( self::ACTIVATED_OPTION, [] ); $now = time(); foreach ( $products as $product ) { $timestamp = isset( $activated[ $product['slug'] ] ) ? (int) $activated[ $product['slug'] ] : 0; if ( $timestamp === 0 || ( $now - $timestamp ) < self::ADOPTED_AFTER ) { return $product; } } // Everything adopted: surface nothing. The standard "About Us" submenu // remains as the trailing item. return null; } } Recommendations/PageAbstract.php 0000644 00000045673 15252526345 0012774 0 ustar 00 <?php namespace WPMailSMTP\Admin\Recommendations; use WPMailSMTP\WP; /** * Abstract recommended-plugins landing page. * * Renders a 3-step Install -> Setup -> Result landing page for a single * recommended product, handles the per-product status-check AJAX, and records * install attribution. * * @since 4.9.0 */ abstract class PageAbstract { /** * Admin menu page slug. * * @since 4.9.0 * * @var string */ public const SLUG = ''; /** * Configuration. * * @since 4.9.0 * * @var array */ protected $config = []; /** * Runtime data used for generating page HTML. * * @since 4.9.0 * * @var array */ protected $output_data = []; /** * Constructor. * * @since 4.9.0 */ public function __construct() { if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_options() ) ) { return; } $this->hooks(); } /** * Hooks. * * @since 4.9.0 */ public function hooks(): void { $plugin = static::get_plugin_name(); if ( wp_doing_ajax() ) { add_action( "wp_ajax_wp_mail_smtp_page_check_{$plugin}_status", [ $this, 'ajax_check_plugin_status' ] ); add_action( 'activated_plugin', [ $this, 'plugin_activated' ] ); } // Check what page we are on. // phpcs:ignore WordPress.Security.NonceVerification.Recommended $page = isset( $_GET['page'] ) ? sanitize_key( $_GET['page'] ) : ''; // Only load if we are actually on the correct page. if ( $page !== static::SLUG ) { return; } add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] ); } /** * Enqueue JS and CSS files. * * @since 4.9.0 */ public function enqueue_assets(): void { $min = WP::asset_min(); // Lity (registered by Admin\Area). wp_enqueue_style( 'wp-mail-smtp-admin-lity' ); wp_enqueue_script( 'wp-mail-smtp-admin-lity' ); // Custom styles for Lity image size limitation. wp_add_inline_style( 'wp-mail-smtp-admin-lity', ' .lity-image .lity-container { max-width: 1040px !important; } .lity-image img { max-width: 1040px !important; width: 100%; height: auto; } ' ); wp_enqueue_script( 'wp-mail-smtp-admin-recommendations-' . static::get_plugin_name(), wp_mail_smtp()->assets_url . "/js/smtp-recommendations{$min}.js", [ 'jquery', 'wp-mail-smtp-admin' ], WPMS_PLUGIN_VER, true ); wp_localize_script( 'wp-mail-smtp-admin-recommendations-' . static::get_plugin_name(), 'wp_mail_smtp_recommendations', [ 'plugin_page' => $this->get_js_strings() ] ); } /** * Generate and output page HTML. * * @since 4.9.0 */ public function output(): void { echo '<div id="wp-mail-smtp-admin-' . esc_attr( static::get_plugin_name() ) . '" class="wp-mail-smtp-plugin-recommendation-page">'; $this->output_section_heading(); $this->output_section_screenshot(); $this->output_section_footer(); $this->output_section_step_install(); $this->output_section_step_setup(); $this->output_section_step_result(); echo '</div>'; } /** * Generate and output step 'Install' section HTML. * * @since 4.9.0 * * @noinspection HtmlUnknownTarget */ protected function output_section_step_install(): void { $step = $this->get_data_step_install(); if ( empty( $step ) ) { return; } $button_format = '<button class="button %3$s" data-plugin="%1$s" data-action="%4$s" data-provider="%5$s">%2$s</button>'; $button_allowed_html = [ 'button' => [ 'class' => true, 'data-plugin' => true, 'data-action' => true, 'data-provider' => true, ], ]; if ( ! $this->output_data['plugin_installed'] && ! $this->output_data['pro_plugin_installed'] && ! current_user_can( 'install_plugins' ) ) { $button_format = '<a class="link" href="%1$s" target="_blank" rel="nofollow noopener">%2$s <span aria-hidden="true" class="dashicons dashicons-external"></span></a>'; $button_allowed_html = [ 'a' => [ 'class' => true, 'href' => true, 'target' => true, 'rel' => true, ], 'span' => [ 'class' => true, 'aria-hidden' => true, ], ]; } $is_url = (bool) preg_match( '#^https?://#i', (string) $step['plugin'] ); $plugin_attr = $is_url ? esc_url( $step['plugin'] ) : esc_attr( $step['plugin'] ); $button = sprintf( $button_format, $plugin_attr, esc_html( $step['button_text'] ), esc_attr( $step['button_class'] ), esc_attr( $step['button_action'] ), esc_attr( static::get_plugin_name() ) ); printf( '<section class="step step-install"> <aside class="num"> <img src="%1$s" alt="%2$s" /> <i class="loader hidden"></i> </aside> <div> <h2>%3$s</h2> <p>%4$s</p> %5$s </div> </section>', esc_url( wp_mail_smtp()->assets_url . '/images/recommendations/' . $step['icon'] ), esc_attr__( 'Step 1', 'wp-mail-smtp' ), esc_html( $step['heading'] ), esc_html( $step['description'] ), wp_kses( $button, $button_allowed_html ) ); } /** * Generate and output step 'Setup' section HTML. * * @since 4.9.0 * * @noinspection HtmlUnknownTarget */ protected function output_section_step_setup(): void { $step = $this->get_data_step_setup(); if ( empty( $step ) ) { return; } printf( '<section class="step step-setup %1$s"> <aside class="num"> <img src="%2$s" alt="%3$s" /> <i class="loader hidden"></i> </aside> <div> <h2>%4$s</h2> <p>%5$s</p> <button class="button %6$s" data-url="%7$s">%8$s</button> </div> </section>', esc_attr( $step['section_class'] ), esc_url( wp_mail_smtp()->assets_url . '/images/recommendations/' . $step['icon'] ), esc_attr__( 'Step 2', 'wp-mail-smtp' ), esc_html( $step['heading'] ), esc_html( $step['description'] ), esc_attr( $step['button_class'] ), esc_url( admin_url( $this->config[ static::get_plugin_name() . '_onboarding' ] ) ), esc_html( $step['button_text'] ) ); } /** * Generate and output footer section HTML. * * @since 4.9.0 */ protected function output_section_footer(): void { // Default implementation - can be overridden by child classes. } /** * Step 'Install' data. * * @since 4.9.0 * * @return array Step data. */ protected function get_data_step_install(): array { $step = []; $step['heading'] = $this->get_install_heading(); $step['description'] = $this->get_install_description(); $this->output_data['all_plugins'] = get_plugins(); $this->output_data['plugin_installed'] = array_key_exists( $this->config['lite_plugin'], $this->output_data['all_plugins'] ); $this->output_data['plugin_activated'] = false; $this->output_data['pro_plugin_installed'] = array_key_exists( $this->config['pro_plugin'], $this->output_data['all_plugins'] ); $this->output_data['pro_plugin_activated'] = false; if ( ! $this->output_data['plugin_installed'] && ! $this->output_data['pro_plugin_installed'] ) { $step['icon'] = 'plugin-page/step-1.svg'; $step['button_text'] = $this->get_install_button_text(); $step['button_class'] = 'button-primary'; $step['button_action'] = 'install'; $step['plugin'] = $this->config['lite_download_url']; if ( ! current_user_can( 'install_plugins' ) ) { $step['heading'] = $this->get_plugin_title(); $step['description'] = ''; $step['button_text'] = $this->get_plugin_title() . ' on WordPress.org'; $step['plugin'] = $this->config['lite_wporg_url']; } } else { $this->output_data['plugin_activated'] = is_plugin_active( $this->config['lite_plugin'] ) || is_plugin_active( $this->config['pro_plugin'] ); $step['icon'] = $this->output_data['plugin_activated'] ? 'plugin-page/complete.svg' : 'plugin-page/step-1.svg'; $step['button_text'] = $this->output_data['plugin_activated'] ? $this->get_installed_activated_text() : $this->get_activate_text(); $step['button_class'] = $this->output_data['plugin_activated'] ? 'grey disabled' : 'button-primary'; $step['button_action'] = $this->output_data['plugin_activated'] ? '' : 'activate'; $step['plugin'] = $this->output_data['pro_plugin_installed'] ? $this->config['pro_plugin'] : $this->config['lite_plugin']; $step['is_pro'] = $this->output_data['pro_plugin_installed']; } return $step; } /** * Step 'Setup' data. * * @since 4.9.0 * * @return array Step data. */ protected function get_data_step_setup(): array { $step = []; $this->output_data['plugin_setup'] = false; if ( $this->output_data['plugin_activated'] ) { $this->output_data['plugin_setup'] = $this->is_plugin_configured(); } $step['icon'] = 'plugin-page/step-2.svg'; $step['section_class'] = $this->output_data['plugin_activated'] ? '' : 'grey'; $step['heading'] = $this->get_setup_heading(); $step['description'] = $this->get_setup_description(); $step['button_text'] = $this->get_setup_button_text(); $step['button_class'] = 'grey disabled'; if ( $this->output_data['plugin_setup'] ) { $step['icon'] = 'plugin-page/complete.svg'; $step['section_class'] = ''; $step['button_text'] = $this->get_setup_completed_text(); } else { $step['button_class'] = $this->output_data['plugin_activated'] ? 'button-primary' : 'grey disabled'; } return $step; } /** * Ajax endpoint. Check plugin setup status. * Used to properly init the step 2 section after completing step 1. * * @since 4.9.0 */ public function ajax_check_plugin_status(): void { // Security checks. if ( ! check_ajax_referer( 'wp-mail-smtp-admin', 'nonce', false ) || ! current_user_can( wp_mail_smtp()->get_capability_manage_options() ) ) { wp_send_json_error( [ 'error' => esc_html__( 'You do not have permission.', 'wp-mail-smtp' ) ] ); } $result = []; if ( ! $this->is_plugin_available() ) { wp_send_json_error( [ 'error' => esc_html__( 'Plugin unavailable.', 'wp-mail-smtp' ) ] ); } $result['setup_status'] = (int) $this->is_plugin_configured(); $result['license_level'] = 'lite'; $result['step3_button_url'] = $this->config[ static::get_plugin_name() . '_addon_page' ]; if ( $this->is_pro_active() ) { $result['license_level'] = 'pro'; } $result['result_status'] = $this->is_plugin_finished_setup(); $result['addon_installed'] = (int) array_key_exists( $this->config[ static::get_plugin_name() . '_addon' ], get_plugins() ); wp_send_json_success( $result ); } /** * Set the source of the plugin installation. * * @since 4.9.0 * * @param string $plugin_basename The basename of the plugin. */ public function plugin_activated( string $plugin_basename ): void { if ( $plugin_basename !== $this->config['lite_plugin'] ) { return; } $source = wp_mail_smtp()->is_pro() ? 'WP Mail SMTP Pro' : 'WP Mail SMTP'; update_option( static::get_plugin_name() . '_source', $source, false ); update_option( static::get_plugin_name() . '_date', time(), false ); } /** * JS strings. * * @since 4.9.0 * * @return array Array of strings. * @noinspection HtmlUnknownTarget */ protected function get_js_strings(): array { $error_could_not_install = sprintf( wp_kses( /* translators: %1$s - Lite plugin download URL. */ __( 'Could not install the plugin automatically. Please <a href="%1$s">download</a> it and install it manually.', 'wp-mail-smtp' ), [ 'a' => [ 'href' => true, ], ] ), esc_url( $this->config['lite_download_url'] ?? '' ) ); $error_could_not_activate = sprintf( wp_kses( /* translators: %1$s - Plugins page URL. */ __( 'Could not activate the plugin. Please activate it on the <a href="%1$s">Plugins page</a>.', 'wp-mail-smtp' ), [ 'a' => [ 'href' => true, ], ] ), esc_url( admin_url( 'plugins.php' ) ) ); return [ 'installing' => esc_html__( 'Installing...', 'wp-mail-smtp' ), 'activating' => esc_html__( 'Activating...', 'wp-mail-smtp' ), 'activated' => $this->get_installed_activated_text(), 'activated_pro' => $this->get_pro_installed_activated_text(), 'install_now' => esc_html__( 'Install Now', 'wp-mail-smtp' ), 'activate_now' => esc_html__( 'Activate Now', 'wp-mail-smtp' ), 'download_now' => esc_html__( 'Download Now', 'wp-mail-smtp' ), 'plugins_page' => esc_html__( 'Go to Plugins page', 'wp-mail-smtp' ), 'error_could_not_install' => $error_could_not_install, 'error_could_not_activate' => $error_could_not_activate, static::get_plugin_name() . '_manual_install_url' => $this->config['lite_download_url'], static::get_plugin_name() . '_manual_activate_url' => admin_url( 'plugins.php' ), ]; } /** * Get the plugin name for use in IDs, CSS classes, and config keys. * * @since 4.9.0 * * @return string Plugin name. */ abstract protected static function get_plugin_name(): string; /** * Generate and output heading section HTML. * * @since 4.9.0 * * @noinspection HtmlUnknownTarget */ public function output_section_heading(): void { $strings = $this->get_heading_strings(); // Heading section. printf( '<section class="top"> <img class="img-top" src="%1$s" alt="%2$s"/> <h1>%3$s</h1> <p>%4$s</p> </section>', esc_url( $this->get_heading_image_url() ), esc_attr( $this->get_heading_alt_text() ), esc_html( $this->get_heading_title() ), esc_html( implode( ' ', $strings ) ) ); } /** * Get heading image URL. * * @since 4.9.0 * * @return string Heading image URL. */ protected function get_heading_image_url(): string { return wp_mail_smtp()->assets_url . '/images/recommendations/plugins/' . static::get_plugin_name() . '/logo.svg'; // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get heading title text. * * @since 4.9.0 * * @return string Heading title. */ abstract protected function get_heading_title(): string; /** * Get heading alt text for logo. * * @since 4.9.0 * * @return string Heading alt text. */ abstract protected function get_heading_alt_text(): string; /** * Get heading description strings. * * @since 4.9.0 * * @return array Array of description strings. */ abstract protected function get_heading_strings(): array; /** * Generate and output screenshot section HTML. * * @since 4.9.0 */ protected function output_section_screenshot(): void { $features = $this->get_screenshot_features(); $list = ''; foreach ( $features as $feature ) { $list .= sprintf( '<li><span aria-hidden="true" class="wpms:icon-[fa6-solid--arrow-right] wpms:text-link wpms:w-[14px] wpms:h-[14px] wpms:shrink-0"></span>%s</li>', esc_html( $feature ) ); } // Screenshot section. printf( '<section class="screenshot"> <div class="cont"> <img src="%1$s" alt="%2$s" srcset="%4$s 2x"/> <a href="%3$s" class="hover" data-lity></a> </div> <ul>%5$s</ul> </section>', esc_url( wp_mail_smtp()->assets_url . '/images/recommendations/plugins/' . static::get_plugin_name() . '/screenshot-tnail.png' ), esc_attr( $this->get_screenshot_alt_text() ), esc_url( wp_mail_smtp()->assets_url . '/images/recommendations/plugins/' . static::get_plugin_name() . '/screenshot-full@2x.png' ), esc_url( wp_mail_smtp()->assets_url . '/images/recommendations/plugins/' . static::get_plugin_name() . '/screenshot-tnail@2x.png' ), wp_kses( $list, [ 'li' => [], 'span' => [ 'class' => [], 'aria-hidden' => [], ], ] ) ); } /** * Get screenshot features list. * * @since 4.9.0 * * @return array Array of feature strings. */ abstract protected function get_screenshot_features(): array; /** * Get screenshot alt text. * * @since 4.9.0 * * @return string Alt text for screenshot image. */ abstract protected function get_screenshot_alt_text(): string; /** * Generate and output step 'Result' section HTML. * * @since 4.9.0 */ abstract protected function output_section_step_result(): void; /** * Whether a plugin is configured or not. * * @since 4.9.0 * * @return bool True if a plugin is configured properly. */ abstract protected function is_plugin_configured(): bool; /** * Whether a plugin is active or not. * * @since 4.9.0 * * @return bool True if the plugin is active. */ abstract protected function is_plugin_activated(): bool; /** * Whether a plugin is finished setup or not. * * @since 4.9.0 * * @return bool True if the plugin is finished setup. */ abstract protected function is_plugin_finished_setup(): bool; /** * Whether a plugin is available (class/function exists). * * @since 4.9.0 * * @return bool True if a plugin is available. */ abstract protected function is_plugin_available(): bool; /** * Whether a pro-version is active. * * @since 4.9.0 * * @return bool True if a pro-version is active. */ abstract protected function is_pro_active(): bool; /** * Get the heading for the installation step. * * @since 4.9.0 * * @return string Install step heading. */ abstract protected function get_install_heading(): string; /** * Get the description for the installation step. * * @since 4.9.0 * * @return string Install step description. */ abstract protected function get_install_description(): string; /** * Get the plugin title. * * @since 4.9.0 * * @return string Plugin title. */ abstract protected function get_plugin_title(): string; /** * Get the installation button text. * * @since 4.9.0 * * @return string Install button text. */ abstract protected function get_install_button_text(): string; /** * Get the text when a plugin is installed and activated. * * @since 4.9.0 * * @return string Installed & activated text. */ abstract protected function get_installed_activated_text(): string; /** * Get the activate button text. * * @since 4.9.0 * * @return string Activate button text. */ abstract protected function get_activate_text(): string; /** * Get the heading for the setup step. * * @since 4.9.0 * * @return string Setup step heading. */ abstract protected function get_setup_heading(): string; /** * Get the description for the setup step. * * @since 4.9.0 * * @return string Setup step description. */ abstract protected function get_setup_description(): string; /** * Get the setup button text. * * @since 4.9.0 * * @return string Setup button text. */ abstract protected function get_setup_button_text(): string; /** * Get the text when setup is completed. * * @since 4.9.0 * * @return string Setup completed text. */ abstract protected function get_setup_completed_text(): string; /** * Get the text when a pro-version is installed and activated. * * @since 4.9.0 * * @return string Pro installed and activated text. */ abstract protected function get_pro_installed_activated_text(): string; } Recommendations/Pages/WPConsent.php 0000644 00000031262 15252526345 0013340 0 ustar 00 <?php namespace WPMailSMTP\Admin\Recommendations\Pages; use WPMailSMTP\Admin\Recommendations\PageAbstract; /** * WPConsent recommended-plugin landing page (Privacy Compliance). * * @since 4.9.0 */ class WPConsent extends PageAbstract { /** * Admin menu page slug. * * @since 4.9.0 * * @var string */ public const SLUG = 'wp-mail-smtp-recommended-wpconsent'; /** * Configuration. * * @since 4.9.0 * * @var array */ protected $config = [ 'lite_plugin' => 'wpconsent-cookies-banner-privacy-suite/wpconsent.php', 'lite_wporg_url' => 'https://wordpress.org/plugins/wpconsent-cookies-banner-privacy-suite/', 'lite_download_url' => 'https://downloads.wordpress.org/plugin/wpconsent-cookies-banner-privacy-suite.zip', 'pro_plugin' => 'wpconsent-premium/wpconsent-premium.php', 'wpconsent_addon' => 'wpconsent-premium/wpconsent-premium.php', 'wpconsent_addon_page' => 'https://wpconsent.com/?utm_source=wpmailsmtpplugin&utm_medium=link&utm_campaign=privacy-compliance-page', 'wpconsent_onboarding' => 'admin.php?page=wpconsent-onboarding', ]; /** * Hooks. * * Removes WPConsent's onboarding redirect during AJAX to prevent it from * hijacking the status-check JSON response. * * @since 4.9.0 */ public function hooks(): void { if ( wp_doing_ajax() ) { remove_action( 'admin_init', 'wpconsent_maybe_redirect_onboarding', 9999 ); } parent::hooks(); } /** * Get the plugin name for use in IDs, CSS classes, and config keys. * * @since 4.9.0 * * @return string Plugin name. */ protected static function get_plugin_name(): string { return 'wpconsent'; // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get heading title text. * * @since 4.9.0 * * @return string Heading title. */ protected function get_heading_title(): string { return esc_html__( 'Make Your Website Privacy-Compliant in Minutes', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get heading alt text for logo. * * @since 4.9.0 * * @return string Heading alt text. */ protected function get_heading_alt_text(): string { return esc_attr__( 'WP Mail SMTP ♥ WPConsent', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get heading description strings. * * @since 4.9.0 * * @return array Array of description strings. */ protected function get_heading_strings(): array { return [ // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement esc_html__( 'Build trust with clear, compliant privacy practices. WPConsent adds clean, professional banners and handles the technical side for you.', 'wp-mail-smtp' ), esc_html__( 'Built for transparency. Designed for ease.', 'wp-mail-smtp' ), ]; } /** * Get screenshot features list. * * @since 4.9.0 * * @return array Array of feature strings. */ protected function get_screenshot_features(): array { return [ // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement esc_html__( 'A professional banner that fits your site.', 'wp-mail-smtp' ), esc_html__( 'Tools like Google Analytics and Facebook Pixel paused until consent.', 'wp-mail-smtp' ), esc_html__( 'Peace of mind knowing you’re aligned with global laws.', 'wp-mail-smtp' ), esc_html__( 'Self-hosted. Your data remains on your site.', 'wp-mail-smtp' ), ]; } /** * Get screenshot alt text. * * @since 4.9.0 * * @return string Alt text for screenshot image. */ protected function get_screenshot_alt_text(): string { return esc_attr__( 'WPConsent screenshot', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Generate and output step 'Result' section HTML. * * @since 4.9.0 * * @noinspection HtmlUnknownTarget */ protected function output_section_step_result(): void { $step = $this->get_data_step_result(); if ( empty( $step ) ) { return; } printf( '<section class="step step-result %1$s"> <aside class="num"> <img src="%2$s" alt="%3$s" /> <i class="loader hidden"></i> </aside> <div> <h2>%4$s</h2> <p>%5$s</p> <button class="button %6$s" data-url="%7$s">%8$s</button> </div> </section>', esc_attr( $step['section_class'] ), esc_url( wp_mail_smtp()->assets_url . '/images/recommendations/' . $step['icon'] ), esc_attr__( 'Step 3', 'wp-mail-smtp' ), esc_html__( 'Get Advanced Cookie Consent Features', 'wp-mail-smtp' ), esc_html__( 'With WPConsent Pro you can access advanced features like geolocation, popup layout, records of consent, multilanguage support, and more.', 'wp-mail-smtp' ), esc_attr( $step['button_class'] ), esc_url( $step['button_url'] ), esc_html( $step['button_text'] ) ); } /** * Step 'Result' data. * * @since 4.9.0 * * @return array Step data. * @noinspection PhpUndefinedFunctionInspection */ protected function get_data_step_result(): array { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh $step = []; $step['icon'] = 'plugin-page/step-3.svg'; $step['section_class'] = $this->output_data['plugin_setup'] ? '' : 'grey'; $step['button_text'] = esc_html__( 'Learn More', 'wp-mail-smtp' ); $step['button_class'] = 'grey disabled'; $step['button_url'] = ''; $plugin_license_level = $this->get_license_level(); switch ( $plugin_license_level ) { case 'lite': $step['button_url'] = $this->config['wpconsent_addon_page']; $step['button_class'] = $this->output_data['plugin_setup'] ? 'button-primary' : 'grey disabled'; break; case 'pro': $addon_installed = array_key_exists( $this->config['wpconsent_addon'], $this->output_data['all_plugins'] ); $step['button_text'] = $addon_installed ? esc_html__( 'WPConsent Pro Installed & Activated', 'wp-mail-smtp' ) : esc_html__( 'Install Now', 'wp-mail-smtp' ); $step['button_class'] = $this->output_data['plugin_setup'] ? 'grey disabled' : 'button-primary'; $step['icon'] = $addon_installed ? 'plugin-page/complete.svg' : 'plugin-page/step-3.svg'; break; } return $step; } /** * Retrieve the license level of the plugin. * * @since 4.9.0 * * @return string The plugin license level ('lite' or 'pro'). */ protected function get_license_level(): string { $plugin_license_level = 'lite'; // Check if premium features are available. if ( function_exists( 'wpconsent' ) ) { $wpconsent = wpconsent(); if ( isset( $wpconsent->license ) && method_exists( $wpconsent->license, 'is_active' ) ) { $plugin_license_level = $wpconsent->license->is_active() ? 'pro' : 'lite'; } } return $plugin_license_level; } /** * Whether the plugin is finished setup or not. * * @since 4.9.0 * * @return bool True if the plugin is finished setup. */ protected function is_plugin_finished_setup(): bool { if ( ! $this->is_plugin_configured() ) { return false; } return $this->get_license_level() === 'pro'; } /** * Whether a plugin is configured or not. * * @since 4.9.0 * * @return bool True if plugin is configured properly. * @noinspection PhpUndefinedFunctionInspection */ protected function is_plugin_configured(): bool { if ( ! $this->is_plugin_activated() ) { return false; } // The plugin is considered configured if the consent banner is enabled. if ( function_exists( 'wpconsent' ) ) { $wpconsent = wpconsent(); if ( isset( $wpconsent->settings ) ) { $enable_consent_banner = $wpconsent->settings->get_option( 'enable_consent_banner', 0 ); return ! empty( $enable_consent_banner ); } } return false; } /** * Whether a plugin is active or not. * * @since 4.9.0 * * @return bool True if plugin is active. */ protected function is_plugin_activated(): bool { return ( // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement function_exists( 'wpconsent' ) && ( is_plugin_active( $this->config['lite_plugin'] ) || is_plugin_active( $this->config['pro_plugin'] ) ) ); } /** * Whether a plugin is available (class/function exists). * * @since 4.9.0 * * @return bool True if plugin is available. */ protected function is_plugin_available(): bool { return function_exists( 'wpconsent' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Whether pro version is active. * * @since 4.9.0 * * @return bool True if pro version is active. * @noinspection PhpUndefinedFunctionInspection */ protected function is_pro_active(): bool { if ( ! function_exists( 'wpconsent' ) ) { return false; } $wpconsent = wpconsent(); return isset( $wpconsent->license ) && method_exists( $wpconsent->license, 'is_active' ) && $wpconsent->license->is_active(); } /** * Get the heading for the install step. * * @since 4.9.0 * * @return string Install step heading. */ protected function get_install_heading(): string { return esc_html__( 'Install & Activate WPConsent', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the description for the install step. * * @since 4.9.0 * * @return string Install step description. */ protected function get_install_description(): string { return esc_html__( 'Install WPConsent from the WordPress.org plugin repository.', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the plugin title. * * @since 4.9.0 * * @return string Plugin title. */ protected function get_plugin_title(): string { return esc_html__( 'WPConsent', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the install button text. * * @since 4.9.0 * * @return string Install button text. */ protected function get_install_button_text(): string { return esc_html__( 'Install WPConsent', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the text when a plugin is installed and activated. * * @since 4.9.0 * * @return string Installed & activated text. */ protected function get_installed_activated_text(): string { return esc_html__( 'WPConsent Installed & Activated', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the activate button text. * * @since 4.9.0 * * @return string Activate button text. */ protected function get_activate_text(): string { return esc_html__( 'Activate WPConsent', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the heading for the setup step. * * @since 4.9.0 * * @return string Setup step heading. */ protected function get_setup_heading(): string { return esc_html__( 'Set Up WPConsent', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the description for the setup step. * * @since 4.9.0 * * @return string Setup step description. */ protected function get_setup_description(): string { return esc_html__( 'WPConsent has an intuitive setup wizard to guide you through the cookie consent configuration process.', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the setup button text. * * @since 4.9.0 * * @return string Setup button text. */ protected function get_setup_button_text(): string { return esc_html__( 'Run Setup Wizard', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the text when setup is completed. * * @since 4.9.0 * * @return string Setup completed text. */ protected function get_setup_completed_text(): string { return esc_html__( 'Setup Complete', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the text when a pro-version is installed and activated. * * @since 4.9.0 * * @return string Pro installed and activated text. */ protected function get_pro_installed_activated_text(): string { return esc_html__( 'WPConsent Pro Installed & Activated', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } } Recommendations/Pages/Duplicator.php 0000644 00000033420 15252526345 0013564 0 ustar 00 <?php namespace WPMailSMTP\Admin\Recommendations\Pages; use WPMailSMTP\Admin\Recommendations\PageAbstract; /** * Duplicator recommended-plugin landing page (Backup & Migration). * * @since 4.9.0 */ class Duplicator extends PageAbstract { /** * Admin menu page slug. * * @since 4.9.0 * * @var string */ public const SLUG = 'wp-mail-smtp-recommended-duplicator'; /** * Configuration. * * @since 4.9.0 * * @var array */ protected $config = [ 'lite_plugin' => 'duplicator/duplicator.php', 'lite_wporg_url' => 'https://wordpress.org/plugins/duplicator/', 'lite_download_url' => 'https://downloads.wordpress.org/plugin/duplicator.zip', 'pro_plugin' => 'duplicator-pro/duplicator-pro.php', 'duplicator_addon' => 'duplicator-pro/duplicator-pro.php', 'duplicator_addon_page' => 'https://duplicator.com/?utm_source=wpmailsmtpplugin&utm_medium=link&utm_campaign=duplicator-page', 'duplicator_onboarding' => 'admin.php?page=duplicator', ]; /** * Get the plugin name for use in IDs, CSS classes, and config keys. * * @since 4.9.0 * * @return string Plugin name. */ protected static function get_plugin_name(): string { return 'duplicator'; // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get heading title text. * * @since 4.9.0 * * @return string Heading title. */ protected function get_heading_title(): string { return esc_html__( 'WP Mail SMTP Delivers It. Duplicator Protects It.', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get heading alt text for logo. * * @since 4.9.0 * * @return string Heading alt text. */ protected function get_heading_alt_text(): string { return esc_attr__( 'WP Mail SMTP ♥ Duplicator', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get heading description strings. * * @since 4.9.0 * * @return array Array of description strings. */ protected function get_heading_strings(): array { return [ // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement esc_html__( 'Your email settings and sender reputation live in your database. One bad update, one crash, and everything is gone. Duplicator backs up your entire WordPress site automatically so you can restore with one click.', 'wp-mail-smtp' ), esc_html__( 'Trusted by over 1.5 million websites.', 'wp-mail-smtp' ), ]; } /** * Get screenshot features list. * * @since 4.9.0 * * @return array Array of feature strings. */ protected function get_screenshot_features(): array { return [ // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement esc_html__( 'Back up your entire site automatically.', 'wp-mail-smtp' ), esc_html__( 'Restore your site with one click if anything goes wrong.', 'wp-mail-smtp' ), esc_html__( 'Store backups safely in Google Drive, Dropbox, or Amazon S3.', 'wp-mail-smtp' ), esc_html__( 'Schedule daily backups so you never have to think about it.', 'wp-mail-smtp' ), ]; } /** * Get screenshot alt text. * * @since 4.9.0 * * @return string Alt text for screenshot image. */ protected function get_screenshot_alt_text(): string { return esc_attr__( 'Duplicator screenshot', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Generate and output step 'Result' section HTML. * * @since 4.9.0 * * @noinspection HtmlUnknownTarget */ protected function output_section_step_result(): void { $step = $this->get_data_step_result(); if ( empty( $step ) ) { return; } printf( '<section class="step step-result %1$s"> <aside class="num"> <img src="%2$s" alt="%3$s" /> <i class="loader hidden"></i> </aside> <div> <h2>%4$s</h2> <p>%5$s</p> <button class="button %6$s" data-url="%7$s">%8$s</button> </div> </section>', esc_attr( $step['section_class'] ), esc_url( wp_mail_smtp()->assets_url . '/images/recommendations/' . $step['icon'] ), esc_attr__( 'Step 3', 'wp-mail-smtp' ), esc_html__( 'Set Up Scheduled Cloud Backups', 'wp-mail-smtp' ), esc_html__( 'Keep your data safe forever with automatic daily backups to Google Drive, Dropbox, or Amazon S3.', 'wp-mail-smtp' ), esc_attr( $step['button_class'] ), esc_url( admin_url( 'admin.php?page=duplicator-schedules' ) ), esc_html( $step['button_text'] ) ); } /** * Step 'Result' data. * * @since 4.9.0 * * @return array Step data. */ protected function get_data_step_result(): array { $count = $this->get_package_count(); $data = [ 'section_class' => $count ? '' : 'grey', 'button_class' => ! $count ? 'grey disabled' : 'button-primary', 'icon' => 'plugin-page/step-3.svg', 'button_text' => esc_html__( 'Set Up Cloud Backups', 'wp-mail-smtp' ), ]; if ( $count && $this->is_pro_active() ) { $data['section_class'] = ''; $data['button_class'] = 'button-primary'; if ( $this->get_schedule_count() ) { $data['icon'] = 'plugin-page/complete.svg'; $data['button_class'] = 'grey disabled'; $data['button_text'] = esc_html__( 'Cloud Backups Set Up', 'wp-mail-smtp' ); } } return $data; } /** * Get the number of Duplicator packages (backups) in the database. * * Duplicator stores backups in a custom DB table. There is no core API to * inspect custom plugin tables, so a direct query is required and the result * is cached with the object cache to limit DB hits per request. * * @since 4.9.0 * * @return int Number of packages. */ protected function get_package_count(): int { if ( ! $this->is_plugin_available() ) { return 0; } global $wpdb; $packages_table = $this->is_pro_active() ? $wpdb->prefix . 'duplicator_backups' : $wpdb->prefix . 'duplicator_packages'; $blog_id = function_exists( 'get_current_blog_id' ) ? get_current_blog_id() : 0; $table_exists_cache_key = "wpms_dup_table_exists_{$blog_id}"; $package_count_cache_key = "wpms_dup_package_count_{$blog_id}"; $table_exists = wp_cache_get( $table_exists_cache_key, 'wp-mail-smtp' ); if ( $table_exists === false ) { // PHPCS: Direct query required — no WP API exists for custom plugin tables. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $table_exists = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $packages_table ) ) ); wp_cache_set( $table_exists_cache_key, $table_exists, 'wp-mail-smtp', 60 ); } $package_count = 0; if ( $table_exists === $packages_table ) { $package_count = wp_cache_get( $package_count_cache_key, 'wp-mail-smtp' ); if ( $package_count === false ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $package_count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$packages_table}" ); wp_cache_set( $package_count_cache_key, $package_count, 'wp-mail-smtp', 60 ); } } return (int) $package_count; } /** * Count saved Duplicator backup schedules. * * The schedule model's namespace changed between Duplicator releases, so * both the current and legacy class locations are checked. * * @since 4.9.0 * * @return int Number of saved schedules. */ private function get_schedule_count(): int { $classes = [ '\Duplicator\Addons\ScheduleAddon\Models\ScheduleEntity', '\Duplicator\Models\ScheduleEntity', ]; foreach ( $classes as $class ) { if ( class_exists( $class ) && method_exists( $class, 'count' ) ) { return (int) $class::count(); } } return 0; } /** * Whether the plugin is finished setup or not. * * @since 4.9.0 * * @return bool True if the plugin is finished setup. */ protected function is_plugin_finished_setup(): bool { if ( ! $this->is_plugin_configured() ) { return false; } $count = $this->get_package_count(); $schedule_count = ( $count && $this->is_pro_active() ) ? $this->get_schedule_count() : 0; return $count && $schedule_count; } /** * Whether a plugin is configured or not. * * @since 4.9.0 * * @return bool True if plugin is configured properly. */ protected function is_plugin_configured(): bool { if ( ! $this->is_plugin_activated() ) { return false; } return $this->get_package_count() > 0; } /** * Whether a plugin is active or not. * * @since 4.9.0 * * @return bool True if plugin is active. */ protected function is_plugin_activated(): bool { return ( // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement ( defined( 'DUPLICATOR_VERSION' ) || class_exists( 'Duplicator\Plugin' ) || class_exists( 'Duplicator\Pro\Requirements' ) ) && ( is_plugin_active( $this->config['lite_plugin'] ) || is_plugin_active( $this->config['pro_plugin'] ) ) ); } /** * Whether a plugin is available (class/function exists). * * @since 4.9.0 * * @return bool True if plugin is available. */ protected function is_plugin_available(): bool { return class_exists( 'Duplicator\Plugin' ) || defined( 'DUPLICATOR_VERSION' ) || class_exists( 'DUP_PRO_Plugin' ) || defined( 'DUPLICATOR_PRO_VERSION' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Whether pro version is active. * * @since 4.9.0 * * @return bool True if pro version is active. */ protected function is_pro_active(): bool { // Duplicator Pro 4.x exposes no Pro-only class/constant (it shares // DUPLICATOR_VERSION and the Duplicator\ namespace with Lite), so detect // the active Pro plugin file directly; the class/constant checks keep // older Pro releases covered. return is_plugin_active( $this->config['pro_plugin'] ) // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement || class_exists( 'DUP_PRO_Plugin' ) || defined( 'DUPLICATOR_PRO_VERSION' ); } /** * Get the heading for the install step. * * @since 4.9.0 * * @return string Install step heading. */ protected function get_install_heading(): string { return esc_html__( 'Install & Activate Duplicator', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the description for the install step. * * @since 4.9.0 * * @return string Install step description. */ protected function get_install_description(): string { return esc_html__( 'Your first step toward bulletproof backups.', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the plugin title. * * @since 4.9.0 * * @return string Plugin title. */ protected function get_plugin_title(): string { return esc_html__( 'Duplicator', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the install button text. * * @since 4.9.0 * * @return string Install button text. */ protected function get_install_button_text(): string { return esc_html__( 'Install Duplicator', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the text when a plugin is installed and activated. * * @since 4.9.0 * * @return string Installed & activated text. */ protected function get_installed_activated_text(): string { return esc_html__( 'Duplicator Installed & Activated', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the activate button text. * * @since 4.9.0 * * @return string Activate button text. */ protected function get_activate_text(): string { return esc_html__( 'Activate Duplicator', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the heading for the setup step. * * @since 4.9.0 * * @return string Setup step heading. */ protected function get_setup_heading(): string { return esc_html__( 'Create Your First Backup', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the description for the setup step. * * @since 4.9.0 * * @return string Setup step description. */ protected function get_setup_description(): string { return esc_html__( 'Back up your site, in just one click.', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the setup button text. * * @since 4.9.0 * * @return string Setup button text. */ protected function get_setup_button_text(): string { return esc_html__( 'Create First Backup', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the text when setup is completed. * * @since 4.9.0 * * @return string Setup completed text. */ protected function get_setup_completed_text(): string { return esc_html__( 'Backup Created', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the text when a pro-version is installed and activated. * * @since 4.9.0 * * @return string Pro installed and activated text. */ protected function get_pro_installed_activated_text(): string { return esc_html__( 'Duplicator Pro Installed & Activated', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } } Recommendations/Pages/ActiveLayer.php 0000644 00000024772 15252526345 0013700 0 ustar 00 <?php namespace WPMailSMTP\Admin\Recommendations\Pages; use WPMailSMTP\Admin\Recommendations\PageAbstract; /** * ActiveLayer recommended-plugin landing page (Spam Protection). * * @since 4.9.0 */ class ActiveLayer extends PageAbstract { /** * Admin menu page slug. * * @since 4.9.0 * * @var string */ public const SLUG = 'wp-mail-smtp-recommended-activelayer'; /** * Configuration. * * @since 4.9.0 * * @var array */ protected $config = [ 'lite_plugin' => 'activelayer-anti-spam-spam-protection-for-forms-comments/activelayer-anti-spam-spam-protection-for-forms-comments.php', 'lite_wporg_url' => 'https://wordpress.org/plugins/activelayer-anti-spam-spam-protection-for-forms-comments/', 'lite_download_url' => 'https://downloads.wordpress.org/plugin/activelayer-anti-spam-spam-protection-for-forms-comments.zip', 'pro_plugin' => '', 'activelayer_addon' => '', 'activelayer_addon_page' => 'https://activelayer.com/pricing/?utm_source=wpmailsmtpplugin&utm_medium=link&utm_campaign=spam-protection-page', 'activelayer_onboarding' => 'admin.php?page=activelayer-settings', ]; /** * Get the plugin name for use in IDs, CSS classes, and config keys. * * @since 4.9.0 * * @return string Plugin name. */ protected static function get_plugin_name(): string { return 'activelayer'; // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get heading title text. * * @since 4.9.0 * * @return string Heading title. */ protected function get_heading_title(): string { return esc_html__( 'Block Spam Without Blocking Real Visitors', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get heading alt text for logo. * * @since 4.9.0 * * @return string Heading alt text. */ protected function get_heading_alt_text(): string { return esc_attr__( 'WP Mail SMTP ♥ ActiveLayer', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get heading description strings. * * @since 4.9.0 * * @return array Array of description strings. */ protected function get_heading_strings(): array { return [ // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement esc_html__( 'ActiveLayer uses server-side AI to catch spam in milliseconds, invisible to your real visitors and deadly to bots. No CAPTCHAs, no friction, no lost conversions.', 'wp-mail-smtp' ), esc_html__( 'Smarter protection for your forms, comments, and reviews.', 'wp-mail-smtp' ), ]; } /** * Get screenshot features list. * * @since 4.9.0 * * @return array Array of feature strings. */ protected function get_screenshot_features(): array { return [ // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement esc_html__( 'AI detection using content, sender reputation, and behavior signals.', 'wp-mail-smtp' ), esc_html__( 'No CAPTCHAs, no friction for real visitors.', 'wp-mail-smtp' ), esc_html__( 'Millisecond verdicts, invisible to legitimate users.', 'wp-mail-smtp' ), esc_html__( 'Works with 9+ popular form plugins out of the box.', 'wp-mail-smtp' ), ]; } /** * Get screenshot alt text. * * @since 4.9.0 * * @return string Alt text for screenshot image. */ protected function get_screenshot_alt_text(): string { return esc_attr__( 'ActiveLayer screenshot', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Generate and output step 'Result' section HTML. * * @since 4.9.0 * * @noinspection HtmlUnknownTarget */ protected function output_section_step_result(): void { $step = $this->get_data_step_result(); if ( empty( $step ) ) { return; } printf( '<section class="step step-result %1$s"> <aside class="num"> <img src="%2$s" alt="%3$s" /> <i class="loader hidden"></i> </aside> <div> <h2>%4$s</h2> <p>%5$s</p> <button class="button %6$s" data-url="%7$s">%8$s</button> </div> </section>', esc_attr( $step['section_class'] ), esc_url( wp_mail_smtp()->assets_url . '/images/recommendations/' . $step['icon'] ), esc_attr__( 'Step 3', 'wp-mail-smtp' ), esc_html__( 'Protect Even More of Your Site', 'wp-mail-smtp' ), esc_html__( 'Upgrade your plan for higher limits and advanced controls across all your forms, comments, and reviews.', 'wp-mail-smtp' ), esc_attr( $step['button_class'] ), esc_url( $step['button_url'] ), esc_html( $step['button_text'] ) ); } /** * Step 'Result' data. * * @since 4.9.0 * * @return array Step data. */ protected function get_data_step_result(): array { $step = []; $step['icon'] = 'plugin-page/step-3.svg'; $step['section_class'] = $this->output_data['plugin_setup'] ? '' : 'grey'; $step['button_text'] = esc_html__( 'Learn More', 'wp-mail-smtp' ); $step['button_url'] = $this->config['activelayer_addon_page']; $step['button_class'] = $this->output_data['plugin_setup'] ? 'button-primary' : 'grey disabled'; return $step; } /** * Whether the plugin is finished setup or not. * * @since 4.9.0 * * @return bool True if the plugin is finished setup. */ protected function is_plugin_finished_setup(): bool { return $this->is_plugin_configured(); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Whether a plugin is configured or not. * * @since 4.9.0 * * @return bool True if plugin is configured properly. */ protected function is_plugin_configured(): bool { if ( ! $this->is_plugin_activated() ) { return false; } $settings = get_option( 'activelayer_global_settings' ); return is_array( $settings ) && ! empty( $settings['api_key'] ); } /** * Whether a plugin is active or not. * * @since 4.9.0 * * @return bool True if plugin is active. */ protected function is_plugin_activated(): bool { return is_plugin_active( $this->config['lite_plugin'] ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Whether a plugin is available (class/function exists). * * @since 4.9.0 * * @return bool True if plugin is available. */ protected function is_plugin_available(): bool { return class_exists( 'ActiveLayer\Plugin' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Whether pro version is active. * * @since 4.9.0 * * @return bool True if pro version is active. */ protected function is_pro_active(): bool { return false; // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the heading for the install step. * * @since 4.9.0 * * @return string Install step heading. */ protected function get_install_heading(): string { return esc_html__( 'Install & Activate ActiveLayer', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the description for the install step. * * @since 4.9.0 * * @return string Install step description. */ protected function get_install_description(): string { return esc_html__( 'Install ActiveLayer from the WordPress.org plugin repository.', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the plugin title. * * @since 4.9.0 * * @return string Plugin title. */ protected function get_plugin_title(): string { return esc_html__( 'ActiveLayer', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the install button text. * * @since 4.9.0 * * @return string Install button text. */ protected function get_install_button_text(): string { return esc_html__( 'Install ActiveLayer', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the text when a plugin is installed and activated. * * @since 4.9.0 * * @return string Installed & activated text. */ protected function get_installed_activated_text(): string { return esc_html__( 'ActiveLayer Installed & Activated', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the activate button text. * * @since 4.9.0 * * @return string Activate button text. */ protected function get_activate_text(): string { return esc_html__( 'Activate ActiveLayer', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the heading for the setup step. * * @since 4.9.0 * * @return string Setup step heading. */ protected function get_setup_heading(): string { return esc_html__( 'Connect ActiveLayer', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the description for the setup step. * * @since 4.9.0 * * @return string Setup step description. */ protected function get_setup_description(): string { return esc_html__( 'Add your free API key on the settings page to switch on protection.', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the setup button text. * * @since 4.9.0 * * @return string Setup button text. */ protected function get_setup_button_text(): string { return esc_html__( 'Open Settings', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the text when setup is completed. * * @since 4.9.0 * * @return string Setup completed text. */ protected function get_setup_completed_text(): string { return esc_html__( 'Setup Complete', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the text when a pro-version is installed and activated. * * @since 4.9.0 * * @return string Pro installed and activated text. */ protected function get_pro_installed_activated_text(): string { return esc_html__( 'ActiveLayer Installed & Activated', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } } Recommendations/Pages/WPVibe.php 0000644 00000023775 15252526345 0012626 0 ustar 00 <?php namespace WPMailSMTP\Admin\Recommendations\Pages; use WPMailSMTP\Admin\Recommendations\PageAbstract; /** * WPVibe recommended-plugin landing page. * * @since 4.9.0 */ class WPVibe extends PageAbstract { /** * Admin menu page slug. * * @since 4.9.0 * * @var string */ public const SLUG = 'wp-mail-smtp-recommended-wpvibe'; /** * Configuration. * * @since 4.9.0 * * @var array */ protected $config = [ 'lite_plugin' => 'vibe-ai/vibe-ai.php', 'lite_wporg_url' => 'https://wordpress.org/plugins/vibe-ai/', 'lite_download_url' => 'https://downloads.wordpress.org/plugin/vibe-ai.zip', 'pro_plugin' => '', 'wpvibe_addon' => '', 'wpvibe_addon_page' => 'https://wpvibe.ai/docs', 'wpvibe_onboarding' => 'admin.php?page=vibe-ai', ]; /** * Get the plugin name for use in IDs, CSS classes, and config keys. * * @since 4.9.0 * * @return string Plugin name. */ protected static function get_plugin_name(): string { return 'wpvibe'; // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get heading title text. * * @since 4.9.0 * * @return string Heading title. */ protected function get_heading_title(): string { return esc_html__( 'Your AI Just Learned WordPress', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get heading alt text for logo. * * @since 4.9.0 * * @return string Heading alt text. */ protected function get_heading_alt_text(): string { return esc_attr__( 'WP Mail SMTP ♥ WPVibe', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get heading description strings. * * @since 4.9.0 * * @return array Array of description strings. */ protected function get_heading_strings(): array { return [ // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement esc_html__( 'WPVibe connects your AI assistant straight to your WordPress site, so you can create posts, manage plugins, and run tasks just by asking. Built-in guardrails keep your site safe.', 'wp-mail-smtp' ), esc_html__( 'Manage WordPress through conversation, not clicking.', 'wp-mail-smtp' ), ]; } /** * Get screenshot features list. * * @since 4.9.0 * * @return array Array of feature strings. */ protected function get_screenshot_features(): array { return [ // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement esc_html__( 'Works with Claude, ChatGPT, Cursor, and any MCP client.', 'wp-mail-smtp' ), esc_html__( 'Create and edit posts, pages, media, and users through conversation.', 'wp-mail-smtp' ), esc_html__( 'Run 40+ WP-CLI commands without leaving your AI chat.', 'wp-mail-smtp' ), esc_html__( 'One-click setup in about a minute.', 'wp-mail-smtp' ), ]; } /** * Get screenshot alt text. * * @since 4.9.0 * * @return string Alt text for screenshot image. */ protected function get_screenshot_alt_text(): string { return esc_attr__( 'WPVibe screenshot', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Generate and output step 'Result' section HTML. * * @since 4.9.0 * * @noinspection HtmlUnknownTarget */ protected function output_section_step_result(): void { $step = $this->get_data_step_result(); if ( empty( $step ) ) { return; } printf( '<section class="step step-result %1$s"> <aside class="num"> <img src="%2$s" alt="%3$s" /> <i class="loader hidden"></i> </aside> <div> <h2>%4$s</h2> <p>%5$s</p> <button class="button %6$s" data-url="%7$s">%8$s</button> </div> </section>', esc_attr( $step['section_class'] ), esc_url( wp_mail_smtp()->assets_url . '/images/recommendations/' . $step['icon'] ), esc_attr__( 'Step 3', 'wp-mail-smtp' ), esc_html__( 'See What Your AI Can Do', 'wp-mail-smtp' ), esc_html__( 'WPVibe is free to use. Explore the docs to see every ability your AI assistant now has for your WordPress site.', 'wp-mail-smtp' ), esc_attr( $step['button_class'] ), esc_url( $step['button_url'] ), esc_html( $step['button_text'] ) ); } /** * Step 'Result' data. * * @since 4.9.0 * * @return array Step data. */ protected function get_data_step_result(): array { $step = []; $step['icon'] = 'plugin-page/step-3.svg'; $step['section_class'] = $this->output_data['plugin_setup'] ? '' : 'grey'; $step['button_text'] = esc_html__( 'View Docs', 'wp-mail-smtp' ); $step['button_class'] = $this->output_data['plugin_setup'] ? 'button-primary' : 'grey disabled'; $step['button_url'] = $this->config['wpvibe_addon_page']; return $step; } /** * Whether the plugin is finished setup or not. * * @since 4.9.0 * * @return bool True if the plugin is finished setup. */ protected function is_plugin_finished_setup(): bool { return $this->is_plugin_configured(); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Whether a plugin is configured or not. * * @since 4.9.0 * * @return bool True if plugin is configured properly. */ protected function is_plugin_configured(): bool { if ( ! $this->is_plugin_activated() ) { return false; } return (int) get_option( 'wpvibe_last_active', 0 ) > 0; } /** * Whether a plugin is active or not. * * @since 4.9.0 * * @return bool True if plugin is active. */ protected function is_plugin_activated(): bool { return is_plugin_active( $this->config['lite_plugin'] ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Whether a plugin is available (class/function exists). * * @since 4.9.0 * * @return bool True if plugin is available. */ protected function is_plugin_available(): bool { return defined( 'WPVIBE_VERSION' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Whether pro version is active. * * @since 4.9.0 * * @return bool True if pro version is active. */ protected function is_pro_active(): bool { return false; // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the heading for the install step. * * @since 4.9.0 * * @return string Install step heading. */ protected function get_install_heading(): string { return esc_html__( 'Install & Activate WPVibe', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the description for the install step. * * @since 4.9.0 * * @return string Install step description. */ protected function get_install_description(): string { return esc_html__( 'Install WPVibe from the WordPress.org plugin repository.', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the plugin title. * * @since 4.9.0 * * @return string Plugin title. */ protected function get_plugin_title(): string { return esc_html__( 'WPVibe', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the install button text. * * @since 4.9.0 * * @return string Install button text. */ protected function get_install_button_text(): string { return esc_html__( 'Install WPVibe', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the text when a plugin is installed and activated. * * @since 4.9.0 * * @return string Installed & activated text. */ protected function get_installed_activated_text(): string { return esc_html__( 'WPVibe Installed & Activated', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the activate button text. * * @since 4.9.0 * * @return string Activate button text. */ protected function get_activate_text(): string { return esc_html__( 'Activate WPVibe', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the heading for the setup step. * * @since 4.9.0 * * @return string Setup step heading. */ protected function get_setup_heading(): string { return esc_html__( 'Connect Your AI Assistant', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the description for the setup step. * * @since 4.9.0 * * @return string Setup step description. */ protected function get_setup_description(): string { return esc_html__( 'Open WPVibe and authorize your AI client in one click.', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the setup button text. * * @since 4.9.0 * * @return string Setup button text. */ protected function get_setup_button_text(): string { return esc_html__( 'Open WPVibe', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the text when setup is completed. * * @since 4.9.0 * * @return string Setup completed text. */ protected function get_setup_completed_text(): string { return esc_html__( 'Setup Complete', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the text when a pro-version is installed and activated. * * @since 4.9.0 * * @return string Pro installed and activated text. */ protected function get_pro_installed_activated_text(): string { return esc_html__( 'WPVibe Installed & Activated', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } } Recommendations/Pages/Universally.php 0000644 00000024453 15252526345 0014001 0 ustar 00 <?php namespace WPMailSMTP\Admin\Recommendations\Pages; use WPMailSMTP\Admin\Recommendations\PageAbstract; /** * Universally recommended-plugin landing page. * * @since 4.9.0 */ class Universally extends PageAbstract { /** * Admin menu page slug. * * @since 4.9.0 * * @var string */ public const SLUG = 'wp-mail-smtp-recommended-universally'; /** * Configuration. * * @since 4.9.0 * * @var array */ protected $config = [ 'lite_plugin' => 'universally-language-translation-multilingual-tool/universally.php', 'lite_wporg_url' => 'https://wordpress.org/plugins/universally-language-translation-multilingual-tool/', 'lite_download_url' => 'https://downloads.wordpress.org/plugin/universally-language-translation-multilingual-tool.zip', 'pro_plugin' => '', 'universally_addon' => '', 'universally_addon_page' => 'https://universally.com/?utm_source=wpmailsmtpplugin&utm_medium=link&utm_campaign=translation-page', 'universally_onboarding' => 'admin.php?page=universally_settings', ]; /** * Get the plugin name for use in IDs, CSS classes, and config keys. * * @since 4.9.0 * * @return string Plugin name. */ protected static function get_plugin_name(): string { return 'universally'; // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get heading title text. * * @since 4.9.0 * * @return string Heading title. */ protected function get_heading_title(): string { return esc_html__( 'Translate Your Website and Go Global in Minutes', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get heading alt text for logo. * * @since 4.9.0 * * @return string Heading alt text. */ protected function get_heading_alt_text(): string { return esc_attr__( 'WP Mail SMTP ♥ Universally', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get heading description strings. * * @since 4.9.0 * * @return array Array of description strings. */ protected function get_heading_strings(): array { return [ // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement esc_html__( 'Universally uses AI to translate your entire WordPress site into 110+ languages in minutes, understanding context and keeping your brand voice intact. No coding required.', 'wp-mail-smtp' ), esc_html__( 'Built for global reach. Designed for everyone.', 'wp-mail-smtp' ), ]; } /** * Get screenshot features list. * * @since 4.9.0 * * @return array Array of feature strings. */ protected function get_screenshot_features(): array { return [ // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement esc_html__( 'AI translation into 110+ languages.', 'wp-mail-smtp' ), esc_html__( 'Multilingual SEO with translated URLs and meta.', 'wp-mail-smtp' ), esc_html__( 'Auto-translates new content when you publish.', 'wp-mail-smtp' ), esc_html__( 'Exclude specific pages, classes, or IDs from translation.', 'wp-mail-smtp' ), ]; } /** * Get screenshot alt text. * * @since 4.9.0 * * @return string Alt text for screenshot image. */ protected function get_screenshot_alt_text(): string { return esc_attr__( 'Universally screenshot', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Generate and output step 'Result' section HTML. * * @since 4.9.0 * * @noinspection HtmlUnknownTarget */ protected function output_section_step_result(): void { $step = $this->get_data_step_result(); if ( empty( $step ) ) { return; } printf( '<section class="step step-result %1$s"> <aside class="num"> <img src="%2$s" alt="%3$s" /> <i class="loader hidden"></i> </aside> <div> <h2>%4$s</h2> <p>%5$s</p> <button class="button %6$s" data-url="%7$s">%8$s</button> </div> </section>', esc_attr( $step['section_class'] ), esc_url( wp_mail_smtp()->assets_url . '/images/recommendations/' . $step['icon'] ), esc_attr__( 'Step 3', 'wp-mail-smtp' ), esc_html__( 'Reach Even More of the World', 'wp-mail-smtp' ), esc_html__( 'Upgrade your plan for more languages and higher translation limits.', 'wp-mail-smtp' ), esc_attr( $step['button_class'] ), esc_url( $step['button_url'] ), esc_html( $step['button_text'] ) ); } /** * Step 'Result' data. * * @since 4.9.0 * * @return array Step data. */ protected function get_data_step_result(): array { $step = []; $step['icon'] = 'plugin-page/step-3.svg'; $step['section_class'] = $this->output_data['plugin_setup'] ? '' : 'grey'; $step['button_text'] = esc_html__( 'Learn More', 'wp-mail-smtp' ); $step['button_class'] = $this->output_data['plugin_setup'] ? 'button-primary' : 'grey disabled'; $step['button_url'] = $this->config['universally_addon_page']; return $step; } /** * Whether the plugin is finished setup or not. * * @since 4.9.0 * * @return bool True if the plugin is finished setup. */ protected function is_plugin_finished_setup(): bool { return $this->is_plugin_configured(); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Whether a plugin is configured or not. * * @since 4.9.0 * * @return bool True if plugin is configured properly. */ protected function is_plugin_configured(): bool { if ( ! $this->is_plugin_activated() ) { return false; } return ! empty( get_option( 'universally_api_key' ) ); } /** * Whether a plugin is active or not. * * @since 4.9.0 * * @return bool True if plugin is active. */ protected function is_plugin_activated(): bool { return is_plugin_active( $this->config['lite_plugin'] ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Whether a plugin is available (class/function exists). * * @since 4.9.0 * * @return bool True if plugin is available. */ protected function is_plugin_available(): bool { return class_exists( 'Universally\AdminBar', false ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Whether pro version is active. * * @since 4.9.0 * * @return bool True if pro version is active. */ protected function is_pro_active(): bool { return false; // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the heading for the install step. * * @since 4.9.0 * * @return string Install step heading. */ protected function get_install_heading(): string { return esc_html__( 'Install & Activate Universally', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the description for the install step. * * @since 4.9.0 * * @return string Install step description. */ protected function get_install_description(): string { return esc_html__( 'Install Universally from the WordPress.org plugin repository.', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the plugin title. * * @since 4.9.0 * * @return string Plugin title. */ protected function get_plugin_title(): string { return esc_html__( 'Universally', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the install button text. * * @since 4.9.0 * * @return string Install button text. */ protected function get_install_button_text(): string { return esc_html__( 'Install Universally', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the text when a plugin is installed and activated. * * @since 4.9.0 * * @return string Installed & activated text. */ protected function get_installed_activated_text(): string { return esc_html__( 'Universally Installed & Activated', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the activate button text. * * @since 4.9.0 * * @return string Activate button text. */ protected function get_activate_text(): string { return esc_html__( 'Activate Universally', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the heading for the setup step. * * @since 4.9.0 * * @return string Setup step heading. */ protected function get_setup_heading(): string { return esc_html__( 'Connect Universally', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the description for the setup step. * * @since 4.9.0 * * @return string Setup step description. */ protected function get_setup_description(): string { return esc_html__( 'Add your API key on the settings page and choose the languages for your site.', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the setup button text. * * @since 4.9.0 * * @return string Setup button text. */ protected function get_setup_button_text(): string { return esc_html__( 'Open Settings', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the text when setup is completed. * * @since 4.9.0 * * @return string Setup completed text. */ protected function get_setup_completed_text(): string { return esc_html__( 'Setup Complete', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the text when a pro-version is installed and activated. * * @since 4.9.0 * * @return string Pro installed and activated text. */ protected function get_pro_installed_activated_text(): string { return esc_html__( 'Universally Installed & Activated', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } } Recommendations/Pages/WPCode.php 0000644 00000025727 15252526345 0012612 0 ustar 00 <?php namespace WPMailSMTP\Admin\Recommendations\Pages; use WPMailSMTP\Admin\Recommendations\PageAbstract; /** * WPCode recommended-plugin landing page (Code Snippets). * * @since 4.9.0 */ class WPCode extends PageAbstract { /** * Admin menu page slug. * * @since 4.9.0 * * @var string */ public const SLUG = 'wp-mail-smtp-recommended-wpcode'; /** * Configuration. * * @since 4.9.0 * * @var array */ protected $config = [ 'lite_plugin' => 'insert-headers-and-footers/ihaf.php', 'lite_wporg_url' => 'https://wordpress.org/plugins/insert-headers-and-footers/', 'lite_download_url' => 'https://downloads.wordpress.org/plugin/insert-headers-and-footers.zip', 'pro_plugin' => 'wpcode-premium/wpcode.php', 'wpcode_addon' => 'wpcode-premium/wpcode.php', 'wpcode_addon_page' => 'https://wpcode.com/lite/?utm_source=wpmailsmtpplugin&utm_medium=link&utm_campaign=code-snippets-page', 'wpcode_onboarding' => 'admin.php?page=wpcode', ]; /** * Get the plugin name for use in IDs, CSS classes, and config keys. * * @since 4.9.0 * * @return string Plugin name. */ protected static function get_plugin_name(): string { return 'wpcode'; // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get heading title text. * * @since 4.9.0 * * @return string Heading title. */ protected function get_heading_title(): string { return esc_html__( 'Add Custom Code to WordPress Without Breaking Your Site', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get heading alt text for logo. * * @since 4.9.0 * * @return string Heading alt text. */ protected function get_heading_alt_text(): string { return esc_attr__( 'WP Mail SMTP ♥ WPCode', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get heading description strings. * * @since 4.9.0 * * @return array Array of description strings. */ protected function get_heading_strings(): array { return [ // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement esc_html__( 'WPCode lets you safely add PHP, JavaScript, CSS, and HTML snippets with no theme edits and no risk of losing changes when your theme updates. Built-in error handling keeps your site from breaking.', 'wp-mail-smtp' ), esc_html__( 'Trusted by over 3 million websites.', 'wp-mail-smtp' ), ]; } /** * Get screenshot features list. * * @since 4.9.0 * * @return array Array of feature strings. */ protected function get_screenshot_features(): array { return [ // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement esc_html__( 'Add snippets without editing theme files.', 'wp-mail-smtp' ), esc_html__( 'PHP, JavaScript, CSS, and HTML all supported.', 'wp-mail-smtp' ), esc_html__( '100+ ready-made snippets for common tasks.', 'wp-mail-smtp' ), esc_html__( 'Auto-insert and one-click toggle for every snippet.', 'wp-mail-smtp' ), ]; } /** * Get screenshot alt text. * * @since 4.9.0 * * @return string Alt text for screenshot image. */ protected function get_screenshot_alt_text(): string { return esc_attr__( 'WPCode screenshot', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Generate and output step 'Result' section HTML. * * @since 4.9.0 * * @noinspection HtmlUnknownTarget */ protected function output_section_step_result(): void { $step = $this->get_data_step_result(); if ( empty( $step ) ) { return; } printf( '<section class="step step-result %1$s"> <aside class="num"> <img src="%2$s" alt="%3$s" /> <i class="loader hidden"></i> </aside> <div> <h2>%4$s</h2> <p>%5$s</p> <button class="button %6$s" data-url="%7$s">%8$s</button> </div> </section>', esc_attr( $step['section_class'] ), esc_url( wp_mail_smtp()->assets_url . '/images/recommendations/' . $step['icon'] ), esc_attr__( 'Step 3', 'wp-mail-smtp' ), esc_html__( 'Get Advanced Code Snippet Features', 'wp-mail-smtp' ), esc_html__( 'WPCode Pro adds conditional logic, a cloud snippet library, revisions, and conversion pixels to your workflow.', 'wp-mail-smtp' ), esc_attr( $step['button_class'] ), esc_url( $step['button_url'] ), esc_html( $step['button_text'] ) ); } /** * Step 'Result' data. * * @since 4.9.0 * * @return array Step data. */ protected function get_data_step_result(): array { $step = []; $step['icon'] = 'plugin-page/step-3.svg'; $step['section_class'] = $this->output_data['plugin_setup'] ? '' : 'grey'; $step['button_text'] = esc_html__( 'Learn More', 'wp-mail-smtp' ); $step['button_class'] = 'grey disabled'; $step['button_url'] = ''; if ( ! $this->is_pro_active() ) { $step['button_url'] = $this->config['wpcode_addon_page']; $step['button_class'] = $this->output_data['plugin_setup'] ? 'button-primary' : 'grey disabled'; } else { $addon_installed = array_key_exists( $this->config['wpcode_addon'], $this->output_data['all_plugins'] ); $step['button_text'] = $addon_installed ? esc_html__( 'WPCode Pro Installed & Activated', 'wp-mail-smtp' ) : esc_html__( 'Install Now', 'wp-mail-smtp' ); $step['button_class'] = $this->output_data['plugin_setup'] ? 'grey disabled' : 'button-primary'; $step['icon'] = $addon_installed ? 'plugin-page/complete.svg' : 'plugin-page/step-3.svg'; } return $step; } /** * Whether the plugin is finished setup or not. * * @since 4.9.0 * * @return bool True if the plugin is finished setup. */ protected function is_plugin_finished_setup(): bool { if ( ! $this->is_plugin_configured() ) { return false; } return $this->is_pro_active(); } /** * Whether a plugin is configured or not. * * @since 4.9.0 * * @return bool True if plugin is configured properly. */ protected function is_plugin_configured(): bool { if ( ! $this->is_plugin_activated() ) { return false; } $activated = get_option( 'ihaf_activated' ); return is_array( $activated ) && ! empty( $activated['wpcode'] ); } /** * Whether a plugin is active or not. * * @since 4.9.0 * * @return bool True if plugin is active. */ protected function is_plugin_activated(): bool { return ( // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement function_exists( 'WPCode' ) && ( is_plugin_active( $this->config['lite_plugin'] ) || is_plugin_active( $this->config['pro_plugin'] ) ) ); } /** * Whether a plugin is available (class/function exists). * * @since 4.9.0 * * @return bool True if plugin is available. */ protected function is_plugin_available(): bool { return function_exists( 'WPCode' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Whether pro version is active. * * @since 4.9.0 * * @return bool True if pro version is active. */ protected function is_pro_active(): bool { return class_exists( 'WPCode_License' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the heading for the install step. * * @since 4.9.0 * * @return string Install step heading. */ protected function get_install_heading(): string { return esc_html__( 'Install & Activate WPCode', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the description for the install step. * * @since 4.9.0 * * @return string Install step description. */ protected function get_install_description(): string { return esc_html__( 'Install WPCode from the WordPress.org plugin repository.', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the plugin title. * * @since 4.9.0 * * @return string Plugin title. */ protected function get_plugin_title(): string { return esc_html__( 'WPCode', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the install button text. * * @since 4.9.0 * * @return string Install button text. */ protected function get_install_button_text(): string { return esc_html__( 'Install WPCode', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the text when a plugin is installed and activated. * * @since 4.9.0 * * @return string Installed & activated text. */ protected function get_installed_activated_text(): string { return esc_html__( 'WPCode Installed & Activated', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the activate button text. * * @since 4.9.0 * * @return string Activate button text. */ protected function get_activate_text(): string { return esc_html__( 'Activate WPCode', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the heading for the setup step. * * @since 4.9.0 * * @return string Setup step heading. */ protected function get_setup_heading(): string { return esc_html__( 'Set Up WPCode', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the description for the setup step. * * @since 4.9.0 * * @return string Setup step description. */ protected function get_setup_description(): string { return esc_html__( 'Open WPCode to create your first snippet and start customizing your site safely.', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the setup button text. * * @since 4.9.0 * * @return string Setup button text. */ protected function get_setup_button_text(): string { return esc_html__( 'Open WPCode', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the text when setup is completed. * * @since 4.9.0 * * @return string Setup completed text. */ protected function get_setup_completed_text(): string { return esc_html__( 'Setup Complete', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } /** * Get the text when a pro-version is installed and activated. * * @since 4.9.0 * * @return string Pro installed and activated text. */ protected function get_pro_installed_activated_text(): string { return esc_html__( 'WPCode Pro Installed & Activated', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement } } Recommendations/Migration.php 0000644 00000011241 15252526345 0012345 0 ustar 00 <?php namespace WPMailSMTP\Admin\Recommendations; use WPMailSMTP\MigrationAbstract; /** * Recommended-plugins migration class. * * @since 4.9.0 */ class Migration extends MigrationAbstract { /** * Version of the latest migration for recommended plugins. * * @since 4.9.0 */ const DB_VERSION = 1; /** * Option key where we save the current recommended-plugins migration version. * * @since 4.9.0 */ const OPTION_NAME = 'wp_mail_smtp_recommendations_migration_version'; /** * Option key where we save any errors during recommended-plugins migration. * * @since 4.9.0 */ const ERROR_OPTION_NAME = 'wp_mail_smtp_recommendations_migration_error'; /** * Backfill first-activation timestamps for already-installed partner plugins. * * Reads each partner's own activation option so existing installs are not * re-promoted a product the user already uses. * * @since 4.9.0 */ protected function migrate_to_1() { $seeded = []; $this->check_wpconsent_activation( $seeded ); $this->check_duplicator_activation( $seeded ); $this->check_wpcode_activation( $seeded ); $this->seed_active_plugins_as_adopted( $seeded ); // Only write the option when there is something to seed. // Use add_option (not update_option) so we never clobber a value the // live activated_plugin hook may already have written. if ( ! empty( $seeded ) ) { add_option( RecommendedPlugins::ACTIVATED_OPTION, $seeded ); } $this->update_db_ver( 1 ); } /** * Seed WPConsent activation timestamp from the partner's own option. * * @since 4.9.0 * * @param array $seeded Array of slug => timestamp being built. Passed by reference. */ private function check_wpconsent_activation( &$seeded ) { $value = get_option( 'wpconsent_activated', [] ); if ( empty( $value ) || ! is_array( $value ) ) { return; } // Lite stores its timestamp under 'wpconsent'; Pro under 'wpconsent_pro'. $timestamp = isset( $value['wpconsent'] ) ? $value['wpconsent'] : ''; if ( empty( $timestamp ) ) { $timestamp = isset( $value['wpconsent_pro'] ) ? $value['wpconsent_pro'] : ''; } if ( ! empty( $timestamp ) ) { $seeded['wpconsent'] = (int) $timestamp; } } /** * Seed Duplicator activation timestamp from the partner's own option. * * @since 4.9.0 * * @param array $seeded Array of slug => timestamp being built. Passed by reference. */ private function check_duplicator_activation( &$seeded ) { $install_info = get_option( 'duplicator_install_info' ); $timestamp = ( is_array( $install_info ) && ! empty( $install_info['time'] ) ) ? $install_info['time'] : null; // Fall back to Duplicator Pro install info when the Lite timestamp is absent. if ( empty( $timestamp ) ) { $pro_install_info = get_option( 'duplicator_pro_install_info' ); $timestamp = ( is_array( $pro_install_info ) && ! empty( $pro_install_info['time'] ) ) ? $pro_install_info['time'] : null; } if ( ! empty( $timestamp ) ) { $seeded['duplicator'] = (int) $timestamp; } } /** * Seed WPCode activation timestamp from the partner's own option. * * @since 4.9.0 * * @param array $seeded Array of slug => timestamp being built. Passed by reference. */ private function check_wpcode_activation( &$seeded ) { $value = get_option( 'ihaf_activated', [] ); if ( empty( $value ) || ! is_array( $value ) ) { return; } $timestamp = isset( $value['wpcode'] ) ? $value['wpcode'] : ''; if ( ! empty( $timestamp ) ) { $seeded['wpcode'] = (int) $timestamp; } } /** * Mark any currently-active catalog plugin not already recorded as adopted. * * Pre-existing installs should not be promoted after upgrade. Products that * expose no activation-time option can't be backdated from their own data, * so detect an active install directly and stamp it past the rotation * window, making it count as already adopted. * * @since 4.9.0 * * @param array $seeded Array of slug => timestamp being built. Passed by reference. */ private function seed_active_plugins_as_adopted( &$seeded ) { if ( ! function_exists( 'is_plugin_active' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $adopted_time = time() - RecommendedPlugins::ADOPTED_AFTER - DAY_IN_SECONDS; foreach ( ( new RecommendedPlugins() )->get_products() as $product ) { if ( isset( $seeded[ $product['slug'] ] ) ) { continue; } $files = array_filter( [ isset( $product['plugin'] ) ? $product['plugin'] : '', isset( $product['plugin_pro'] ) ? $product['plugin_pro'] : '', ] ); foreach ( $files as $file ) { if ( is_plugin_active( $file ) ) { $seeded[ $product['slug'] ] = $adopted_time; break; } } } } } ParentPageAbstract.php 0000644 00000016703 15252526345 0011007 0 ustar 00 <?php namespace WPMailSMTP\Admin; use WPMailSMTP\WP; /** * Class ParentPageAbstract. * * @since 2.8.0 */ abstract class ParentPageAbstract implements PageInterface { /** * Slug of a page. * * @since 2.8.0 * * @var string */ protected $slug; /** * Page tabs. * * @since 2.8.0 * * @var PageAbstract[] */ protected $tabs = []; /** * Page default tab slug. * * @since 2.8.0 * * @var string */ protected $default_tab = ''; /** * Constructor. * * @since 2.8.0 * * @param array $tabs Page tabs. */ public function __construct( $tabs = [] ) { /** * Filters parent page tabs. * * @since 2.8.0 * * @param string[] $tabs Parent page tabs. */ $tabs = apply_filters( 'wp_mail_smtp_admin_page_' . $this->slug . '_tabs', $tabs ); if ( wp_mail_smtp()->get_admin()->is_admin_page( $this->slug ) ) { $this->init_tabs( $tabs ); $this->hooks(); } if ( WP::is_doing_self_ajax() ) { $this->init_ajax( $tabs ); } } /** * Hooks. * * @since 2.8.0 */ protected function hooks() { add_action( 'admin_init', [ $this, 'process_actions' ] ); // Register tab related hooks. if ( isset( $this->tabs[ $this->get_current_tab() ] ) ) { $this->tabs[ $this->get_current_tab() ]->hooks(); } } /** * Initialize ajax actions. * * @since 3.0.0 * * @param array $tabs Page tabs. */ private function init_ajax( $tabs ) { foreach ( $tabs as $tab ) { if ( $this->is_valid_tab( $tab ) ) { ( new $tab( $this ) )->ajax(); } } } /** * Get the page slug. * * @since 2.8.0 * * @return string */ public function get_slug() { return $this->slug; } /** * Get the page tabs. * * @since 2.8.0 * * @return PageAbstract[] */ public function get_tabs() { return $this->tabs; } /** * Get the page tabs slugs. * * @since 2.8.0 * * @return string[] */ public function get_tabs_slugs() { return array_map( function ( $tab ) { return $tab->get_slug(); }, $this->tabs ); } /** * Get the page/tab link. * * @since 2.8.0 * * @param string $tab Tab to generate a link to. * * @return string */ public function get_link( $tab = '' ) { return add_query_arg( 'tab', $this->get_defined_tab( $tab ), WP::admin_url( 'admin.php?page=' . Area::SLUG . '-' . $this->slug ) ); } /** * Get the current tab. * * @since 2.8.0 * * @return string */ public function get_current_tab() { $tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended return $this->get_defined_tab( $tab ); } /** * Get the tab label. * * @since 2.9.0 * * @param string $tab Tab key. * * @return string */ public function get_tab_label( $tab ) { $tabs = $this->get_tabs(); return isset( $tabs[ $tab ] ) ? $tabs[ $tab ]->get_label() : ''; } /** * Get the tab title. * * @since 2.9.0 * * @param string $tab Tab key. * * @return string */ public function get_tab_title( $tab ) { $tabs = $this->get_tabs(); return isset( $tabs[ $tab ] ) ? $tabs[ $tab ]->get_title() : ''; } /** * Get the defined or default tab. * * @since 2.8.0 * * @param string $tab Tab to check. * * @return string Defined tab. Fallback to default one if it doesn't exist. */ protected function get_defined_tab( $tab ) { $tab = sanitize_key( $tab ); return in_array( $tab, $this->get_tabs_slugs(), true ) ? $tab : $this->default_tab; } /** * Initialize tabs. * * @since 2.8.0 * * @param array $tabs Page tabs. */ public function init_tabs( $tabs ) { foreach ( $tabs as $key => $tab ) { if ( ! $this->is_valid_tab( $tab ) ) { continue; } $this->tabs[ $key ] = new $tab( $this ); } // Sort tabs by priority. $this->sort_tabs(); } /** * All possible plugin forms manipulation and hooks registration will be done here. * * @since 2.8.0 */ public function process_actions() { $tabs = $this->get_tabs_slugs(); // Allow to process only own tabs. if ( ! array_key_exists( $this->get_current_tab(), $tabs ) ) { return; } // Process POST only if it exists. // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash if ( ! empty( $_POST ) && isset( $_POST['wp-mail-smtp-post'] ) ) { $current_tab = $this->tabs[ $this->get_current_tab() ]; // Verify nonce. $current_tab->check_admin_referer(); // Verify capability. // Per-site cap is intentional: the only tab routed here that writes to the global option store (Debug Events) gates itself with manage_network_options. if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_options() ) ) { wp_die( esc_html__( 'You don\'t have the capability to perform this action.', 'wp-mail-smtp' ) ); } if ( ! empty( $_POST['wp-mail-smtp'] ) ) { $post = $_POST['wp-mail-smtp']; } else { $post = []; } $current_tab->process_post( $post ); } // phpcs:enable // This won't do anything for most pages. // Works for plugin page only, when GET params are allowed. $this->tabs[ $this->get_current_tab() ]->process_auth(); } /** * Display page content based on the current tab. * * @since 2.8.0 */ public function display() { $current_tab = $this->get_current_tab(); $page_slug = $this->slug; ?> <div class="wp-mail-smtp-page-title"> <?php if ( count( $this->tabs ) > 1 ) : ?> <?php foreach ( $this->tabs as $tab ) : ?> <a href="<?php echo esc_url( $tab->get_link() ); ?>" class="tab <?php echo $current_tab === $tab->get_slug() ? 'active' : ''; ?>"> <?php echo esc_html( $tab->get_label() ); ?> </a> <?php endforeach; ?> <?php else : ?> <span class="page-title"><?php echo esc_html( array_values( $this->tabs )[0]->get_title() ); ?></span> <?php endif; ?> <?php /** * Fires after page title. * * @since 2.9.0 * * @param ParentPageAbstract $page Current page. */ do_action( "wp_mail_smtp_admin_page_{$page_slug}_{$current_tab}_display_header", $this ); ?> </div> <div class="wp-mail-smtp-page-content"> <?php foreach ( $this->tabs as $tab ) { if ( $tab->get_slug() === $current_tab ) { printf( '<h1 class="screen-reader-text">%s</h1>', esc_html( $tab->get_title() ) ); /** * Fires before tab content. * * @since 2.8.0 * * @param PageAbstract $tab Current tab. */ do_action( 'wp_mail_smtp_admin_pages_before_content', $tab ); /** * Fires before tab content. * * @since 2.9.0 * * @param PageAbstract $tab Current tab. */ do_action( "wp_mail_smtp_admin_page_{$page_slug}_{$current_tab}_display_before", $tab ); $tab->display(); /** * Fires after tab content. * * @since 2.8.0 * * @param PageAbstract $tab Current tab. */ do_action( "wp_mail_smtp_admin_page_{$page_slug}_{$current_tab}_display_after", $tab ); break; } } ?> </div> <?php } /** * Sort tabs by priority. * * @since 2.8.0 */ protected function sort_tabs() { uasort( $this->tabs, function ( $a, $b ) { return ( $a->get_priority() < $b->get_priority() ) ? - 1 : 1; } ); } /** * Whether tab is valid. * * @since 3.0.0 * * @param array $tab Page tab. * * @return bool */ private function is_valid_tab( $tab ) { return is_subclass_of( $tab, '\WPMailSMTP\Admin\PageAbstract' ); } } DashboardWidget.php 0000644 00000053605 15252526345 0010332 0 ustar 00 <?php namespace WPMailSMTP\Admin; use WPMailSMTP\Admin\DebugEvents\DebugEvents; use WPMailSMTP\Helpers\Helpers; use WPMailSMTP\Options; use WPMailSMTP\WP; use WPMailSMTP\Reports\Reports; use WPMailSMTP\Reports\Emails\Summary as SummaryReportEmail; /** * Dashboard Widget shows the number of sent emails in WP Dashboard. * * @since 2.9.0 */ class DashboardWidget { /** * Instance slug. * * @since 2.9.0 * * @const string */ const SLUG = 'dash_widget_lite'; /** * The WP option key for storing the total number of sent emails. * * @since 2.9.0 * @since 3.0.0 Constant moved to Reports class. * * @const string */ const SENT_EMAILS_COUNTER_OPTION_KEY = Reports::SENT_EMAILS_COUNTER_OPTION_KEY; /** * Constructor. * * @since 2.9.0 */ public function __construct() {} /** * Init class. * * @since 2.9.0 */ public function init() { // Prevent the class initialization, if the dashboard widget hidden setting is enabled. if ( Options::init()->get( 'general', 'dashboard_widget_hidden' ) ) { return; } add_action( 'admin_init', function() { // This widget should be displayed for certain high-level users only. if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_options() ) ) { return; } /** * Filters whether the initialization of the dashboard widget should be allowed. * * @since 2.9.0 * * @param bool $var If the dashboard widget should be initialized. */ if ( ! apply_filters( 'wp_mail_smtp_admin_dashboard_widget', '__return_true' ) ) { return; } $this->hooks(); } ); } /** * Widget hooks. * * @since 2.9.0 */ public function hooks() { add_action( 'admin_enqueue_scripts', [ $this, 'widget_scripts' ] ); add_action( 'wp_dashboard_setup', [ $this, 'widget_register' ] ); add_action( 'wp_ajax_wp_mail_smtp_' . static::SLUG . '_save_widget_meta', [ $this, 'save_widget_meta_ajax' ] ); add_action( 'wp_ajax_wp_mail_smtp_' . static::SLUG . '_enable_summary_report_email', [ $this, 'enable_summary_report_email_ajax', ] ); } /** * Load widget-specific scripts. * Load them only on the admin dashboard page. * * @since 2.9.0 */ public function widget_scripts() { $screen = get_current_screen(); if ( ! isset( $screen->id ) || 'dashboard' !== $screen->id ) { return; } $min = WP::asset_min(); wp_enqueue_style( 'wp-mail-smtp-dashboard-widget', wp_mail_smtp()->assets_url . '/css/dashboard-widget.min.css', [], WPMS_PLUGIN_VER ); wp_enqueue_script( 'wp-mail-smtp-chart', wp_mail_smtp()->assets_url . '/js/vendor/chart.min.js', [ 'moment' ], '4.4.9', true ); wp_enqueue_script( 'wp-mail-smtp-chart-adapter', wp_mail_smtp()->assets_url . '/js/vendor/chartjs-adapter-moment.min.js', [ 'moment', 'wp-mail-smtp-chart' ], '1.0.1', true ); wp_enqueue_script( 'wp-mail-smtp-dashboard-widget', wp_mail_smtp()->assets_url . "/js/smtp-dashboard-widget{$min}.js", [ 'jquery', 'wp-mail-smtp-chart', 'wp-mail-smtp-chart-adapter' ], WPMS_PLUGIN_VER, true ); wp_localize_script( 'wp-mail-smtp-dashboard-widget', 'wp_mail_smtp_dashboard_widget', [ 'slug' => static::SLUG, 'nonce' => wp_create_nonce( 'wp_mail_smtp_' . static::SLUG . '_nonce' ), ] ); } /** * Register the widget. * * @since 2.9.0 */ public function widget_register() { global $wp_meta_boxes; $widget_key = 'wp_mail_smtp_reports_widget_lite'; wp_add_dashboard_widget( $widget_key, esc_html__( 'WP Mail SMTP', 'wp-mail-smtp' ), [ $this, 'widget_content' ] ); // Attempt to place the widget at the top. $normal_dashboard = $wp_meta_boxes['dashboard']['normal']['core']; if ( isset( $normal_dashboard[ $widget_key ] ) ) { $widget_instance = [ $widget_key => $normal_dashboard[ $widget_key ] ]; unset( $normal_dashboard[ $widget_key ] ); $sorted_dashboard = array_merge( $widget_instance, $normal_dashboard ); //phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited $wp_meta_boxes['dashboard']['normal']['core'] = $sorted_dashboard; } } /** * Save a widget meta for a current user using AJAX. * * @since 2.9.0 */ public function save_widget_meta_ajax() { check_admin_referer( 'wp_mail_smtp_' . static::SLUG . '_nonce' ); if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_options() ) ) { wp_send_json_error(); } $meta = ! empty( $_POST['meta'] ) ? sanitize_key( $_POST['meta'] ) : ''; $value = ! empty( $_POST['value'] ) ? sanitize_key( $_POST['value'] ) : 0; $this->widget_meta( 'set', $meta, $value ); wp_send_json_success(); } /** * Enable summary report email using AJAX. * * @since 3.0.0 */ public function enable_summary_report_email_ajax() { check_admin_referer( 'wp_mail_smtp_' . static::SLUG . '_nonce' ); if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) { wp_send_json_error(); } $options = Options::init(); $data = [ 'general' => [ SummaryReportEmail::SETTINGS_SLUG => false, ], ]; $options->set( $data, false, false ); wp_send_json_success(); } /** * Load widget content. * * @since 2.9.0 */ public function widget_content() { echo '<div class="wp-mail-smtp-dash-widget wp-mail-smtp-dash-widget--lite">'; $this->widget_content_html(); echo '</div>'; } /** * Increment the number of total emails sent by 1. * * @deprecated 3.0.0 * * @since 2.9.0 */ public function increment_sent_email_counter() { _deprecated_function( __METHOD__, '3.0.0' ); } /** * Widget content HTML. * * @since 2.9.0 */ private function widget_content_html() { $hide_graph = (bool) $this->widget_meta( 'get', 'hide_graph' ); ?> <?php if ( ! $hide_graph ) : ?> <div class="wp-mail-smtp-dash-widget-chart-block-container"> <div class="wp-mail-smtp-dash-widget-block wp-mail-smtp-dash-widget-chart-block"> <canvas id="wp-mail-smtp-dash-widget-chart" width="554" height="291"></canvas> <div class="wp-mail-smtp-dash-widget-chart-upgrade"> <div class="wp-mail-smtp-dash-widget-modal"> <a href="#" class="wp-mail-smtp-dash-widget-dismiss-chart-upgrade"> <span class="dashicons dashicons-no-alt"></span> </a> <h2><?php esc_html_e( 'View Detailed Email Stats', 'wp-mail-smtp' ); ?></h2> <p><?php esc_html_e( 'Automatically keep track of every email sent from your WordPress site and view valuable statistics right here in your dashboard.', 'wp-mail-smtp' ); ?></p> <p> <a href="<?php echo esc_url( wp_mail_smtp()->get_upgrade_link( [ 'medium' => 'dashboard-widget', 'content' => 'upgrade-to-wp-mail-smtp-pro' ] ) ); // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound ?>" target="_blank" rel="noopener noreferrer" class="button button-primary button-hero"> <?php esc_html_e( 'Upgrade to WP Mail SMTP Pro', 'wp-mail-smtp' ); ?> </a> </p> </div> </div> <div class="wp-mail-smtp-dash-widget-overlay"></div> </div> </div> <?php endif; ?> <div class="wp-mail-smtp-dash-widget-block wp-mail-smtp-dash-widget-block-settings"> <div> <?php $this->email_types_select_html(); ?> </div> <div> <?php $this->timespan_select_html(); $this->widget_settings_html(); ?> </div> </div> <div id="wp-mail-smtp-dash-widget-email-stats-block" class="wp-mail-smtp-dash-widget-block wp-mail-smtp-dash-widget-email-stats-block"> <?php $this->email_stats_block(); ?> </div> <?php $this->display_after_email_stats_block_content(); } /** * Display the content after the email stats block. * * @since 3.9.0 * * @return void */ private function display_after_email_stats_block_content() { if ( empty( $this->widget_meta( 'get', 'hide_email_alerts_banner' ) ) ) { // Check if we have error debug events. $error_debug_events_count = DebugEvents::get_error_debug_events_count(); if ( ! is_wp_error( $error_debug_events_count ) && ! empty( $error_debug_events_count ) ) { $this->show_email_alerts_banner( $error_debug_events_count ); return; } } $hide_summary_report_email_block = (bool) $this->widget_meta( 'get', 'hide_summary_report_email_block' ); if ( SummaryReportEmail::is_disabled() && ! $hide_summary_report_email_block && current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) { $this->show_summary_report_email_block(); } $this->show_upgrade_footer(); } /** * Display the email alerts banner. * * @since 3.9.0 * * @param int $error_count The number of debug events error. * * @return void */ private function show_email_alerts_banner( $error_count ) { ?> <div id="wp-mail-smtp-dash-widget-email-alerts-education" class="wp-mail-smtp-dash-widget-block wp-mail-smtp-dash-widget-email-alerts-education"> <div class="wp-mail-smtp-dash-widget-email-alerts-education-error-icon"> <?php printf( '<img src="%s" alt="%s"/>', esc_url( wp_mail_smtp()->assets_url . '/images/dash-widget/error-icon.svg' ), esc_attr__( 'Error icon', 'wp-mail-smtp' ) ); ?> </div> <div class="wp-mail-smtp-dash-widget-email-alerts-education-content"> <?php $error_title = sprintf( /* translators: %d - number of failed emails. */ _n( 'We detected %d failed email in the last 30 days.', 'We detected %d failed emails in the last 30 days.', $error_count, 'wp-mail-smtp' ), $error_count ); $error_content = sprintf( /* translators: %s - URL to WPMailSMTP.com. */ __( '<a href="%s" target="_blank" rel="noopener noreferrer">Upgrade to Pro</a> and get instant alert notifications when they fail.', 'wp-mail-smtp' ), esc_url( wp_mail_smtp()->get_upgrade_link( [ 'medium' => 'dashboard-widget', 'content' => 'alerts-promo-upgrade-to-pro' ] ) ) // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound ); ?> <p> <strong><?php echo esc_html( $error_title ); ?></strong><br /> <?php echo wp_kses( $error_content, [ 'a' => [ 'href' => [], 'target' => [], 'rel' => [], ], ] ); ?> </p> </div> <button type="button" id="wp-mail-smtp-dash-widget-dismiss-email-alert-block" class="wp-mail-smtp-dash-widget-dismiss-email-alert-block" title="<?php esc_attr_e( 'Dismiss email alert block', 'wp-mail-smtp' ); ?>"> <span class="dashicons dashicons-no-alt"></span> </button> </div> <?php } /** * Show the summary report email block. * * @since 3.9.0 * * @return void */ private function show_summary_report_email_block() { ?> <div id="wp-mail-smtp-dash-widget-summary-report-email-block" class="wp-mail-smtp-dash-widget-block wp-mail-smtp-dash-widget-summary-report-email-block"> <div> <div class="wp-mail-smtp-dash-widget-summary-report-email-block-setting"> <label for="wp-mail-smtp-dash-widget-summary-report-email-enable"> <input type="checkbox" id="wp-mail-smtp-dash-widget-summary-report-email-enable"> <i class="wp-mail-smtp-dash-widget-loader"></i> <span> <?php echo wp_kses( __( '<b>NEW!</b> Enable Weekly Email Summaries', 'wp-mail-smtp' ), [ 'b' => [], ] ); ?> </span> </label> <a href="<?php echo esc_url( SummaryReportEmail::get_preview_link() ); ?>" target="_blank"> <?php esc_html_e( 'View Example', 'wp-mail-smtp' ); ?> </a> <i class="dashicons dashicons-dismiss wp-mail-smtp-dash-widget-summary-report-email-dismiss"></i> </div> <div class="wp-mail-smtp-dash-widget-summary-report-email-block-applied hidden"> <i class="wp-mail-smtp-dashicons-yes-alt-green"></i> <span><?php esc_attr_e( 'Weekly Email Summaries have been enabled', 'wp-mail-smtp' ); ?></span> </div> </div> </div> <?php } /** * Show the upgrade footer. * * @since 3.9.0 * * @return void */ private function show_upgrade_footer() { $hide_graph = (bool) $this->widget_meta( 'get', 'hide_graph' ); ?> <div id="wp-mail-smtp-dash-widget-upgrade-footer" class="wp-mail-smtp-dash-widget-block wp-mail-smtp-dash-widget-upgrade-footer wp-mail-smtp-dash-widget-upgrade-footer--<?php echo ! $hide_graph ? 'hide' : 'show'; ?>"> <p> <?php printf( wp_kses( /* translators: %s - URL to WPMailSMTP.com. */ __( '<a href="%s" target="_blank" rel="noopener noreferrer">Upgrade to Pro</a> for detailed stats, email logs, and more!', 'wp-mail-smtp' ), [ 'a' => [ 'href' => [], 'rel' => [], 'target' => [], ], ] ), // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound esc_url( wp_mail_smtp()->get_upgrade_link( [ 'medium' => 'dashboard-widget', 'content' => 'upgrade-to-pro' ] ) ) ); ?> </p> </div> <?php } /** * Timespan select HTML. * * @since 2.9.0 */ private function timespan_select_html() { ?> <select id="wp-mail-smtp-dash-widget-timespan" class="wp-mail-smtp-dash-widget-select-timespan" title="<?php esc_attr_e( 'Select timespan', 'wp-mail-smtp' ); ?>"> <option value="all"> <?php esc_html_e( 'All Time', 'wp-mail-smtp' ); ?> </option> <?php foreach ( [ 7, 14, 30 ] as $option ) : ?> <option value="<?php echo absint( $option ); ?>" disabled> <?php /* translators: %d - Number of days. */ ?> <?php echo esc_html( sprintf( _n( 'Last %d day', 'Last %d days', absint( $option ), 'wp-mail-smtp' ), absint( $option ) ) ); ?> </option> <?php endforeach; ?> </select> <?php } /** * Email types select HTML. * * @since 2.9.0 */ private function email_types_select_html() { $options = [ 'delivered' => esc_html__( 'Confirmed Emails', 'wp-mail-smtp' ), 'sent' => esc_html__( 'Unconfirmed Emails', 'wp-mail-smtp' ), 'unsent' => esc_html__( 'Failed Emails', 'wp-mail-smtp' ), ]; if ( Helpers::mailer_without_send_confirmation() ) { unset( $options['sent'] ); $options['delivered'] = esc_html__( 'Sent Emails', 'wp-mail-smtp' ); } ?> <select id="wp-mail-smtp-dash-widget-email-type" class="wp-mail-smtp-dash-widget-select-email-type" title="<?php esc_attr_e( 'Select email type', 'wp-mail-smtp' ); ?>"> <option value="all"> <?php esc_html_e( 'All Emails', 'wp-mail-smtp' ); ?> </option> <?php foreach ( $options as $key => $title ) : ?> <option value="<?php echo sanitize_key( $key ); ?>" disabled> <?php echo esc_html( $title ); ?> </option> <?php endforeach; ?> </select> <?php } /** * Widget settings HTML. * * @since 2.9.0 */ private function widget_settings_html() { ?> <div class="wp-mail-smtp-dash-widget-settings-container"> <button id="wp-mail-smtp-dash-widget-settings-button" class="wp-mail-smtp-dash-widget-settings-button button" type="button"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19 19"> <path d="M18,11l-2.18,0c-0.17,0.7 -0.44,1.35 -0.81,1.93l1.54,1.54l-2.1,2.1l-1.54,-1.54c-0.58,0.36 -1.23,0.63 -1.91,0.79l0,2.18l-3,0l0,-2.18c-0.68,-0.16 -1.33,-0.43 -1.91,-0.79l-1.54,1.54l-2.12,-2.12l1.54,-1.54c-0.36,-0.58 -0.63,-1.23 -0.79,-1.91l-2.18,0l0,-2.97l2.17,0c0.16,-0.7 0.44,-1.35 0.8,-1.94l-1.54,-1.54l2.1,-2.1l1.54,1.54c0.58,-0.37 1.24,-0.64 1.93,-0.81l0,-2.18l3,0l0,2.18c0.68,0.16 1.33,0.43 1.91,0.79l1.54,-1.54l2.12,2.12l-1.54,1.54c0.36,0.59 0.64,1.24 0.8,1.94l2.17,0l0,2.97Zm-8.5,1.5c1.66,0 3,-1.34 3,-3c0,-1.66 -1.34,-3 -3,-3c-1.66,0 -3,1.34 -3,3c0,1.66 1.34,3 3,3Z"></path> </svg> </button> <div class="wp-mail-smtp-dash-widget-settings-menu"> <div class="wp-mail-smtp-dash-widget-settings-menu--style"> <h4><?php esc_html_e( 'Graph Style', 'wp-mail-smtp' ); ?></h4> <div> <div class="wp-mail-smtp-dash-widget-settings-menu-item"> <input type="radio" id="wp-mail-smtp-dash-widget-settings-style-bar" name="style" value="bar" disabled> <label for="wp-mail-smtp-dash-widget-settings-style-bar"><?php esc_html_e( 'Bar', 'wp-mail-smtp' ); ?></label> </div> <div class="wp-mail-smtp-dash-widget-settings-menu-item"> <input type="radio" id="wp-mail-smtp-dash-widget-settings-style-line" name="style" value="line" checked disabled> <label for="wp-mail-smtp-dash-widget-settings-style-line"><?php esc_html_e( 'Line', 'wp-mail-smtp' ); ?></label> </div> </div> </div> <div class="wp-mail-smtp-dash-widget-settings-menu--color"> <h4><?php esc_html_e( 'Color Scheme', 'wp-mail-smtp' ); ?></h4> <div> <div class="wp-mail-smtp-dash-widget-settings-menu-item"> <input type="radio" id="wp-mail-smtp-dash-widget-settings-color-smtp" name="color" value="smtp" disabled> <label for="wp-mail-smtp-dash-widget-settings-color-smtp"><?php esc_html_e( 'WP Mail SMTP', 'wp-mail-smtp' ); ?></label> </div> <div class="wp-mail-smtp-dash-widget-settings-menu-item"> <input type="radio" id="wp-mail-smtp-dash-widget-settings-color-wp" name="color" value="wp" checked disabled> <label for="wp-mail-smtp-dash-widget-settings-color-wp"><?php esc_html_e( 'WordPress', 'wp-mail-smtp' ); ?></label> </div> </div> </div> <button type="button" class="button wp-mail-smtp-dash-widget-settings-menu-save" disabled><?php esc_html_e( 'Save Changes', 'wp-mail-smtp' ); ?></button> </div> </div> <?php } /** * Email statistics block. * * @since 2.9.0 */ private function email_stats_block() { $output_data = $this->get_email_stats_data(); ?> <table id="wp-mail-smtp-dash-widget-email-stats-table" cellspacing="0"> <tr> <?php $count = 0; $per_row = 2; foreach ( array_values( $output_data ) as $stats ) : if ( ! is_array( $stats ) ) { continue; } if ( ! isset( $stats['icon'], $stats['title'] ) ) { continue; } // Make some exceptions for mailers without send confirmation functionality. if ( Helpers::mailer_without_send_confirmation() ) { $per_row = 3; } // Create new row after every $per_row cells. if ( $count !== 0 && $count % $per_row === 0 ) { echo '</tr><tr>'; } $count++; ?> <td class="wp-mail-smtp-dash-widget-email-stats-table-cell wp-mail-smtp-dash-widget-email-stats-table-cell--<?php echo esc_attr( $stats['type'] ); ?> wp-mail-smtp-dash-widget-email-stats-table-cell--3"> <div class="wp-mail-smtp-dash-widget-email-stats-table-cell-container"> <img src="<?php echo esc_url( $stats['icon'] ); ?>" alt="<?php esc_attr_e( 'Table cell icon', 'wp-mail-smtp' ); ?>"> <span> <?php echo esc_html( $stats['title'] ); ?> </span> </div> </td> <?php endforeach; ?> </tr> </table> <?php } /** * Prepare the email stats data. * The text and counts of the email stats. * * @since 2.9.0 * * @return array[] */ private function get_email_stats_data() { $reports = new Reports(); $total_sent = $reports->get_total_emails_sent(); $output_data = [ 'all' => [ 'type' => 'all', 'icon' => wp_mail_smtp()->assets_url . '/images/dash-widget/wp/total.svg', /* translators: %d number of total emails sent. */ 'title' => esc_html( sprintf( esc_html__( '%d total', 'wp-mail-smtp' ), $total_sent ) ), ], 'delivered' => [ 'type' => 'delivered', 'icon' => wp_mail_smtp()->assets_url . '/images/dash-widget/wp/delivered.svg', /* translators: %s fixed string of 'N/A'. */ 'title' => esc_html( sprintf( esc_html__( 'Confirmed %s', 'wp-mail-smtp' ), 'N/A' ) ), ], 'sent' => [ 'type' => 'sent', 'icon' => wp_mail_smtp()->assets_url . '/images/dash-widget/wp/sent.svg', /* translators: %s fixed string of 'N/A'. */ 'title' => esc_html( sprintf( esc_html__( 'Unconfirmed %s', 'wp-mail-smtp' ), 'N/A' ) ), ], 'unsent' => [ 'type' => 'unsent', 'icon' => wp_mail_smtp()->assets_url . '/images/dash-widget/wp/unsent.svg', /* translators: %s fixed string of 'N/A'. */ 'title' => esc_html( sprintf( esc_html__( 'Failed %s', 'wp-mail-smtp' ), 'N/A' ) ), ], ]; if ( Helpers::mailer_without_send_confirmation() ) { // Skip the 'unconfirmed sent' section. unset( $output_data['sent'] ); // Change the 'confirmed sent' section into a general 'sent' section. $output_data['delivered']['title'] = esc_html( /* translators: %s fixed string of 'N/A'. */ sprintf( esc_html__( 'Sent %s', 'wp-mail-smtp' ), 'N/A' ) ); } return $output_data; } /** * Get/set a widget meta. * * @since 2.9.0 * * @param string $action Possible value: 'get' or 'set'. * @param string $meta Meta name. * @param int $value Value to set. * * @return mixed */ protected function widget_meta( $action, $meta, $value = 0 ) { $allowed_actions = [ 'get', 'set' ]; if ( ! in_array( $action, $allowed_actions, true ) ) { return false; } if ( $action === 'get' ) { return $this->get_widget_meta( $meta ); } $meta_key = $this->get_widget_meta_key( $meta ); $value = sanitize_key( $value ); if ( 'set' === $action && ! empty( $value ) ) { return update_user_meta( get_current_user_id(), $meta_key, $value ); } if ( 'set' === $action && empty( $value ) ) { return delete_user_meta( get_current_user_id(), $meta_key ); } return false; } /** * Get the widget meta value. * * @since 3.9.0 * * @param string $meta Meta name. * * @return mixed */ private function get_widget_meta( $meta ) { $defaults = [ 'hide_graph' => 0, 'hide_summary_report_email_block' => 0, 'hide_email_alerts_banner' => 0, ]; $meta_value = get_user_meta( get_current_user_id(), $this->get_widget_meta_key( $meta ), true ); if ( ! empty( $meta_value ) ) { return $meta_value; } if ( isset( $defaults[ $meta ] ) ) { return $defaults[ $meta ]; } return null; } /** * Retrieve the meta key. * * @since 3.9.0 * * @param string $meta Meta name. * * @return string */ private function get_widget_meta_key( $meta ) { return 'wp_mail_smtp_' . static::SLUG . '_' . $meta; } } ConnectionSettings.php 0000644 00000043147 15252526345 0011117 0 ustar 00 <?php namespace WPMailSMTP\Admin; use WPMailSMTP\ConnectionInterface; use WPMailSMTP\EmailSendingDebug; use WPMailSMTP\Helpers\UI; use WPMailSMTP\Options; /** * Class ConnectionSettings. * * @since 3.7.0 */ class ConnectionSettings { /** * The Connection object. * * @since 3.7.0 * * @var ConnectionInterface */ private $connection; /** * After process scroll to anchor. * * @since 3.7.0 * * @var false|string */ private $scroll_to = false; /** * Constructor. * * @since 3.7.0 * * @param ConnectionInterface $connection The Connection object. */ public function __construct( $connection ) { $this->connection = $connection; } /** * Display connection settings. * * @since 3.7.0 */ public function display() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded, Generic.Metrics.NestingLevel.MaxExceeded $mailer = $this->connection->get_mailer_slug(); $connection_options = $this->connection->get_options(); $hide_from_email = false; $disabled_email = in_array( $mailer, [ 'zoho' ], true ) ? 'disabled' : ''; $disabled_name = in_array( $mailer, [ 'outlook' ], true ) ? 'disabled' : ''; if ( empty( $mailer ) || ! in_array( $mailer, Options::$mailers, true ) ) { $mailer = 'mail'; } if ( $mailer === 'sendlayer' && $connection_options->get( 'sendlayer', 'quick_connect' ) && $connection_options->get( 'sendlayer', 'is_shared_domain' ) ) { // For SendLayer, hide the From Email setting since it's managed from the SendLayer dashboard. $hide_from_email = true; } $mailer_supported_settings = wp_mail_smtp()->get_providers()->get_options( $mailer )->get_supports(); ?> <!-- From Email --> <div class="wp-mail-smtp-setting-group js-wp-mail-smtp-setting-from_email" style="display: <?php echo ( empty( $mailer_supported_settings['from_email'] ) || $hide_from_email ) ? 'none' : 'block'; ?>;"> <div id="wp-mail-smtp-setting-row-from_email" class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-email wp-mail-smtp-clear"> <div class="wp-mail-smtp-setting-label"> <label for="wp-mail-smtp-setting-from_email"><?php esc_html_e( 'From Email', 'wp-mail-smtp' ); ?></label> </div> <div class="wp-mail-smtp-setting-field"> <input name="wp-mail-smtp[mail][from_email]" type="email" value="<?php echo esc_attr( $connection_options->get( 'mail', 'from_email' ) ); ?>" id="wp-mail-smtp-setting-from_email" spellcheck="false" placeholder="<?php echo esc_attr( wp_mail_smtp()->get_processor()->get_default_email() ); ?>" <?php disabled( $connection_options->is_const_defined( 'mail', 'from_email' ) || ! empty( $disabled_email ) ); ?> /> <?php if ( ! in_array( $mailer, [ 'zoho' ], true ) ) : ?> <p class="desc"> <?php esc_html_e( 'The email address that emails are sent from.', 'wp-mail-smtp' ); ?> </p> <p class="desc"> <?php esc_html_e( 'If you\'re using an email provider (Yahoo, Outlook.com, etc) this should be your email address for that account.', 'wp-mail-smtp' ); ?> </p> <p class="desc"> <?php esc_html_e( 'Please note that other plugins can change this, to prevent this use the setting below.', 'wp-mail-smtp' ); ?> </p> <?php endif; ?> </div> </div> <div id="wp-mail-smtp-setting-row-from_email_force" class="wp-mail-smtp-setting-row wp-mail-smtp-clear js-wp-mail-smtp-setting-from_email_force" style="display: <?php echo empty( $mailer_supported_settings['from_email_force'] ) ? 'none' : 'block'; ?>;"> <div class="wp-mail-smtp-setting-label"> <label for="wp-mail-smtp-setting-from_email_force"><?php esc_html_e( 'Force From Email', 'wp-mail-smtp' ); ?></label> </div> <div class="wp-mail-smtp-setting-field"> <?php UI::toggle( [ 'name' => 'wp-mail-smtp[mail][from_email_force]', 'id' => 'wp-mail-smtp-setting-from_email_force', 'value' => 'true', 'checked' => (bool) $connection_options->get( 'mail', 'from_email_force' ), 'disabled' => $connection_options->is_const_defined( 'mail', 'from_email_force' ) || ! empty( $disabled_email ), ] ); ?> <?php if ( ! empty( $disabled_email ) ) : ?> <p class="desc"> <?php esc_html_e( 'Current provider will automatically force From Email to be the email address that you use to set up the OAuth connection below.', 'wp-mail-smtp' ); ?> </p> <?php else : ?> <p class="desc"> <?php esc_html_e( 'If checked, the From Email setting above will be used for all emails, ignoring values set by other plugins.', 'wp-mail-smtp' ); ?> </p> <?php endif; ?> </div> </div> </div> <?php /** * Fires after the From Email Address setting row. * * @since 4.8.0 * * @param ConnectionInterface $connection The Connection object. * @param Options $connection_options The connection options instance. * @param string $mailer The current mailer slug. */ do_action( 'wp_mail_smtp_admin_connection_settings_display_after_from_email_setting_row', $this->connection, $connection_options, $mailer ); ?> <!-- From Name --> <div class="wp-mail-smtp-setting-group js-wp-mail-smtp-setting-from_name" style="display: <?php echo empty( $mailer_supported_settings['from_name'] ) ? 'none' : 'block'; ?>;"> <div id="wp-mail-smtp-setting-row-from_name" class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-text wp-mail-smtp-clear "> <div class="wp-mail-smtp-setting-label"> <label for="wp-mail-smtp-setting-from_name"><?php esc_html_e( 'From Name', 'wp-mail-smtp' ); ?></label> </div> <div class="wp-mail-smtp-setting-field"> <input name="wp-mail-smtp[mail][from_name]" type="text" value="<?php echo esc_attr( $connection_options->get( 'mail', 'from_name' ) ); ?>" id="wp-mail-smtp-setting-from_name" spellcheck="false" placeholder="<?php echo esc_attr( wp_mail_smtp()->get_processor()->get_default_name() ); ?>" <?php disabled( $connection_options->is_const_defined( 'mail', 'from_name' ) || ! empty( $disabled_name ) ); ?> /> <?php if ( empty( $disabled_name ) ) : ?> <p class="desc"> <?php esc_html_e( 'The name that emails are sent from.', 'wp-mail-smtp' ); ?> </p> <?php endif; ?> </div> </div> <div id="wp-mail-smtp-setting-row-from_name_force" class="wp-mail-smtp-setting-row wp-mail-smtp-clear js-wp-mail-smtp-setting-from_name_force" style="display: <?php echo empty( $mailer_supported_settings['from_name_force'] ) ? 'none' : 'block'; ?>;"> <div class="wp-mail-smtp-setting-label"> <label for="wp-mail-smtp-setting-from_name_force"><?php esc_html_e( 'Force From Name', 'wp-mail-smtp' ); ?></label> </div> <div class="wp-mail-smtp-setting-field"> <?php UI::toggle( [ 'name' => 'wp-mail-smtp[mail][from_name_force]', 'id' => 'wp-mail-smtp-setting-from_name_force', 'value' => 'true', 'checked' => (bool) $connection_options->get( 'mail', 'from_name_force' ), 'disabled' => $connection_options->is_const_defined( 'mail', 'from_name_force' ) || ! empty( $disabled_name ), ] ); ?> <?php if ( ! empty( $disabled_name ) ) : ?> <p class="desc"> <?php esc_html_e( 'Current provider doesn\'t support setting and forcing From Name. Emails will be sent on behalf of the account name used to setup the OAuth connection below.', 'wp-mail-smtp' ); ?> </p> <?php else : ?> <p class="desc"> <?php esc_html_e( 'If checked, the From Name setting above will be used for all emails, ignoring values set by other plugins.', 'wp-mail-smtp' ); ?> </p> <?php endif; ?> </div> </div> </div> <!-- Return Path --> <div id="wp-mail-smtp-setting-row-return_path" class="wp-mail-smtp-setting-row wp-mail-smtp-clear js-wp-mail-smtp-setting-return_path" style="display: <?php echo empty( $mailer_supported_settings['return_path'] ) ? 'none' : 'block'; ?>;"> <div class="wp-mail-smtp-setting-label"> <label for="wp-mail-smtp-setting-return_path"><?php esc_html_e( 'Return Path', 'wp-mail-smtp' ); ?></label> </div> <div class="wp-mail-smtp-setting-field"> <?php UI::toggle( [ 'name' => 'wp-mail-smtp[mail][return_path]', 'id' => 'wp-mail-smtp-setting-return_path', 'value' => 'true', 'checked' => (bool) $connection_options->get( 'mail', 'return_path' ), 'disabled' => $connection_options->is_const_defined( 'mail', 'return_path' ), ] ); ?> <p class="desc"> <?php esc_html_e( 'Return Path indicates where non-delivery receipts - or bounce messages - are to be sent.', 'wp-mail-smtp' ); ?><br/> <?php esc_html_e( 'If unchecked, bounce messages may be lost.', 'wp-mail-smtp' ); ?> </p> </div> </div> <!-- Mailer --> <div id="wp-mail-smtp-setting-row-mailer" class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-mailer wp-mail-smtp-clear"> <div class="wp-mail-smtp-setting-label"> <label for="wp-mail-smtp-setting-mailer"><?php esc_html_e( 'Mailer', 'wp-mail-smtp' ); ?></label> </div> <div class="wp-mail-smtp-setting-field"> <div class="wp-mail-smtp-mailers"> <?php foreach ( wp_mail_smtp()->get_providers()->get_options_all( $this->connection ) as $provider ) : ?> <div class="wp-mail-smtp-mailer wp-mail-smtp-mailer-<?php echo esc_attr( $provider->get_slug() ); ?> <?php echo $mailer === $provider->get_slug() ? 'active' : ''; ?>"> <div class="wp-mail-smtp-mailer-image <?php echo $provider->is_recommended() ? 'is-recommended' : ''; ?>"> <img src="<?php echo esc_url( $provider->get_logo_url() ); ?>" alt="<?php echo esc_attr( $provider->get_title() ); ?>"> </div> <div class="wp-mail-smtp-mailer-text"> <?php if ( $provider->is_disabled() ) : ?> <input type="radio" name="wp-mail-smtp[mail][mailer]" disabled data-title="<?php echo esc_attr( $provider->get_title() ); ?>" class="js-wp-mail-smtp-setting-mailer-radio-input educate" id="wp-mail-smtp-setting-mailer-<?php echo esc_attr( $provider->get_slug() ); ?>" value="<?php echo esc_attr( $provider->get_slug() ); ?>" /> <?php else : ?> <input id="wp-mail-smtp-setting-mailer-<?php echo esc_attr( $provider->get_slug() ); ?>" data-title="<?php echo esc_attr( $provider->get_title() ); ?>" type="radio" name="wp-mail-smtp[mail][mailer]" value="<?php echo esc_attr( $provider->get_slug() ); ?>" class="js-wp-mail-smtp-setting-mailer-radio-input<?php echo $provider->is_disabled() ? ' educate' : ''; ?>" <?php checked( $provider->get_slug(), $mailer ); ?> <?php disabled( $connection_options->is_const_defined( 'mail', 'mailer' ) || $provider->is_disabled() ); ?> /> <?php endif; ?> <label for="wp-mail-smtp-setting-mailer-<?php echo esc_attr( $provider->get_slug() ); ?>"> <?php echo esc_html( $provider->get_title() ); ?> </label> </div> </div> <?php endforeach; ?> </div> <!-- Suggest a mailer --> <div class="wp-mail-smtp-suggest-new-mailer"> <p class="desc"> <?php esc_html_e( 'Don\'t see what you\'re looking for?', 'wp-mail-smtp' ); ?> <?php printf( '<a href="%1$s" target="_blank" rel="noopener noreferrer">%2$s</a>', esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/suggest-a-mailer/', 'Suggest a Mailer' ) ), esc_html__( 'Suggest a Mailer', 'wp-mail-smtp' ) ); ?> </p> </div> </div> </div> <!-- Mailer Options --> <div class="wp-mail-smtp-setting-group wp-mail-smtp-mailer-options"> <?php foreach ( wp_mail_smtp()->get_providers()->get_options_all( $this->connection ) as $provider ) : ?> <?php $provider_desc = $provider->get_description(); ?> <div class="wp-mail-smtp-mailer-option wp-mail-smtp-mailer-option-<?php echo esc_attr( $provider->get_slug() ); ?> <?php echo $mailer === $provider->get_slug() ? 'active' : 'hidden'; ?>"> <?php if ( ! $provider->is_disabled() ) : ?> <!-- Mailer Title/Notice/Description --> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-content wp-mail-smtp-clear section-heading <?php echo empty( $provider_desc ) ? 'no-desc' : ''; ?>"> <div class="wp-mail-smtp-setting-field"> <h2><?php echo esc_html( $provider->get_title() ); ?></h2> <?php $provider_edu_notice = $provider->get_notice( 'educational' ); $is_dismissed = (bool) get_user_meta( get_current_user_id(), "wp_mail_smtp_notice_educational_for_{$provider->get_slug()}_dismissed", true ); if ( ! empty( $provider_edu_notice ) && ! $is_dismissed ) : ?> <p class="inline-notice inline-edu-notice" data-notice="educational" data-mailer="<?php echo esc_attr( $provider->get_slug() ); ?>"> <a href="#" title="<?php esc_attr_e( 'Dismiss this notice', 'wp-mail-smtp' ); ?>" class="wp-mail-smtp-mailer-notice-dismiss js-wp-mail-smtp-mailer-notice-dismiss"> <span class="dashicons dashicons-dismiss"></span> </a> <?php echo wp_kses_post( $provider_edu_notice ); ?> </p> <?php endif; ?> <?php if ( ! empty( $provider_desc ) ) : ?> <p class="desc"><?php echo wp_kses_post( $provider_desc ); ?></p> <?php endif; ?> </div> </div> <?php endif; ?> <?php $provider->display_options(); ?> </div> <?php endforeach; ?> </div> <?php } /** * Process connection settings. Should be called before options save. * * @since 3.7.0 * * @param array $data Connection data. * @param array $old_data Old connection data. */ public function process( $data, $old_data ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded, Generic.Metrics.CyclomaticComplexity.TooHigh // When checkbox is unchecked - it's not submitted at all, so we need to define its default false value. if ( ! isset( $data['mail']['from_email_force'] ) ) { $data['mail']['from_email_force'] = false; } if ( ! isset( $data['mail']['from_name_force'] ) ) { $data['mail']['from_name_force'] = false; } if ( ! isset( $data['mail']['return_path'] ) ) { $data['mail']['return_path'] = false; } if ( ! isset( $data['smtp']['autotls'] ) ) { $data['smtp']['autotls'] = false; } if ( ! isset( $data['smtp']['auth'] ) ) { $data['smtp']['auth'] = false; } if ( ! isset( $data['mailersend']['has_pro_plan'] ) ) { $data['mailersend']['has_pro_plan'] = false; } // When switching mailers. if ( ! empty( $old_data['mail']['mailer'] ) && ! empty( $data['mail']['mailer'] ) && $old_data['mail']['mailer'] !== $data['mail']['mailer'] ) { // Save correct from email address if Zoho mailer is already configured. if ( in_array( $data['mail']['mailer'], [ 'zoho' ], true ) && ! empty( $old_data[ $data['mail']['mailer'] ]['user_details']['email'] ) ) { $data['mail']['from_email'] = $old_data[ $data['mail']['mailer'] ]['user_details']['email']; } // Clear any cached send failure for this connection — it belongs to the old mailer. EmailSendingDebug::clear( $this->connection->get_id() ); } // Prevent redirect to setup wizard from settings page after successful auth. if ( ! empty( $data['mail']['mailer'] ) && in_array( $data['mail']['mailer'], [ 'gmail', 'outlook', 'zoho' ], true ) ) { $data[ $data['mail']['mailer'] ]['is_setup_wizard_auth'] = false; } /** * Filters connection data. * * @since 3.11.0 * * @param array $data Connection data. * @param array $old_data Old connection data. */ return apply_filters( 'wp_mail_smtp_admin_connection_settings_process_data', $data, $old_data ); } /** * Post process connection settings. Should be called after options save. * * @since 3.7.0 * * @param array $data Connection data. * @param array $old_data Old connection data. */ public function post_process( $data, $old_data ) { // When switching mailers. if ( ! empty( $old_data['mail']['mailer'] ) && ! empty( $data['mail']['mailer'] ) && $old_data['mail']['mailer'] !== $data['mail']['mailer'] ) { // Save correct from email address if Gmail or Outlook mailer is already configured. if ( in_array( $data['mail']['mailer'], [ 'gmail', 'outlook' ], true ) ) { $auth = wp_mail_smtp()->get_providers()->get_auth( $data['mail']['mailer'], $this->connection ); $user_info = ! $auth->is_auth_required() ? $auth->get_user_info() : false; if ( ! empty( $user_info['email'] ) && is_email( $user_info['email'] ) !== false && ( empty( $data['mail']['from_email'] ) || $data['mail']['from_email'] !== $user_info['email'] ) ) { $data['mail']['from_email'] = $user_info['email']; $this->connection->get_options()->set( $data, false, false ); } } } } /** * Get connection settings admin page URL. * * @since 3.7.0 * * @return string */ public function get_admin_page_url() { /** * Filters connection settings admin page URL. * * @since 3.7.0 * * @param string $admin_page_url Connection settings admin page URL. * @param ConnectionInterface $connection The Connection object. */ return apply_filters( 'wp_mail_smtp_admin_connection_settings_get_admin_page_url', wp_mail_smtp()->get_admin()->get_admin_page_url(), $this->connection ); } /** * Get after process scroll to anchor. Returns `false` if scroll is not needed. * * @since 3.7.0 */ public function get_scroll_to() { return $this->scroll_to; } } DomainChecker.php 0000644 00000010757 15252526345 0007774 0 ustar 00 <?php namespace WPMailSMTP\Admin; use WPMailSMTP\Helpers\Helpers; /** * Class for interacting with the Domain Checker API. * * @since 2.6.0 */ class DomainChecker { /** * The domain checker API endpoint. * * @since 2.6.0 */ const ENDPOINT = 'https://connect.wpmailsmtp.com/domain-check/'; /** * The API results. * * @since 2.6.0 * * @var array */ private $results; /** * The plugin mailer slug. * * @since 2.7.0 * * @var string */ protected $mailer; /** * Verify the domain for the provided mailer and email address and save the API results. * * @since 2.6.0 * * @param string $mailer The plugin mailer. * @param string $email The email address from which the domain will be extracted. * @param string $sending_domain The optional sending domain to check the domain records for. */ public function __construct( $mailer, $email, $sending_domain = '' ) { $this->mailer = $mailer; $params = [ 'mailer' => $mailer, 'email' => base64_encode( $email ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode 'domain' => $sending_domain, ]; $response = wp_remote_get( add_query_arg( $params, self::ENDPOINT ), [ 'user-agent' => Helpers::get_default_user_agent(), ] ); if ( is_wp_error( $response ) ) { $this->results = [ 'success' => false, 'message' => method_exists( $response, 'get_error_message' ) ? $response->get_error_message() : esc_html__( 'Something went wrong. Please try again later.', 'wp-mail-smtp' ), 'checks' => [], ]; } else { $this->results = json_decode( wp_remote_retrieve_body( $response ), true ); } } /** * Simple getter for the API results. * * @since 2.6.0 * * @return array */ public function get_results() { return $this->results; } /** * Check if the domain checker has found any errors. * * @since 2.6.0 * * @return bool */ public function has_errors() { if ( empty( $this->results['success'] ) ) { return true; } if ( empty( $this->results['checks'] ) ) { return false; } $has_error = false; foreach ( $this->results['checks'] as $check ) { if ( $check['state'] === 'error' ) { $has_error = true; break; } } return $has_error; } /** * Check if the domain checker has not found any errors or warnings. * * @since 2.6.0 * * @return bool */ public function no_issues() { if ( empty( $this->results['success'] ) ) { return false; } $no_issues = true; foreach ( $this->results['checks'] as $check ) { if ( in_array( $check['state'], [ 'error', 'warning' ], true ) ) { $no_issues = false; break; } } return $no_issues; } /** * Check if the domain checker support mailer. * * @since 2.7.0 * * @return bool */ public function is_supported_mailer() { return ! in_array( $this->mailer, [ 'mail', 'pepipostapi' ], true ); } /** * Get the domain checker results html. * * @since 2.8.0 * * @return string */ public function get_results_html() { $results = $this->get_results(); $allowed_html = [ 'b' => [], 'i' => [], 'a' => [ 'href' => [], 'target' => [], 'rel' => [], ], ]; ob_start(); ?> <div id="wp-mail-smtp-domain-check-details"> <h2><?php esc_html_e( 'Domain Check Results', 'wp-mail-smtp' ); ?></h2> <?php if ( empty( $results['success'] ) ) : ?> <div class="notice-inline <?php echo $this->is_supported_mailer() ? 'notice-error' : 'notice-warning'; ?>"> <p><?php echo wp_kses( $results['message'], $allowed_html ); ?></p> </div> <?php endif; ?> <?php if ( ! empty( $results['checks'] ) ) : ?> <div class="wp-mail-smtp-domain-check-details-check-list"> <?php foreach ( $results['checks'] as $check ) : ?> <div class="wp-mail-smtp-domain-check-details-check-list-item"> <img src="<?php echo esc_url( wp_mail_smtp()->assets_url . '/images/icons/' . esc_attr( $check['state'] ) . '.svg' ); ?>" class="wp-mail-smtp-domain-check-details-check-list-item-icon" alt="<?php printf( /* translators: %s - item state name. */ esc_attr__( '%s icon', 'wp-mail-smtp' ), esc_attr( $check['state'] ) ); ?>"> <div class="wp-mail-smtp-domain-check-details-check-list-item-content"> <h3><?php echo esc_html( $check['type'] ); ?></h3> <p><?php echo wp_kses( $check['message'], $allowed_html ); ?></p> </div> </div> <?php endforeach; ?> </div> <?php endif; ?> </div> <?php return ob_get_clean(); } } SetupWizard.php 0000644 00000174642 15252526345 0007565 0 ustar 00 <?php namespace WPMailSMTP\Admin; use Plugin_Upgrader; use WPMailSMTP\Connect; use WPMailSMTP\TestEmail\TestEmail; use WPMailSMTP\Helpers\Helpers; use WPMailSMTP\Helpers\PluginImportDataRetriever; use WPMailSMTP\Options; use WPMailSMTP\UsageTracking\UsageTracking; use WPMailSMTP\WP; use WPMailSMTP\Reports\Emails\Summary as SummaryReportEmail; use WPMailSMTP\Tasks\Reports\SummaryEmailTask as SummaryReportEmailTask; /** * Class for the plugin's Setup Wizard. * * @since 2.6.0 */ class SetupWizard { /** * The WP Option key for storing setup wizard stats. * * @since 3.1.0 */ const STATS_OPTION_KEY = 'wp_mail_smtp_setup_wizard_stats'; /** * Run all the hooks needed for the Setup Wizard. * * @since 2.6.0 */ public function hooks() { add_action( 'admin_init', [ $this, 'maybe_load_wizard' ] ); add_action( 'admin_init', [ $this, 'maybe_redirect_after_activation' ], 9999 ); add_action( 'admin_menu', [ $this, 'add_dashboard_page' ], 20 ); add_filter( 'removable_query_args', [ $this, 'maybe_disable_automatic_query_args_removal' ] ); // API AJAX callbacks. add_action( 'wp_ajax_wp_mail_smtp_vue_wizard_steps_started', [ $this, 'wizard_steps_started' ] ); add_action( 'wp_ajax_wp_mail_smtp_vue_get_settings', [ $this, 'get_settings' ] ); add_action( 'wp_ajax_wp_mail_smtp_vue_update_settings', [ $this, 'update_settings' ] ); add_action( 'wp_ajax_wp_mail_smtp_vue_import_settings', [ $this, 'import_settings' ] ); add_action( 'wp_ajax_wp_mail_smtp_vue_get_oauth_url', [ $this, 'get_oauth_url' ] ); add_action( 'wp_ajax_wp_mail_smtp_vue_remove_oauth_connection', [ $this, 'remove_oauth_connection' ] ); add_action( 'wp_ajax_wp_mail_smtp_vue_get_connected_data', [ $this, 'get_connected_data' ] ); add_action( 'wp_ajax_wp_mail_smtp_vue_install_plugin', [ $this, 'install_plugin' ] ); add_action( 'wp_ajax_wp_mail_smtp_vue_get_partner_plugins_info', [ $this, 'get_partner_plugins_info' ] ); add_action( 'wp_ajax_wp_mail_smtp_vue_subscribe_to_newsletter', [ $this, 'subscribe_to_newsletter' ] ); add_action( 'wp_ajax_wp_mail_smtp_vue_upgrade_plugin', [ $this, 'upgrade_plugin' ] ); add_action( 'wp_ajax_wp_mail_smtp_vue_check_mailer_configuration', [ $this, 'check_mailer_configuration' ] ); add_action( 'wp_ajax_wp_mail_smtp_vue_send_feedback', [ $this, 'send_feedback' ] ); } /** * Get the URL of the Setup Wizard page. * * @since 2.6.0 * * @return string */ public static function get_site_url() { return wp_mail_smtp()->get_admin()->get_admin_page_url() . '-setup-wizard'; } /** * Checks if the Wizard should be loaded in current context. * * @since 2.6.0 */ public function maybe_load_wizard() { // Check for wizard-specific parameter // Allow plugins to disable the setup wizard // Check if current user is allowed to save settings. if ( ! ( isset( $_GET['page'] ) && // phpcs:ignore WordPress.Security.NonceVerification.Recommended Area::SLUG . '-setup-wizard' === $_GET['page'] && // phpcs:ignore WordPress.Security.NonceVerification.Recommended $this->should_setup_wizard_load() && current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) ) { return; } // Don't load the interface if doing an ajax call. if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { return; } set_current_screen(); // Remove an action in the Gutenberg plugin ( not core Gutenberg ) which throws an error. remove_action( 'admin_print_styles', 'gutenberg_block_editor_admin_print_styles' ); // Remove hooks for deprecated functions in WordPress 6.4.0. remove_action( 'admin_print_styles', 'print_emoji_styles' ); remove_action( 'admin_head', 'wp_admin_bar_header' ); $this->load_setup_wizard(); } /** * Maybe redirect to the setup wizard after plugin activation on a new install. * * @since 2.6.0 */ public function maybe_redirect_after_activation() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh if ( wp_doing_ajax() || wp_doing_cron() ) { return; } // Only users who can activate plugins should be redirected. if ( ! current_user_can( 'activate_plugins' ) ) { return; } // Check if we should consider redirection. if ( ! get_transient( 'wp_mail_smtp_activation_redirect' ) ) { return; } delete_transient( 'wp_mail_smtp_activation_redirect' ); // Check option to disable setup wizard redirect. if ( get_option( 'wp_mail_smtp_activation_prevent_redirect' ) ) { return; } // Only do this for single site installs if Network Wide setting is not enabled. if ( isset( $_GET['activate-multi'] ) || is_network_admin() || WP::use_global_plugin_settings() ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return; } // Don't redirect if the Setup Wizard is disabled. if ( ! $this->should_setup_wizard_load() ) { return; } // Initial install. if ( get_option( 'wp_mail_smtp_initial_version' ) === WPMS_PLUGIN_VER ) { update_option( 'wp_mail_smtp_activation_prevent_redirect', true ); wp_safe_redirect( self::get_site_url() ); exit; } } /** * Register page through WordPress's hooks. * * Create a dummy admin page, where the Setup Wizard app can be displayed, * but it's not visible in the admin dashboard menu. * * @since 2.6.0 */ public function add_dashboard_page() { if ( ! $this->should_setup_wizard_load() ) { return; } add_submenu_page( '', '', '', wp_mail_smtp()->get_capability_manage_global_options(), Area::SLUG . '-setup-wizard', '' ); } /** * Load the Setup Wizard template. * * @since 2.6.0 */ private function load_setup_wizard() { /** * Before setup wizard load. * * @since 2.8.0 * * @param \WPMailSMTP\Admin\SetupWizard $setup_wizard SetupWizard instance. */ do_action( 'wp_mail_smtp_admin_setup_wizard_load_setup_wizard_before', $this ); $this->enqueue_scripts(); $this->setup_wizard_header(); $this->setup_wizard_content(); $this->setup_wizard_footer(); /** * After setup wizard load. * * @since 2.8.0 * * @param \WPMailSMTP\Admin\SetupWizard $setup_wizard SetupWizard instance. */ do_action( 'wp_mail_smtp_admin_setup_wizard_load_setup_wizard_after', $this ); exit; } /** * Load the scripts needed for the Setup Wizard. * * @since 2.6.0 */ public function enqueue_scripts() { if ( ! defined( 'WPMS_VUE_LOCAL_DEV' ) || ! WPMS_VUE_LOCAL_DEV ) { $rtl = is_rtl() ? '.rtl' : ''; wp_enqueue_style( 'wp-mail-smtp-vue-style', wp_mail_smtp()->assets_url . '/vue/css/wizard' . $rtl . '.min.css', [], WPMS_PLUGIN_VER ); } wp_enqueue_script( 'wp-mail-smtp-vue-vendors', wp_mail_smtp()->assets_url . '/vue/js/chunk-vendors.min.js', [], WPMS_PLUGIN_VER, true ); wp_enqueue_script( 'wp-mail-smtp-vue-script', wp_mail_smtp()->assets_url . '/vue/js/wizard.min.js', [ 'wp-mail-smtp-vue-vendors' ], WPMS_PLUGIN_VER, true ); wp_localize_script( 'wp-mail-smtp-vue-script', 'wp_mail_smtp_vue', [ 'ajax_url' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'wpms-admin-nonce' ), 'is_multisite' => is_multisite(), 'translations' => WP::get_jed_locale_data( 'wp-mail-smtp' ), 'exit_url' => wp_mail_smtp()->get_admin()->get_admin_page_url(), 'email_test_tab_url' => add_query_arg( 'tab', 'test', wp_mail_smtp()->get_admin()->get_admin_page_url( Area::SLUG . '-tools' ) ), 'is_pro' => wp_mail_smtp()->is_pro(), 'is_ssl' => is_ssl(), 'license_exists' => apply_filters( 'wp_mail_smtp_admin_setup_wizard_license_exists', false ), 'plugin_version' => WPMS_PLUGIN_VER, 'other_smtp_plugins' => $this->detect_other_smtp_plugins(), 'mailer_options' => $this->prepare_mailer_options(), 'defined_constants' => $this->prepare_defined_constants(), 'upgrade_link' => wp_mail_smtp()->get_upgrade_link( [ 'medium' => 'setup-wizard' ] ), 'versions' => $this->prepare_versions_data(), 'public_url' => wp_mail_smtp()->assets_url . '/vue/', 'current_user_email' => wp_get_current_user()->user_email, 'completed_time' => self::get_stats()['completed_time'], 'sendlayer' => [ 'connect_nonce' => wp_create_nonce( 'wp-mail-smtp-sendlayer-connect' ), 'return_url' => self::get_site_url() . '#/step/configure_mailer/sendlayer', ], 'education' => [ 'upgrade_text' => esc_html__( 'We\'re sorry, the %mailer% mailer is not available on your plan. Please upgrade to the PRO plan to unlock all these awesome features.', 'wp-mail-smtp' ), 'upgrade_button' => esc_html__( 'Upgrade to Pro', 'wp-mail-smtp' ), 'upgrade_url' => add_query_arg( 'discount', 'SMTPLITEUPGRADE', wp_mail_smtp()->get_upgrade_link( [ 'medium' => 'setup-wizard' ] ) ), 'upgrade_bonus' => sprintf( wp_kses( /* Translators: %s - discount value $50 */ __( '<strong>Bonus:</strong> WP Mail SMTP users get <span class="highlight">%s off</span> regular price,<br>applied at checkout.', 'wp-mail-smtp' ), [ 'strong' => [], 'span' => [ 'class' => [], ], 'br' => [], ] ), '$50' ), 'upgrade_doc' => sprintf( '<a href="%1$s" target="_blank" rel="noopener noreferrer" class="already-purchased">%2$s</a>', // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/how-to-upgrade-wp-mail-smtp-to-pro-version/', [ 'medium' => 'setup-wizard', 'content' => 'Wizard Pro Mailer Popup - Already purchased' ] ) ), esc_html__( 'Already purchased?', 'wp-mail-smtp' ) ) ], ] ); } /** * Outputs the simplified header used for the Setup Wizard. * * @since 2.6.0 */ public function setup_wizard_header() { ?> <!DOCTYPE html> <html <?php language_attributes(); ?>> <head> <meta name="viewport" content="width=device-width"/> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title><?php esc_html_e( 'WP Mail SMTP › Setup Wizard', 'wp-mail-smtp' ); ?></title> <?php do_action( 'admin_print_styles' ); ?> <?php do_action( 'admin_print_scripts' ); ?> <?php do_action( 'admin_head' ); ?> </head> <body class="wp-mail-smtp-setup-wizard"> <?php } /** * Outputs the content of the current step. * * @since 2.6.0 */ public function setup_wizard_content() { $admin_url = is_network_admin() ? network_admin_url() : admin_url(); $this->settings_error_page( 'wp-mail-smtp-vue-setup-wizard', '<a href="' . $admin_url . '">' . esc_html__( 'Go back to the Dashboard', 'wp-mail-smtp' ) . '</a>' ); $this->settings_inline_js(); } /** * Outputs the simplified footer used for the Setup Wizard. * * @since 2.6.0 */ public function setup_wizard_footer() { ?> <?php wp_print_scripts( 'wp-mail-smtp-vue-script' ); ?> </body> </html> <?php } /** * Error page HTML * * @since 2.6.0 * * @param string $id The HTML ID attribute of the main container div. * @param string $footer The centered footer content. */ private function settings_error_page( $id = 'wp-mail-smtp-vue-site-settings', $footer = '' ) { $inline_logo_image = 'data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNDIgNjAiPjxkZWZzPjxzdHlsZT4uY2xzLTExLC5jbHMtMTJ7ZmlsbC1ydWxlOmV2ZW5vZGR9LmNscy00e2ZpbGw6bm9uZX0uY2xzLTExe2ZpbGw6Izg2YTE5Nn0uY2xzLTEye2ZpbGw6I2ZmZn08L3N0eWxlPjwvZGVmcz48cGF0aCBkPSJNNjkuMDYgMTEuMTFMNjQuNyAyMy40OWgtLjA2bC0xLjg5LTYuNTUtMi02LjE0aC0zLjEzdi40NGw2IDE3Ljg5aDEuNjZsNC4zOS0xMS42N2guMDZsNC4zOSAxMS42N2gxLjU2bDYuMDYtMTcuODl2LS40NGgtMy4ybC0xLjkyIDYuMTQtMS44MiA2LjUyaC0uMDZsLTQuMzItMTIuMzV6TTg3LjY4IDI5aC0zVjEwLjhoNy41NGE2LjE3IDYuMTcgMCAwMTYuNDIgNi40MiA2LjE0IDYuMTQgMCAwMS02LjQyIDYuMzJoLTQuNXptLS4wNS04LjExaDQuNTVhMy41NCAzLjU0IDAgMDAzLjUxLTMuNzUgMy40OSAzLjQ5IDAgMDAtMy41MS0zLjcxaC00LjU1em0yOS0uNzNsLTcuNDEtOS40MWgtMS4xMVYyOWgzVjE3LjYxbDUuMjggNi43NGguNDFsNS4yNS02Ljc0VjI5aDMuMDVWMTAuNzVIMTI0em0yNC4xMS0yLjc4djcuODhjMCAxLjE0IDAgMS44NyAxLjM1IDEuNzR2MS45MmMtMS44LjM0LTMuNjQuMTMtMy42NC0ydi0uNTJhNC41NyA0LjU3IDAgMDEtNC4zMiAyLjczYy0zLjgyIDAtNS42Ny0zLjA3LTUuNjctNi42LjA4LTQuMTYgMy4wNS02LjQ1IDcuMTMtNi4zN2ExMi42MiAxMi42MiAwIDAxNS4xNiAxLjIyek0xMzggMjIuNzFWMTlhNi40OSA2LjQ5IDAgMDAtMi42My0uNTJjLTIuMzkgMC00IDEuMzctNC4wOCA0LjA4IDAgMi4yOSAxLjEyIDQuMDggMy40MyA0LjA4IDIuMTMuMDIgMy4yMi0xLjY0IDMuMjgtMy45M3ptNi41Ny0xMC4xMmExLjY1IDEuNjUgMCAwMDEuNzUgMS42OSAxLjYxIDEuNjEgMCAwMDEuNjgtMS42OSAxLjcxIDEuNzEgMCAwMC0zLjQxIDB6bTMuMTIgNGgtMi44M1YyOWgyLjgzek0xNTEuMyAxMHYxNC41M2MwIDQuMTggMS43NyA1LjE3IDUuNjIgNC41NWwtLjExLTIuMTljLTIuMTUuMzQtMi43LS4zMS0yLjctMi4zOVYxMHptMTMuNDcgMTMuODZjLjA4IDMuODIgMy44IDUuNTkgNy4zNiA1LjUxIDMuNCAwIDcuMTctMS41MSA3LjE3LTUuNTEgMC00LjE5LTMuMzgtNC45Mi03LjA3LTUuMzMtMi4xLS4yOS00LjE2LS41NS00LjE2LTIuNnMyLjE2LTIuNzMgMy44Mi0yLjczIDMuODUuNjIgNCAyLjU3aDIuODZjLS4wOC0zLjcyLTMuMzUtNS4yOC02LjgxLTUuMjhzLTYuODQgMS43Ny02Ljg0IDUuNTEgMy4zIDQuNzEgNi42MyA1YzIuMTEuMiA0LjU4LjQ0IDQuNTggMi44M3MtMi4yOSAyLjgzLTQuMjEgMi44My00LjIyLS43NS00LjM1LTIuOHptMjYuNDQtMy42N2wtNy40MS05LjQxaC0xLjEyVjI5aDNWMTcuNjFsNS4zMiA2Ljc0aC40Mmw1LjI1LTYuNzRWMjloM1YxMC43NWgtMS4wN3ptMTYuNTQtNi42OFYyOWgzVjEzLjQ4SDIxNlYxMC44aC0xMy41M3YyLjY4em0xNCAxNS41MmgtM1YxMC44aDcuNTRhNi4xNyA2LjE3IDAgMDE2LjQyIDYuNDIgNi4xNCA2LjE0IDAgMDEtNi40MiA2LjMyaC00LjV6bTAtOC4xMWg0LjU1YTMuNTQgMy41NCAwIDAwMy41MS0zLjc1IDMuNDkgMy40OSAwIDAwLTMuNTEtMy43MWgtNC41NXoiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIzMjgyYyIvPjxwYXRoIGQ9Ik05NC4xOCAzOC4wOWEuNDYuNDYgMCAwMS4wOS4xOSAxLjE1IDEuMTUgMCAwMTAgLjJ2LjE4YTEuMzMgMS4zMyAwIDAxLS4wOC4yNCAxLjA5IDEuMDkgMCAwMS0uMjEuMzcuNTguNTggMCAwMS0uNDYgMCAuMy4zIDAgMDAtLjE3IDAgMS41OSAxLjU5IDAgMDAtLjM0LS4wNmgtLjM1YTEuNyAxLjcgMCAwMC0uNTUuMDggMS4xMiAxLjEyIDAgMDAtLjQ3LjI5IDEuNzIgMS43MiAwIDAwLS4zNC42IDMuMzQgMy4zNCAwIDAwLS4xNiAxdjEuMTNoMi4xcTAgLjM5LS4wNi42M2EyLjEgMi4xIDAgMDEtLjEuNC42MS42MSAwIDAxLS4xNS4yMiAxLjI2IDEuMjYgMCAwMS0uMjMuMTNoLS4yM2E1LjM1IDUuMzUgMCAwMS0uNjEgMGgtLjc1djcuMTFhMS4xMiAxLjEyIDAgMDEwIC4yNC4yNS4yNSAwIDAxLS4yLjIxIDYuMDggNi4wOCAwIDAxLS42Ni4wN2gtLjUzYTMuMTUgMy4xNSAwIDAxLS42MS0uMDYgMS40IDEuNCAwIDAxMC0uMjNWNDMuN2EyLjE5IDIuMTkgMCAwMS0xLjE3LS4zMWMwLS4xOSAwLS4zNS4wOC0uNDZhLjY5LjY5IDAgMDEuMS0uMjcuNjEuNjEgMCAwMS4xNy0uMTUuODYuODYgMCAwMS4yNS0uMWwuMjItLjA2LjM1LS4wN3YtLjczYTYuMjYgNi4yNiAwIDAxLjA2LS42NSAzLjc5IDMuNzkgMCAwMS40My0xLjYxIDMuMTYgMy4xNiAwIDAxLjg1LTEgMy4yNCAzLjI0IDAgMDExLjA5LS40OSA0LjQgNC40IDAgMDExLjEtLjE1IDMuMiAzLjIgMCAwMTEgLjEzIDEuMzYgMS4zNiAwIDAxLjUzLjI3em05LjgyIDUuNjhhMi4wOCAyLjA4IDAgMDAtLjcxLjEyIDEuNjUgMS42NSAwIDAwLS41OS4zOHY2YTIuNTQgMi41NCAwIDAxMCAuNDEuOTEuOTEgMCAwMS0uMTYuMzcgMS4wNSAxLjA1IDAgMDEtLjI0LjE1IDEuMyAxLjMgMCAwMS0uMzMuMDZoLTEuMjd2LTdhMy44OCAzLjg4IDAgMDAtLjA3LS44MSA0LjczIDQuNzMgMCAwMC0uMTgtLjYzIDEuNjYgMS42NiAwIDAxLjMxLS4yMyAzLjY2IDMuNjYgMCAwMS41Mi0uMjUuNTYuNTYgMCAwMS4xNSAwaC4xMmEuODkuODkgMCAwMS40NS4wOS43Ni43NiAwIDAxLjI0LjMgMy41NyAzLjU3IDAgMDEuNTYtLjMzYy4yLS4wOS4zOS0uMTcuNTctLjIzYTMgMyAwIDAxLjU1LS4xNCAxLjUxIDEuNTEgMCAwMS41NiAwYy41OC4wNi45LjI0IDEgLjUzYTIuODkgMi44OSAwIDAxLS4wNy41NyAxLjQ2IDEuNDYgMCAwMS0uMjcuNjRoLS44N2EuNTYuNTYgMCAwMC0uMTUgMHptNy45MSA3LjU2aC0xLjY3di01Ljg5YTMuMiAzLjIgMCAwMC0uMjQtMS40Ny45LjkgMCAwMC0uODYtLjQzIDEuNjcgMS42NyAwIDAwLS44LjIgMi40MSAyLjQxIDAgMDAtLjYzLjQ5djYuMTRhMi4zNyAyLjM3IDAgMDEwIC40MS42NC42NCAwIDAxLS40My40NyAxLjk0IDEuOTQgMCAwMS0uMzIuMDcgMy4yOCAzLjI4IDAgMDEtLjQ5IDBoLS43NnYtNy4wMWEzLjk0IDMuOTQgMCAwMC0uMDctLjgxIDQuODIgNC44MiAwIDAwLS4xNi0uNjMgMi4yMyAyLjIzIDAgMDEuODMtLjQ4LjU2LjU2IDAgMDEuMTUgMGguMDlhLjguOCAwIDAxLjQ1LjEzLjg2Ljg2IDAgMDEuMjYuMjggNC4zOSA0LjM5IDAgMDExLjE0LS41OCAzLjg0IDMuODQgMCAwMTEuMjQtLjE5aC4zOWEyLjcgMi43IDAgMDExIC4yNyAyLjI2IDIuMjYgMCAwMS42OC41MyAyLjU4IDIuNTggMCAwMS42MS0uMzZjLjIzLS4xLjQ0LS4xOS42My0uMjVhMy42NSAzLjY1IDAgMDExLjIxLS4xOWguNGEyLjkxIDIuOTEgMCAwMTEuMTIuMyAxLjkgMS45IDAgMDEuNjkuNjkgMyAzIDAgMDEuMzQgMSA3Ljc0IDcuNzQgMCAwMS4xIDEuMzN2NS42OWExIDEgMCAwMS0uMjUuMTYgMS40IDEuNCAwIDAxLS4zNS4wOGgtMS4zYTMuMDUgMy4wNSAwIDAxLS4xMy0uMzIgMS42MSAxLjYxIDAgMDEwLS4zOXYtNS4xMWEzLjQ1IDMuNDUgMCAwMC0uMjMtMS40OC44OS44OSAwIDAwLS44NS0uNDIgMS42NCAxLjY0IDAgMDAtLjgxLjIyIDMuNDggMy40OCAwIDAwLS42Ny41M2wuMDYtLjA2djYuNjhjLjAzLjItLjEuMzQtLjM1LjR6TTEyMC42IDQyaC41NmEzLjA1IDMuMDUgMCAwMTEuMzYuMzZjLjI5LjE5LjQ2LjM2LjUuNWExLjI5IDEuMjkgMCAwMS0uMTIuNDggMi42MSAyLjYxIDAgMDEtLjI3LjVoLS4xOGEuODguODggMCAwMS0uMiAwIDMgMyAwIDAwLS4zMi0uMDYgMS41OCAxLjU4IDAgMDEtLjMxLS4wOSAyLjMyIDIuMzIgMCAwMC0uODctLjE3IDEuMTUgMS4xNSAwIDAwLS43OS4yNS43Ny43NyAwIDAwLS4zLjYzIDEgMSAwIDAwLjEuNDQgMS41NCAxLjU0IDAgMDAuNDIuNDZsLjM2LjI3LjQ0LjMxLjU3LjQyLjU1LjRhMy42MyAzLjYzIDAgMDEuOSAxIDIuMjggMi4yOCAwIDAxLjI4IDEuMTNBMi42NSAyLjY1IDAgMDExMjMgNTBhMi41NyAyLjU3IDAgMDEtLjcyLjg1IDMuMTkgMy4xOSAwIDAxLTEuMDguNTIgNC41OSA0LjU5IDAgMDEtMS4zLjE3IDQuNzEgNC43MSAwIDAxLTEuNjYtLjI2IDEuMzggMS4zOCAwIDAxLS44OS0uNjYgMS41NyAxLjU3IDAgMDEuMS0uNTEgMS44NiAxLjg2IDAgMDEuMjgtLjUyaC4yN2ExLjIxIDEuMjEgMCAwMS41OC4xNyAzLjkzIDMuOTMgMCAwMC42Ni4yMiAyLjg2IDIuODYgMCAwMC43LjA5aC4zNGExIDEgMCAwMC42OS0uMzIgMSAxIDAgMDAuMjQtLjcyIDEuMTYgMS4xNiAwIDAwLS4xNy0uNiAxLjgzIDEuODMgMCAwMC0uNTYtLjU1bC0uMTctLjExYy0uMDctLjA1LS4wOS0uMDctLjA2IDAtLjIyLS4xNi0uNDUtLjMyLS42OS0uNTFsLS42Mi0uNTEtLjQ4LS4zOGEyLjYyIDIuNjIgMCAwMS0uODgtMS44NiAyLjExIDIuMTEgMCAwMS44NC0xLjc5IDMuNjYgMy42NiAwIDAxMi4xOC0uNzJ6bS0yMC41MSA0LjdhNi43OSA2Ljc5IDAgMDEtLjI5IDIuMTIgNC4zIDQuMyAwIDAxLS44IDEuNTIgMy40MyAzLjQzIDAgMDEtMS4yMi45MyAzLjg3IDMuODcgMCAwMS0xLjUzLjMgMy41OCAzLjU4IDAgMDEtMi44My0xLjEyIDUuMzkgNS4zOSAwIDAxLTEtMy42NCA2LjgyIDYuODIgMCAwMS4yOS0yLjEzIDQuMjUgNC4yNSAwIDAxLjgtMS41MSAzLjIxIDMuMjEgMCAwMTEuMjMtLjg4IDQuMTggNC4xOCAwIDAxMS41Ny0uMjkgMy40MSAzLjQxIDAgMDEyLjg0IDEuMTkgNS41NSA1LjU1IDAgMDEuOTQgMy41em0tNS41NCAwYTguMTcgOC4xNyAwIDAwLjEzIDEuNjEgMy4zNyAzLjM3IDAgMDAuMzcgMSAxLjQ1IDEuNDUgMCAwMC41NC41OCAxLjQgMS40IDAgMDAuNy4xN0ExLjMgMS4zIDAgMDA5NyA1MGExLjUxIDEuNTEgMCAwMC41My0uNTggMy4zIDMuMyAwIDAwLjM3LTEgOCA4IDAgMDAuMTMtMS42IDUuMDcgNS4wNyAwIDAwLS40Ni0yLjU1IDEuNDMgMS40MyAwIDAwLTEuMjctLjc1IDEuMjggMS4yOCAwIDAwLS42NS4xOCAxLjU1IDEuNTUgMCAwMC0uNTMuNTcgMy4zNCAzLjM0IDAgMDAtLjM4IDEgNy4zNiA3LjM2IDAgMDAtLjE5IDEuNDZ6IiBmaWxsPSIjNWY1ZTVlIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz48cGF0aCBkPSJNNzMuNTIgNTAuNjZ2LjA4YS4xMS4xMSAwIDAxMCAuMDcgMi42OCAyLjY4IDAgMDEtLjE1LjM5LjUyLjUyIDAgMDEtLjI5LjIyaC0uMzdjLS4xOSAwLS40NiAwLS44Mi4wNWEyLjE0IDIuMTQgMCAwMS0uMzctLjQ0IDEuNzMgMS43MyAwIDAxLS4yNC0uNWwtLjMtMS4wOS0uMzgtMS4yNGMtLjEzLS40NS0uMjUtLjktLjM4LTEuMzdzLS4yNS0uOTEtLjM2LTEuMzVjLS4yMi0uOC0uNC0xLjQ1LS41My0xLjk0YTUuNjMgNS42MyAwIDAwLS4zLS45Mi45Mi45MiAwIDAxLjU4LS4zNSA0LjgyIDQuODIgMCAwMTEuMzItLjEzLjg5Ljg5IDAgMDEuMjQuMyAxLjc0IDEuNzQgMCAwMS4xMy4zNmMuMTYuNzQuMzUgMS41Mi41NSAyLjM0LjA3LjI1LjEyLjUxLjE3Ljc2cy4wOS41LjE0LjcxLjEuNTEuMTMuNjguMDcuMzMuMS40NSAwIC4yNC4wNy4zNC4wNS4yMS4wOC4zNGMuMDctLjI1LjE0LS41NS4yMi0uOTJzLjE4LS43Ny4yOC0xLjE4YzAtLjExLjA4LS4zMy4xNy0uNjVsLjI1LTFjLjA4LS4zNy4xNi0uNzEuMjMtMXMuMTMtLjU0LjE2LS42NWEuNzMuNzMgMCAwMDAtLjE2di0uMjVhLjkzLjkzIDAgMDEuMjItLjA4IDMuNjEgMy42MSAwIDAxLjQ1LS4xMmwuNTEtLjA3YTEgMSAwIDAxLjM5IDAgLjg5Ljg5IDAgMDEuMjIuMzEgMy4wNyAzLjA3IDAgMDEuMTQuNDdsLjM2IDEuNTJjLjEzLjU1LjI3IDEuMTIuNDEgMS43MiAwIC4yMi4xLjQzLjE0LjY0YTUuNjEgNS42MSAwIDAwLjEzLjU5bC4wOS4zN3YuMTdhLjI3LjI3IDAgMDEwIC4xMnYuMTljLjEzLS40Ni4yNS0xIC4zOC0xLjU3cy4yNS0xLjE5LjM5LTEuODNjLjExLS40Ny4yMi0uOTEuMzEtMS4zMXMuMTYtLjc3LjIzLTEuMTFhLjI0LjI0IDAgMDAwLS4xMy40OC40OCAwIDAxLjA5LS4xOS40My40MyAwIDAxLjIxLS4xMWguNTNsLjQuMDVoLjM2YS40OS40OSAwIDAxLjE5IDAgLjIuMiAwIDAxLjA5LjA5IDEgMSAwIDAwMCAuMS41NC41NCAwIDAxMCAuMjVjMCAuMDkgMCAuMi0uMDguMzRsLS4wOC4xOWMtLjEyLjM4LS4yNC44MS0uMzYgMS4yOEw3OS4zOSA0NmMtLjIxLjc5LS40NCAxLjYtLjY2IDIuNDJzLS40NCAxLjU3LS42NCAyLjIydi4xNWEyIDIgMCAwMS0uMTMuMzkuNTYuNTYgMCAwMS0uMjkuMjJoLS4zOWMtLjE5IDAtLjQ2IDAtLjguMDVhMi4xNSAyLjE1IDAgMDEtLjM4LS40NCAyLjExIDIuMTEgMCAwMS0uMjUtLjVjMC0uMTQtLjA4LS4zLS4xMy0uNDZzLS4wOS0uMzEtLjEyLS40NSAwLS4xOS0uMDctLjI3YTEuMjUgMS4yNSAwIDAwLS4wNy0uMjNjLS4xMS0uNDgtLjI0LTEtLjM2LTEuNTdzLS4yNS0xLjA5LS4zNS0xLjU3Yy0uMTEuNTEtLjI1IDEuMDYtLjQgMS42NnMtLjMgMS4xNS0uNDQgMS42OHptOS4xIDQuMzRhMS4zOSAxLjM5IDAgMDEtLjQyLjEzIDMuMjggMy4yOCAwIDAxLTEuNDMgMCA2IDYgMCAwMTAtLjczVjQ0LjMxYTQuNyA0LjcgMCAwMC0uMDYtLjc5IDQuODcgNC44NyAwIDAwLS4xNS0uNjMuNzQuNzQgMCAwMS4yOS0uMjZsLjUxLS4yNGguMjVhLjc0Ljc0IDAgMDEuNDQuMTMuOC44IDAgMDEuMjcuMjggNS42NCA1LjY0IDAgMDExLjE1LS41OHEuMjgtLjA5LjU3LS4xNWEyLjkgMi45IDAgMDEuNjItLjA2IDMuODcgMy44NyAwIDAxMS4zMy4yMyAyLjc4IDIuNzggMCAwMTEuMS43NCAzLjYzIDMuNjMgMCAwMS43NCAxLjMgNi4zIDYuMyAwIDAxLjE3IDEuODcgNy42NSA3LjY1IDAgMDEtLjQ4IDIuOTQgNC4yOCA0LjI4IDAgMDEtMS4yNCAxLjc0IDIuNzYgMi43NiAwIDAxLTEuMDYuNTkgNC4yNCA0LjI0IDAgMDEtMSAuMTQgMyAzIDAgMDEtMS41LS4zMnYzLjMyYTEuNjkgMS42OSAwIDAxMCAuMzh6bTEuMzItNC45YTEuNTEgMS41MSAwIDAwLjY1LS4xNiAxLjY2IDEuNjYgMCAwMC42My0uNTkgMy41NSAzLjU1IDAgMDAuNDgtMS4xNyA3LjY0IDcuNjQgMCAwMC4yLTEuODkgMy43MyAzLjczIDAgMDAtLjQ1LTIuMTEgMS40MSAxLjQxIDAgMDAtMS4yNC0uNjQgMS44IDEuOCAwIDAwLS44MS4yIDMuNzYgMy43NiAwIDAwLS42Ny40NnY1LjM5YTEuODIgMS44MiAwIDAwLjU0LjM2IDEuNTYgMS41NiAwIDAwLjY3LjE1eiIgZmlsbD0iI2I4NWExYiIgZmlsbC1ydWxlPSJldmVub2RkIi8+PHBhdGggY2xhc3M9ImNscy00IiBkPSJNLTYuMjUgMGg2MHY2MGgtNjB6Ii8+PHBhdGggZD0iTTE2LjY2IDguMTRhMTUuNDMgMTUuNDMgMCAwMC03LjkxIDEwLjE3IDIzLjUxIDIzLjUxIDAgMTAzMCAwIDE1LjQxIDE1LjQxIDAgMDAtOS4zNy0xMC44MyAzLjQgMy40IDAgMDAtMi4wOC0yLjY5IDQuNjMgNC42MyAwIDAwLTguODYtMS42NSAyNC40MSAyNC40MSAwIDAwLTEuNzggNXoiIGZpbGw9IiMzOTUzNjAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmYmFhNmYiIGQ9Ik0xOCAyNmgxMnYxNEgxOHoiLz48cGF0aCBkPSJNMjUuODcgMzMuMThsLS4xMi0uMDhhMS40MiAxLjQyIDAgMTExLjY3LTIuMyAxLjg3IDEuODcgMCAwMC0xLjIyLjgxIDEuODUgMS44NSAwIDAwLS4zMyAxLjU3em0tNC40OCAwYTEuOCAxLjggMCAwMC0uMzktMS41NCAxLjkxIDEuOTEgMCAwMC0xLjIzLS44MSAxLjQyIDEuNDIgMCAwMTEuNjcgMi4zLjU3LjU3IDAgMDEtLjA1LjA1ek0yOC42MSAzMGguNTNsLTEuMDcgNC44Mi0yLjE0IDYuNDNoLTQuMjlsLTMuMjEtNS4zNiAxLjA3LTMuMjFjMS4wNyAxLjQzIDEuNzkgMi4zMiAyLjE0IDIuNjguNTQuNTMgMi42OC41MyAzLjc1LS41NEEyNi4xNyAyNi4xNyAwIDAwMjguNjEgMzB6IiBmaWxsPSIjZGM3ZjNjIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz48cGF0aCBkPSJNOS43NCAyOUgxNXYtOWgtNC4wNmExMyAxMyAwIDAxNy41LTEwcTEuMTQtNSAyLjcxLTYuNzVsLjE4LS4xNy4xMS0uMWEyLjI1IDIuMjUgMCAwMTEuMDgtLjQ3IDIuMzIgMi4zMiAwIDAxMi4xNSAzLjc3aC0uMDZhMS42NCAxLjY0IDAgMDEtLjMuMjlBMTUgMTUgMCAwMDIzIDguMTRhNSA1IDAgMDEzLTEuNSAxLjQgMS40IDAgMDEuNjYuMTYgMS4zMyAxLjMzIDAgMDEuNTEgMS43OSAxLjI5IDEuMjkgMCAwMS0uNi41NiAxMyAxMyAwIDAxMTAuMTQgMTFsLjEyLjg3SDMzdjhoNC44M2wxLjc5IDEzLjQzcS02LjMzIDMuOTMtMTUuODUgMy45M1Q4IDQyLjQ0em0xNS4xMyA5LjM5cTMuODctNi4zOSAzLjg3LTcuNjFjMC0yLjIzLTMuMjUtNC4wNi00Ljg3LTQuMDZTMTkgMjguNTQgMTkgMzAuNzhxMCAxLjIyIDMuODEgNy42MmExLjI0IDEuMjQgMCAwMDEuMDYuNTcgMS4wOCAxLjA4IDAgMDAxLS41NnoiIGZpbGw9IiNiZGNmYzgiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGNsYXNzPSJjbHMtNCIgZD0iTTE4Ljk2IDMxLjA3aDkuNjVMMjcgNDcuMTRoLTYuNDNsLTEuNjEtMTYuMDd6Ii8+PHBhdGggZD0iTTM5LjgxIDQ4LjgyYTIwIDIwIDAgMDEtMzIuMDkgMGwuODQtNi4xMWEyLjY4IDIuNjggMCAwMDEgLjE5IDIuODMgMi44MyAwIDAwMi44MS0yLjQzdjEuMjJhMi44NCAyLjg0IDAgMDA1LjY4IDB2MS42MmEyLjg1IDIuODUgMCAwMDUuNjkgMCAyLjg0IDIuODQgMCAwMDUuNjggMHYtMS41N2EyLjg0IDIuODQgMCAxMDUuNjggMHYtMS4yMkEyLjg0IDIuODQgMCAwMDM4IDQzYTIuODcgMi44NyAwIDAwMS0uMThsLjgxIDZ6IiBmaWxsPSIjODA5ZWIwIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz48cGF0aCBkPSJNOC4zIDQ0LjY1bC4yNi0xLjg5YTIuNjggMi42OCAwIDAwMSAuMTkgMi44MyAyLjgzIDAgMDAyLjgxLTIuNDN2MS4yMmEyLjg0IDIuODQgMCAwMDUuNjggMHYxLjYyYTIuODUgMi44NSAwIDAwNS42OSAwIDIuODQgMi44NCAwIDAwNS42OCAwdi0xLjYyYTIuODQgMi44NCAwIDEwNS42OCAwdi0xLjIyQTIuODQgMi44NCAwIDAwMzggNDNhMi44NyAyLjg3IDAgMDAxLS4xOGwuMjUgMS44OWEyLjg1IDIuODUgMCAwMS00LjA3LTIuMTR2MS4yMmEyLjg0IDIuODQgMCAxMS01LjY4IDB2MS42MmEyLjg0IDIuODQgMCAwMS01LjY4IDAgMi44NSAyLjg1IDAgMDEtNS42OSAwdi0xLjY3YTIuODQgMi44NCAwIDAxLTUuNjggMHYtMS4yMkEyLjgzIDIuODMgMCAwMTkuNTggNDVhMi45IDIuOSAwIDAxLTEuMjgtLjN6IiBmaWxsPSIjNzM4ZTllIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz48cGF0aCBjbGFzcz0iY2xzLTExIiBkPSJNMzcuNzggMjIuMzljLTEtMi44Ny0zLTQuNjktNC43Mi00LjUxLTIuMi4yMy0yLjc0IDMuNzYtMi4yOSA4czEuNyA3LjU2IDMuOSA3LjMzIDQtMy44OSAzLjY4LThjLS4wOCAxLjIzLS41MiAyLjI4LTEuMzkgMi4zNS0xLjEyLjEtMS40NC0xLjE5LTEuNTktMi44MnMtLjE0LTMgMS0zLjA4YTEuNTEgMS41MSAwIDAxMS40MS43M3oiLz48cGF0aCBjbGFzcz0iY2xzLTEyIiBkPSJNMzcgMjEuNzVjLS42My0xLjIxLTEuNS0xLjk1LTIuMzktMS44NS0xLjUxLjE1LTEuODcgMi41Ny0xLjU3IDUuNDdzMS4xNyA1LjE4IDIuNjcgNWMxLjExLS4xMiAxLjkzLTEuNSAyLjE2LTMuMzhhMS4xNiAxLjE2IDAgMDEtLjg5LjU3Yy0xLjEyLjEtMS40NC0xLjE5LTEuNTktMi44MnMtLjE0LTMgMS0zLjA4YTEuNjEgMS42MSAwIDAxLjYxLjA5eiIvPjxwYXRoIGNsYXNzPSJjbHMtMTEiIGQ9Ik05LjYgMjIuMzljMS0yLjg3IDMtNC42OSA0LjcyLTQuNTEgMi4yLjIzIDIuNzQgMy43NiAyLjI5IDhzLTEuNyA3LjU2LTMuOSA3LjMzLTQtMy44OS0zLjY4LThjLjA4IDEuMjMuNTEgMi4yOCAxLjM5IDIuMzUgMS4xMi4xIDEuNDQtMS4xOSAxLjU4LTIuODJzLjE1LTMtMS0zLjA4YTEuNTEgMS41MSAwIDAwLTEuNDMuNzF6Ii8+PHBhdGggY2xhc3M9ImNscy0xMiIgZD0iTTEwLjM3IDIxLjc1Yy42My0xLjIxIDEuNTEtMS45NSAyLjQtMS44NSAxLjUuMTUgMS44NyAyLjU3IDEuNTYgNS40N3MtMS4xNiA1LjE4LTIuNjcgNWMtMS4xMS0uMTItMS45My0xLjUtMi4xNi0zLjM4YTEuMTggMS4xOCAwIDAwLjkuNTdjMS4xMS4xIDEuNDQtMS4xOSAxLjU4LTIuODJzLjE0LTMtMS0zLjA4YTEuNjggMS42OCAwIDAwLS42NC4wN3oiLz48cGF0aCBkPSJNMTkgMjguNjNhNS4zNCA1LjM0IDAgMDEwLS42OWMwLTIuNDcgMS4yMS01LjI4IDQuODctNS4yOHM0Ljg3IDIuODEgNC44NyA1LjI4YTQuNCA0LjQgMCAwMS0uMTMgMWMtLjgtMS4zNS0yLjMtMi4xOC00LjgtMi4xOC0yLjM3LjAzLTMuOTEuNzItNC44MSAxLjg3eiIgZmlsbD0iI2Y0ZjhmZiIgZmlsbC1ydWxlPSJldmVub2RkIi8+PHBhdGggY2xhc3M9ImNscy0xMSIgZD0iTTI2LjUyIDkuMTZMMjMuMzQgOWwzLjkzLTEuMTZhMS4zNSAxLjM1IDAgMDEtLjc1IDEuMzJ6TTIzIDguMTRsLTEuMzIgMWExNi43NyAxNi43NyAwIDAwMi0zLjcyQTYuNTYgNi41NiAwIDAwMjQgMi43NSAyLjM2IDIuMzYgMCAwMTI1LjIxIDVhMi40MyAyLjQzIDAgMDEtLjc1IDEuNTFBMTUgMTUgMCAwMDIzIDguMTR6Ii8+PHBhdGggZD0iTTEyOS41OCA1My43OXYtOS4zNWgxLjQ3di45M2EyLjcyIDIuNzIgMCAwMTIuMTgtMS4wOWMxLjc1IDAgMyAxLjMxIDMgMy41NHMtMS4yNCAzLjU2LTMgMy41NmEyLjY3IDIuNjcgMCAwMS0yLjE4LTEuMTF2My41MnptMy4yMS04LjIxYTIuMjIgMi4yMiAwIDAwLTEuNzQuOTF2Mi42OGEyLjI1IDIuMjUgMCAwMDEuNzQuOTEgMiAyIDAgMDAxLjkxLTIuMjYgMiAyIDAgMDAtMS45MS0yLjI0em00LjkxLTEuMTRoMS40N3YxYTIuODkgMi44OSAwIDAxMi4yLTEuMTV2MS40NmEyIDIgMCAwMC0uNDYgMCAyLjM2IDIuMzYgMCAwMC0xLjc0Ljg5djQuNjFoLTEuNDd6bTQuNDQgMy4zOGEzLjQ4IDMuNDggMCAxMTMuNDcgMy41NiAzLjM4IDMuMzggMCAwMS0zLjQ3LTMuNTZ6bTUuNDQgMGEyIDIgMCAxMC0yIDIuMjYgMiAyIDAgMDAyLTIuMjZ6bTcuNzYgMi40N2EyLjczIDIuNzMgMCAwMS0yLjE3IDEuMDljLTEuNzMgMC0zLTEuMzItMy0zLjU1czEuMjYtMy41NSAzLTMuNTVhMi43MSAyLjcxIDAgMDEyLjE3IDEuMXYtMy41MWgxLjQ4djkuMzRoLTEuNDh6bTAtMy44YTIuMjIgMi4yMiAwIDAwLTEuNzUtLjkxIDIgMiAwIDAwLTEuOSAyLjI1IDIgMiAwIDAwMS45IDIuMjUgMi4yMiAyLjIyIDAgMDAxLjc1LS45em03Ljk0IDMuODJhMy4yMyAzLjIzIDAgMDEtMi4zOSAxLjA3IDEuOTIgMS45MiAwIDAxLTIuMTctMi4xNHYtNC44aDEuNDd2NC4yNmMwIDEgLjUzIDEuMzggMS4zNiAxLjM4YTIuMjIgMi4yMiAwIDAwMS43My0uODl2LTQuNzVoMS40N3Y2Ljc3aC0xLjQ3em02LjQ2LTYuMDNhMy4wNSAzLjA1IDAgMDEyLjU5IDEuMmwtMSAuOWExLjc5IDEuNzkgMCAwMC0xLjU1LS44IDIuMjYgMi4yNiAwIDAwMCA0LjUgMS44NyAxLjg3IDAgMDAxLjU1LS44bDEgLjg5YTMgMyAwIDAxLTIuNTkgMS4yMSAzLjU1IDMuNTUgMCAwMTAtNy4xem00LjE3IDUuMzZ2LTMuOTFoLTEuMTJ2LTEuMjloMS4xMnYtMS44NWgxLjQ3djEuODVoMS4zN3YxLjI5aC0xLjM3djMuNTVjMCAuNDYuMjIuOC42NC44YTEgMSAwIDAwLjY2LS4yNGwuMzUgMS4xYTEuOTEgMS45MSAwIDAxLTEuMzkuNDQgMS41NiAxLjU2IDAgMDEtMS43My0xLjc0em0tMTExLjcxLjg0YTIuODcgMi44NyAwIDAxLTIuMTkuOSAyLjI1IDIuMjUgMCAwMS0yLjM1LTIuMjQgMi4xOCAyLjE4IDAgMDEyLjM0LTIuMiAyLjggMi44IDAgMDEyLjE5Ljg2di0xYzAtLjc5LS42NC0xLjI2LTEuNTgtMS4yNmEyLjc5IDIuNzkgMCAwMC0yIC44NWwtLjYtMWE0LjA1IDQuMDUgMCAwMTIuODUtMS4wOWMxLjQ5IDAgMi44MS42MyAyLjgxIDIuNDV2NC40OEg2Mi4yem0wLTEuODNhMiAyIDAgMDAtMS42MS0uNyAxLjIzIDEuMjMgMCAxMDAgMi40MiAyIDIgMCAwMDEuNjEtLjd6IiBmaWxsPSIjOTk5Ii8+PC9zdmc+'; if ( ! wp_mail_smtp()->is_pro() ) { $contact_url = 'https://wordpress.org/support/plugin/wp-mail-smtp/'; } else { // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound $contact_url = esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/contact/', [ 'medium' => 'setup-wizard', 'content' => 'Contact Us' ] ) ); } ?> <style type="text/css"> #wp-mail-smtp-settings-area { visibility: hidden; animation: loadWpMailSMTPSettingsNoJSView 0s 2s forwards; } @keyframes loadWpMailSMTPSettingsNoJSView{ to { visibility: visible; } } body { background: #F1F1F1; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; margin: 0; } #wp-mail-smtp-settings-area .wp-mail-smtp-setup-wizard-header { text-align: center; border-top: 4px solid #E27730; } #wp-mail-smtp-settings-area .wp-mail-smtp-setup-wizard-header h1 { margin: 0; } #wp-mail-smtp-settings-area .wp-mail-smtp-logo { display: inline-block; width: 320px; margin-top: 10px; padding: 0 10px; } #wp-mail-smtp-settings-area .wp-mail-smtp-logo img { width: 100%; height: 100%; } #wp-mail-smtp-settings-error-loading-area { box-sizing: border-box; max-width: 90%; width: auto; margin: 0 auto; background: #fff; border: 1px solid #DDDDDD; border-radius: 6px; -webkit-box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.05); box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.05); padding: 20px 30px; } #wp-mail-smtp-settings-area .wp-mail-smtp-error-footer { text-align: center; margin-top: 20px; font-size: 14px; } #wp-mail-smtp-settings-area .wp-mail-smtp-error-footer a { color: #999999; } #wp-mail-smtp-error-js h3 { font-size: 24px; font-weight: 500; line-height: 23px; margin: 0 0 15px; color: #444444; } #wp-mail-smtp-error-js p.info, #wp-mail-smtp-error-js ul.info { color: #777777; font-size: 16px; line-height: 23px; margin: 0 0 10px; } #wp-mail-smtp-error-js ul.info { margin: -10px 0 20px; } #wp-mail-smtp-error-js a.button { display: inline-block; background-color: #E27730; color: #ffffff; line-height: 22px; font-size: 16px; padding: 14px 30px; font-weight: 500; border-radius: 3px; border: none; cursor: pointer; text-decoration: none; margin-top: 7px; } #wp-mail-smtp-error-js a.button:hover { background-color: #c45e1b; } #wp-mail-smtp-error-js .medium-bold { font-weight: 500; } #wp-mail-smtp-nojs-error-message > div { border: 1px solid #DDDDDD; border-left: 4px solid #DC3232; color: #777777; font-size: 14px; padding: 18px 18px 18px 21px; font-weight: 300; text-align: left; } @media (min-width: 782px) { #wp-mail-smtp-settings-area .wp-mail-smtp-logo { margin-top: 50px; padding: 0; } #wp-mail-smtp-settings-error-loading-area { width: 650px; margin-top: 40px; padding: 52px 67px 49px; } #wp-mail-smtp-settings-area .wp-mail-smtp-error-footer { margin-top: 50px; } #wp-mail-smtp-error-js p.info { margin: 0 0 20px; } } </style> <!--[if IE]> <style> #wp-mail-smtp-settings-area{ visibility: visible !important; } </style> <![endif]--> <div id="<?php echo esc_attr( $id ); ?>"> <div id="wp-mail-smtp-settings-area" class="wp-mail-smtp-settings-area wpms-container"> <header class="wp-mail-smtp-setup-wizard-header"> <h1 class="wp-mail-smtp-setup-wizard-logo"> <div class="wp-mail-smtp-logo"> <img src="<?php echo esc_attr( $inline_logo_image ); ?>" alt="<?php esc_attr_e( 'WP Mail SMTP logo', 'wp-mail-smtp' ); ?>" class="wp-mail-smtp-logo-img"> </div> </h1> </header> <div id="wp-mail-smtp-settings-error-loading-area-container"> <div id="wp-mail-smtp-settings-error-loading-area"> <div> <div id="wp-mail-smtp-error-js"> <h3><?php esc_html_e( 'Whoops, something\'s not working.', 'wp-mail-smtp' ); ?></h3> <p class="info"><?php esc_html_e( 'It looks like something is preventing JavaScript from loading on your website. WP Mail SMTP requires JavaScript in order to give you the best possible experience.', 'wp-mail-smtp' ); ?></p> <p class="info"> <?php esc_html_e( 'In order to fix this issue, please check each of the items below:', 'wp-mail-smtp' ); ?> </p> <ul class="info"> <li><?php esc_html_e( 'If you are using an ad blocker, please disable it or whitelist the current page.', 'wp-mail-smtp' ); ?></li> <li><?php esc_html_e( 'If you aren\'t already using Chrome, Firefox, Safari, or Edge, then please try switching to one of these popular browsers.', 'wp-mail-smtp' ); ?></li> <li><?php esc_html_e( 'Confirm that your browser is updated to the latest version.', 'wp-mail-smtp' ); ?></li> </ul> <p class="info"> <?php esc_html_e( 'If you\'ve checked each of these details and are still running into issues, then please get in touch with our support team. We’d be happy to help!', 'wp-mail-smtp' ); ?> </p> <div style="display: none;" id="wp-mail-smtp-nojs-error-message"> <div> <strong style="font-weight: 500;" id="wp-mail-smtp-alert-message"></strong> </div> <p style="font-size: 14px;color: #777777;padding-bottom: 15px;"><?php esc_html_e( 'Copy the error message above and paste it in a message to the WP Mail SMTP support team.', 'wp-mail-smtp' ); ?></p> </div> <a href="<?php echo esc_url( $contact_url ); ?>" target="_blank" class="button" rel="noopener noreferrer"> <?php esc_html_e( 'Contact Us', 'wp-mail-smtp' ); ?> </a> </div> </div> </div> <div class="wp-mail-smtp-error-footer"> <?php echo wp_kses_post( $footer ); ?> </div> </div> </div> </div> <?php } /** * Attempt to catch the js error preventing the Vue app from loading and displaying that message for better support. * * @since 2.6.0 */ private function settings_inline_js() { ?> <script type="text/javascript"> window.onerror = function myErrorHandler( errorMsg, url, lineNumber ) { /* Don't try to put error in container that no longer exists post-vue loading */ var message_container = document.getElementById( 'wp-mail-smtp-nojs-error-message' ); if ( ! message_container ) { return false; } var message = document.getElementById( 'wp-mail-smtp-alert-message' ); message.innerHTML = errorMsg; message_container.style.display = 'block'; return false; } </script> <?php } /** * Ajax handler for retrieving the plugin settings. * * @since 2.6.0 */ public function get_settings() { check_ajax_referer( 'wpms-admin-nonce', 'nonce' ); if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) { wp_send_json_error( esc_html__( 'You don\'t have permission to change options for this WP site!', 'wp-mail-smtp' ) ); } $options = Options::init(); wp_send_json_success( $options->get_all() ); } /** * Ajax handler for starting the Setup Wizard steps. * * @since 3.1.0 */ public function wizard_steps_started() { check_ajax_referer( 'wpms-admin-nonce', 'nonce' ); if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) { wp_send_json_error( esc_html__( 'You don\'t have permission to change options for this WP site!', 'wp-mail-smtp' ) ); } self::update_stats( [ 'launched_time' => time(), ] ); wp_send_json_success(); } /** * Ajax handler for updating the settings. * * @since 2.6.0 */ public function update_settings() { check_ajax_referer( 'wpms-admin-nonce', 'nonce' ); if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) { wp_send_json_error(); } $options = Options::init(); $overwrite = ! empty( $_POST['overwrite'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $value = isset( $_POST['value'] ) ? wp_slash( json_decode( wp_unslash( $_POST['value'] ), true ) ) : []; // Cancel summary report email task if summary report email was disabled. if ( ! SummaryReportEmail::is_disabled() && isset( $value['general'][ SummaryReportEmail::SETTINGS_SLUG ] ) && $value['general'][ SummaryReportEmail::SETTINGS_SLUG ] === true ) { ( new SummaryReportEmailTask() )->cancel(); } /** * Before updating settings in Setup Wizard. * * @since 3.3.0 * * @param array $post POST data. */ do_action( 'wp_mail_smtp_admin_setup_wizard_update_settings', $value ); $options->set( $value, false, $overwrite ); wp_send_json_success(); } /** * Ajax handler for importing settings from other SMTP plugins. * * @since 2.6.0 */ public function import_settings() { check_ajax_referer( 'wpms-admin-nonce', 'nonce' ); if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) { wp_send_json_error( esc_html__( 'You don\'t have permission to change options for this WP site!', 'wp-mail-smtp' ) ); } $other_plugin = ! empty( $_POST['value'] ) ? sanitize_text_field( wp_unslash( $_POST['value'] ) ) : ''; if ( empty( $other_plugin ) ) { wp_send_json_error(); } $other_plugin_settings = ( new PluginImportDataRetriever( $other_plugin ) )->get(); if ( empty( $other_plugin_settings ) ) { wp_send_json_error(); } $options = Options::init(); $options->set( $other_plugin_settings, false, false ); wp_send_json_success(); } /** * Detect if any other SMTP plugin options are defined. * Other SMTP plugins: * - Easy WP SMTP * - Post SMTP Mailer * - SMTP Mailer * - WP SMTP * - FluentSMTP * * @since 2.6.0 * @since 3.2.0 Added FluentSMTP. * * @return array */ private function detect_other_smtp_plugins() { $data = []; $plugins = [ 'easy-smtp' => 'swpsmtp_options', 'post-smtp-mailer' => 'postman_options', 'smtp-mailer' => 'smtp_mailer_options', 'wp-smtp' => 'wp_smtp_options', 'fluent-smtp' => 'fluentmail-settings', ]; foreach ( $plugins as $plugin_slug => $plugin_options ) { $options = get_option( $plugin_options ); if ( ! empty( $options ) ) { $data[] = $plugin_slug; } } return $data; } /** * Prepare mailer options for all mailers. * * @since 2.6.0 * @since 3.10.0 Supply WPMS_AMAZONSES_DISPLAY_IDENTITIES constant value to control display of Amazon SES identity list. * @since 3.11.0 Removed WPMS_AMAZONSES_DISPLAY_IDENTITIES constant handling. * * @return array */ private function prepare_mailer_options() { $data = []; foreach ( wp_mail_smtp()->get_providers()->get_options_all() as $provider ) { $data[ $provider->get_slug() ] = [ 'slug' => $provider->get_slug(), 'title' => $provider->get_title(), 'description' => $provider->get_description(), 'edu_notice' => $provider->get_notice( 'educational' ), 'min_php' => $provider->get_php_version(), 'disabled' => $provider->is_disabled(), ]; if ( $provider->get_slug() === 'gmail' ) { $data['gmail']['redirect_uri'] = \WPMailSMTP\Providers\Gmail\Auth::get_oauth_redirect_url(); } } return apply_filters( 'wp_mail_smtp_admin_setup_wizard_prepare_mailer_options', $data ); } /** * AJAX callback for getting the oAuth authorization URL. * * @since 2.6.0 */ public function get_oauth_url() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh check_ajax_referer( 'wpms-admin-nonce', 'nonce' ); if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) { wp_send_json_error(); } $data = []; $mailer = ! empty( $_POST['mailer'] ) ? sanitize_text_field( wp_unslash( $_POST['mailer'] ) ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $settings = isset( $_POST['settings'] ) ? wp_slash( json_decode( wp_unslash( $_POST['settings'] ), true ) ) : []; if ( empty( $mailer ) ) { wp_send_json_error(); } $settings = array_merge( $settings, [ 'is_setup_wizard_auth' => true ] ); $options = Options::init(); $options->set( [ $mailer => $settings ], false, false ); switch ( $mailer ) { case 'gmail': $auth = wp_mail_smtp()->get_providers()->get_auth( 'gmail' ); if ( $auth->is_clients_saved() && $auth->is_auth_required() ) { $data['oauth_url'] = $auth->get_auth_url(); } break; } $data = apply_filters( 'wp_mail_smtp_admin_setup_wizard_get_oauth_url', $data, $mailer ); wp_send_json_success( array_merge( [ 'mailer' => $mailer ], $data ) ); } /** * AJAX callback for getting the oAuth connected data. * * @since 2.6.0 */ public function get_connected_data() { // phpcs:ignore Generic.Metrics.NestingLevel.MaxExceeded check_ajax_referer( 'wpms-admin-nonce', 'nonce' ); if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) { wp_send_json_error(); } $data = []; $mailer = ! empty( $_POST['mailer'] ) ? sanitize_text_field( wp_unslash( $_POST['mailer'] ) ) : ''; if ( empty( $mailer ) ) { wp_send_json_error(); } switch ( $mailer ) { case 'gmail': $auth = wp_mail_smtp()->get_providers()->get_auth( 'gmail' ); if ( $auth->is_clients_saved() && ! $auth->is_auth_required() ) { $user_info = $auth->get_user_info(); $data['connected_email'] = $user_info['email']; } break; } wp_send_json_success( array_merge( [ 'mailer' => $mailer ], $data ) ); } /** * AJAX callback for removing the oAuth authorization connection. * * @since 2.6.0 */ public function remove_oauth_connection() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh check_ajax_referer( 'wpms-admin-nonce', 'nonce' ); if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) { wp_send_json_error(); } $mailer = ! empty( $_POST['mailer'] ) ? sanitize_text_field( wp_unslash( $_POST['mailer'] ) ) : ''; if ( empty( $mailer ) ) { wp_send_json_error(); } $options = Options::init(); $old_opt = $options->get_all_raw(); /* * Since Gmail mailer uses the same settings array for both the custom app and One-Click Setup, * we need to make sure we don't remove the wrong settings. */ if ( $mailer === 'gmail' ) { unset( $old_opt[ $mailer ]['access_token'] ); unset( $old_opt[ $mailer ]['refresh_token'] ); unset( $old_opt[ $mailer ]['user_details'] ); unset( $old_opt[ $mailer ]['auth_code'] ); } else { foreach ( $old_opt[ $mailer ] as $key => $value ) { // Unset everything except Client ID, Client Secret and Domain (for Zoho). if ( ! in_array( $key, [ 'domain', 'client_id', 'client_secret' ], true ) ) { unset( $old_opt[ $mailer ][ $key ] ); } } } $options->set( $old_opt ); wp_send_json_success(); } /** * AJAX callback for installing a plugin. * Has to contain the `slug` POST parameter. * * @since 2.6.0 */ public function install_plugin() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded check_ajax_referer( 'wpms-admin-nonce', 'nonce' ); // Check for permissions. if ( ! current_user_can( 'install_plugins' ) ) { wp_send_json_error( esc_html__( 'Could not install the plugin. You don\'t have permission to install plugins.', 'wp-mail-smtp' ) ); } if ( ! current_user_can( 'activate_plugins' ) ) { wp_send_json_error( esc_html__( 'Could not install the plugin. You don\'t have permission to activate plugins.', 'wp-mail-smtp' ) ); } $slug = ! empty( $_POST['slug'] ) ? sanitize_text_field( wp_unslash( $_POST['slug'] ) ) : ''; if ( empty( $slug ) ) { wp_send_json_error( esc_html__( 'Could not install the plugin. Plugin slug is missing.', 'wp-mail-smtp' ) ); } if ( ! in_array( $slug, wp_list_pluck( $this->get_partner_plugins(), 'slug' ), true ) ) { wp_send_json_error( esc_html__( 'Could not install the plugin. Plugin is not whitelisted.', 'wp-mail-smtp' ) ); } $url = esc_url_raw( WP::admin_url( 'admin.php?page=' . Area::SLUG . '-setup-wizard' ) ); /* * The `request_filesystem_credentials` function will output a credentials form in case of failure. * We don't want that, since it will break AJAX response. So just hide output with a buffer. */ ob_start(); // phpcs:ignore WPForms.Formatting.EmptyLineAfterAssigmentVariables.AddEmptyLine $creds = request_filesystem_credentials( $url, '', false, false, null ); ob_end_clean(); // Check for file system permissions. if ( false === $creds ) { wp_send_json_error( esc_html__( 'Could not install the plugin. Don\'t have file permission.', 'wp-mail-smtp' ) ); } if ( ! WP_Filesystem( $creds ) ) { wp_send_json_error( esc_html__( 'Could not install the plugin. Don\'t have file permission.', 'wp-mail-smtp' ) ); } // Do not allow WordPress to search/download translations, as this will break JS output. remove_action( 'upgrader_process_complete', [ 'Language_Pack_Upgrader', 'async_upgrade' ], 20 ); // Import the plugin upgrader. Helpers::include_plugin_upgrader(); // Create the plugin upgrader with our custom skin. $installer = new Plugin_Upgrader( new PluginsInstallSkin() ); // Error check. if ( ! method_exists( $installer, 'install' ) || empty( $slug ) ) { wp_send_json_error( esc_html__( 'Could not install the plugin. WP Plugin installer initialization failed.', 'wp-mail-smtp' ) ); } include_once ABSPATH . 'wp-admin/includes/plugin-install.php'; $api = plugins_api( 'plugin_information', [ 'slug' => $slug, 'fields' => [ 'short_description' => false, 'sections' => false, 'requires' => false, 'rating' => false, 'ratings' => false, 'downloaded' => false, 'last_updated' => false, 'added' => false, 'tags' => false, 'compatibility' => false, 'homepage' => false, 'donate_link' => false, ], ] ); if ( is_wp_error( $api ) ) { wp_send_json_error( $api->get_error_message() ); } $installer->install( $api->download_link ); // Flush the cache and return the newly installed plugin basename. wp_cache_flush(); if ( $installer->plugin_info() ) { $plugin_basename = $installer->plugin_info(); // Disable the WPForms redirect after plugin activation. if ( $slug === 'wpforms-lite' ) { update_option( 'wpforms_activation_redirect', true ); add_option( 'wpforms_installation_source', 'wp-mail-smtp-setup-wizard' ); } // Disable the AIOSEO redirect after plugin activation. if ( $slug === 'all-in-one-seo-pack' ) { update_option( 'aioseo_activation_redirect', true ); } // Activate the plugin silently. $activated = activate_plugin( $plugin_basename ); // Disable the RafflePress redirect after plugin activation. if ( $slug === 'rafflepress' ) { delete_transient( '_rafflepress_welcome_screen_activation_redirect' ); } // Disable the MonsterInsights redirect after plugin activation. if ( $slug === 'google-analytics-for-wordpress' ) { delete_transient( '_monsterinsights_activation_redirect' ); } // Disable the SeedProd redirect after the plugin activation. if ( $slug === 'coming-soon' ) { delete_transient( '_seedprod_welcome_screen_activation_redirect' ); } if ( ! is_wp_error( $activated ) ) { wp_send_json_success( [ 'slug' => $slug, 'is_installed' => true, 'is_activated' => true, ] ); } else { wp_send_json_success( [ 'slug' => $slug, 'is_installed' => true, 'is_activated' => false, ] ); } } wp_send_json_error( esc_html__( 'Could not install the plugin. WP Plugin installer could not retrieve plugin information.', 'wp-mail-smtp' ) ); } /** * AJAX callback for getting all partner's plugin information. * * @since 2.6.0 */ public function get_partner_plugins_info() { check_ajax_referer( 'wpms-admin-nonce', 'nonce' ); if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) { wp_send_json_error( esc_html__( 'You don\'t have the permission to perform this action.', 'wp-mail-smtp' ) ); } $plugins = $this->get_partner_plugins(); $contact_form_plugin_already_installed = false; $contact_form_basenames = [ 'wpforms-lite/wpforms.php', 'wpforms/wpforms.php', 'formidable/formidable.php', 'formidable/formidable-pro.php', 'gravityforms/gravityforms.php', 'ninja-forms/ninja-forms.php', ]; $installed_plugins = get_plugins(); foreach ( $installed_plugins as $basename => $plugin_info ) { if ( in_array( $basename, $contact_form_basenames, true ) ) { $contact_form_plugin_already_installed = true; break; } } // Final check if maybe WPForms is already install and active as a MU plugin. if ( class_exists( '\WPForms\WPForms' ) ) { $contact_form_plugin_already_installed = true; } $data = [ 'plugins' => $plugins, 'contact_form_plugin_already_installed' => $contact_form_plugin_already_installed, ]; wp_send_json_success( $data ); } /** * Get the partner plugins data. * * @since 3.3.0 * * @return array[] */ private function get_partner_plugins() { $installed_plugins = get_plugins(); return [ [ 'slug' => 'wpforms-lite', 'name' => esc_html__( 'Contact Forms by WPForms', 'wp-mail-smtp' ), 'is_activated' => function_exists( 'wpforms' ), 'is_installed' => array_key_exists( 'wpforms-lite/wpforms.php', $installed_plugins ), ], [ 'slug' => 'all-in-one-seo-pack', 'name' => esc_html__( 'All in One SEO', 'wp-mail-smtp' ), 'is_activated' => class_exists( 'AIOSEOP_Core' ), 'is_installed' => array_key_exists( 'all-in-one-seo-pack/all_in_one_seo_pack.php', $installed_plugins ), ], [ 'slug' => 'google-analytics-for-wordpress', 'name' => esc_html__( 'Google Analytics by MonsterInsights', 'wp-mail-smtp' ), 'is_activated' => function_exists( 'MonsterInsights' ), 'is_installed' => array_key_exists( 'google-analytics-for-wordpress/googleanalytics.php', $installed_plugins ), ], [ 'slug' => 'insert-headers-and-footers', 'name' => esc_html__( 'Code Snippets by WPCode', 'wp-mail-smtp' ), 'is_activated' => class_exists( 'InsertHeadersAndFooters' ), 'is_installed' => array_key_exists( 'insert-headers-and-footers/ihaf.php', $installed_plugins ), ], [ 'slug' => 'rafflepress', 'name' => esc_html__( 'Giveaways by RafflePress', 'wp-mail-smtp' ), 'is_activated' => defined( 'RAFFLEPRESS_BUILD' ), 'is_installed' => array_key_exists( 'rafflepress/rafflepress.php', $installed_plugins ), ], [ 'slug' => 'instagram-feed', 'name' => esc_html__( 'Smash Balloon Social Photo Feed', 'wp-mail-smtp' ), 'is_activated' => function_exists( 'sb_instagram_feed_init' ), 'is_installed' => array_key_exists( 'instagram-feed/instagram-feed.php', $installed_plugins ), ], [ 'slug' => 'coming-soon', 'name' => esc_html__( 'SeedProd Landing Page Builder', 'wp-mail-smtp' ), 'is_activated' => defined( 'SEEDPROD_BUILD' ), 'is_installed' => array_key_exists( 'coming-soon/coming-soon.php', $installed_plugins ), ], [ 'slug' => 'wp-call-button', 'name' => esc_html__( 'WP Call Button', 'wp-mail-smtp' ), 'is_activated' => defined( 'WP_CALL_BUTTON_VERSION' ), 'is_installed' => array_key_exists( 'wp-call-button/wp-call-button.php', $installed_plugins ), ], ]; } /** * AJAX callback for subscribing an email address to the WP Mail SMTP Drip newsletter. * * @since 2.6.0 */ public function subscribe_to_newsletter() { check_ajax_referer( 'wpms-admin-nonce', 'nonce' ); if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) { wp_send_json_error( esc_html__( 'You don\'t have the permission to perform this action.', 'wp-mail-smtp' ) ); } $email = ! empty( $_POST['email'] ) ? filter_var( wp_unslash( $_POST['email'] ), FILTER_VALIDATE_EMAIL ) : ''; if ( empty( $email ) ) { wp_send_json_error(); } $body = [ 'email' => base64_encode( $email ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode ]; $wpforms_version_type = $this->get_wpforms_version_type(); if ( ! empty( $wpforms_version_type ) ) { $body['wpforms_version_type'] = $wpforms_version_type; } wp_remote_post( 'https://connect.wpmailsmtp.com/subscribe/drip/', [ 'user-agent' => Helpers::get_default_user_agent(), 'body' => $body, ] ); wp_send_json_success(); } /** * Get the WPForms version type if it's installed. * * @since 3.9.0 * * @return false|string Return `false` if WPForms is not installed, otherwise return either `lite` or `pro`. */ private function get_wpforms_version_type() { if ( ! function_exists( 'wpforms' ) ) { return false; } if ( method_exists( wpforms(), 'is_pro' ) ) { $is_wpforms_pro = wpforms()->is_pro(); } else { $is_wpforms_pro = wpforms()->pro; } return $is_wpforms_pro ? 'pro' : 'lite'; } /** * AJAX callback for plugin upgrade, from lite to pro. * * @since 2.6.0 */ public function upgrade_plugin() { check_ajax_referer( 'wpms-admin-nonce', 'nonce' ); if ( wp_mail_smtp()->is_pro() ) { wp_send_json_success( esc_html__( 'You are already using the WP Mail SMTP PRO version. Please refresh this page and verify your license key.', 'wp-mail-smtp' ) ); } if ( ! current_user_can( 'install_plugins' ) ) { wp_send_json_error( esc_html__( 'You don\'t have the permission to perform this action.', 'wp-mail-smtp' ) ); } $license_key = ! empty( $_POST['license_key'] ) ? sanitize_key( $_POST['license_key'] ) : ''; if ( empty( $license_key ) ) { wp_send_json_error( esc_html__( 'Please enter a valid license key!', 'wp-mail-smtp' ) ); } $url = Connect::generate_url( $license_key, '', add_query_arg( 'upgrade-redirect', '1', self::get_site_url() ) . '#/step/license' ); if ( empty( $url ) ) { wp_send_json_error( esc_html__( 'Upgrade functionality not available!', 'wp-mail-smtp' ) ); } wp_send_json_success( [ 'redirect_url' => $url ] ); } /** * AJAX callback for checking the mailer configuration. * - Send a test email * - Check the domain setup with the Domain Checker API. * * @since 2.6.0 */ public function check_mailer_configuration() { check_ajax_referer( 'wpms-admin-nonce', 'nonce' ); if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) { wp_send_json_error( esc_html__( 'You don\'t have the permission to perform this action.', 'wp-mail-smtp' ) ); } $options = Options::init(); $mailer = $options->get( 'mail', 'mailer' ); $from_email = $options->get( 'mail', 'from_email' ); $domain = ''; // Send the test mail. Domain check runs below with its own (warnings-tolerated) // threshold, so we opt out of TestEmail's stricter no_issues() check. $test_email = ( new TestEmail() ) ->with_context( TestEmail::CONTEXT_SETUP_WIZARD ) ->as_html( false ) ->with_domain_check( false ); $test_email->send( $this->get_test_email_recipient() ); if ( ! $test_email->is_successful() ) { $this->update_completed_stat( false, $mailer ); ( new UsageTracking() )->send_failed_setup_wizard_usage_tracking_data(); wp_send_json_error(); } // Add the optional sending domain parameter. if ( in_array( $mailer, [ 'mailgun', 'sendinblue', 'sendgrid' ], true ) ) { $domain = $options->get( $mailer, 'domain' ); } // Perform the domain checker API test. $domain_checker = new DomainChecker( $mailer, $from_email, $domain ); if ( $domain_checker->has_errors() ) { $this->update_completed_stat( false, $mailer ); ( new UsageTracking() )->send_failed_setup_wizard_usage_tracking_data( $domain_checker ); wp_send_json_error(); } $this->update_completed_stat( true, $mailer ); wp_send_json_success(); } /** * Get the test email recipient. * * @since 4.7.0 * * @return string */ private function get_test_email_recipient() { $options = Options::init(); $mailer = $options->get( 'mail', 'mailer' ); $from_email = $options->get( 'mail', 'from_email' ); /* * Some mailers in a test mode allows to send emails only to the registered * From email address, so we need to cover this case. */ $to_email = $from_email; $mailer_specific_constant_name = 'WPMS_SETUP_WIZARD_TEST_' . strtoupper( $mailer ) . '_EMAIL_RECIPIENT'; if ( defined( $mailer_specific_constant_name ) && is_email( constant( $mailer_specific_constant_name ) ) ) { $to_email = constant( $mailer_specific_constant_name ); } elseif ( defined( 'WPMS_SETUP_WIZARD_TEST_EMAIL_RECIPIENT' ) && is_email( WPMS_SETUP_WIZARD_TEST_EMAIL_RECIPIENT ) ) { $to_email = WPMS_SETUP_WIZARD_TEST_EMAIL_RECIPIENT; } return $to_email; } /** * AJAX callback for sending feedback. * * @since 2.6.0 */ public function send_feedback() { check_ajax_referer( 'wpms-admin-nonce', 'nonce' ); if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) { wp_send_json_error(); } // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $data = ! empty( $_POST['data'] ) ? json_decode( wp_unslash( $_POST['data'] ), true ) : []; $feedback = ! empty( $data['feedback'] ) ? sanitize_textarea_field( $data['feedback'] ) : ''; $permission = ! empty( $data['permission'] ); wp_remote_post( 'https://wpmailsmtp.com/wizard-feedback/', [ 'user-agent' => Helpers::get_default_user_agent(), 'body' => [ 'wpforms' => [ 'id' => 87892, 'fields' => [ '1' => $feedback, '2' => $permission ? wp_get_current_user()->user_email : '', '3' => wp_mail_smtp()->get_license_type(), '4' => WPMS_PLUGIN_VER, ], ], ], ] ); wp_send_json_success(); } /** * Data used for the Vue scripts to display old PHP and WP versions warnings. * * @since 2.6.0 */ private function prepare_versions_data() { global $wp_version; return array( 'php_version' => phpversion(), 'php_version_below_55' => apply_filters( 'wp_mail_smtp_temporarily_hide_php_under_55_upgrade_warnings', version_compare( phpversion(), '5.5', '<' ) ), 'php_version_below_56' => apply_filters( 'wp_mail_smtp_temporarily_hide_php_56_upgrade_warnings', version_compare( phpversion(), '5.6', '<' ) ), 'wp_version' => $wp_version, 'wp_version_below_49' => version_compare( $wp_version, '4.9', '<' ), ); } /** * Remove 'error' from the automatic clearing list of query arguments after page loads. * This will fix the issue with missing oAuth 'error' argument for the Setup Wizard. * * @since 2.6.0 * * @param array $defaults Array of query arguments to be cleared after page load. * * @return array */ public function maybe_disable_automatic_query_args_removal( $defaults ) { if ( ( isset( $_GET['page'] ) && $_GET['page'] === 'wp-mail-smtp-setup-wizard' ) && // phpcs:ignore WordPress.Security.NonceVerification.Recommended ( ! empty( $_GET['error'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended ) { $defaults = array_values( array_diff( $defaults, [ 'error' ] ) ); } return $defaults; } /** * Check if the Setup Wizard should load. * * @since 2.6.0 * * @return bool */ public function should_setup_wizard_load() { return (bool) apply_filters( 'wp_mail_smtp_admin_setup_wizard_load_wizard', true ); } /** * Get the Setup Wizard stats. * - launched_time -> when the Setup Wizard was last launched. * - completed_time -> when the Setup Wizard was last completed. * - was_successful -> if the Setup Wizard was completed successfully. * - mailer -> mailer slug configured in the Setup Wizard on its last completion attempt. * * @since 3.1.0 * * @return array */ public static function get_stats() { $defaults = [ 'launched_time' => 0, 'completed_time' => 0, 'was_successful' => false, 'mailer' => '', ]; return get_option( self::STATS_OPTION_KEY, $defaults ); } /** * Update the Setup Wizard stats. * * @since 3.1.0 * * @param array $options Take a look at SetupWizard::get_stats method for the possible array keys. */ public static function update_stats( $options ) { update_option( self::STATS_OPTION_KEY, array_merge( self::get_stats(), $options ) , false ); } /** * Update the completed Setup Wizard stats. * * @since 3.1.0 * * @param bool $was_successful If the Setup Wizard was completed successfully. * @param string $mailer Mailer slug configured in the Setup Wizard. */ private function update_completed_stat( $was_successful, $mailer = '' ) { self::update_stats( [ 'completed_time' => time(), 'was_successful' => $was_successful, 'mailer' => $mailer, ] ); } /** * Prepare an array of WP Mail SMTP PHP constants in use. * Those that are used in the setup wizard. * * @since 3.2.0 * * @return array */ private function prepare_defined_constants() { $options = Options::init(); if ( ! $options->is_const_enabled() ) { return []; } $constants = [ 'WPMS_MAIL_FROM' => [ 'mail', 'from_email' ], 'WPMS_MAIL_FROM_FORCE' => [ 'mail', 'from_email_force' ], 'WPMS_MAIL_FROM_NAME' => [ 'mail', 'from_name' ], 'WPMS_MAIL_FROM_NAME_FORCE' => [ 'mail', 'from_name_force' ], 'WPMS_MAILER' => [ 'mail', 'mailer' ], 'WPMS_SMTPCOM_API_KEY' => [ 'smtpcom', 'api_key' ], 'WPMS_SMTPCOM_CHANNEL' => [ 'smtpcom', 'channel' ], 'WPMS_SENDINBLUE_API_KEY' => [ 'sendinblue', 'api_key' ], 'WPMS_SENDINBLUE_DOMAIN' => [ 'sendinblue', 'domain' ], 'WPMS_AMAZONSES_CLIENT_ID' => [ 'amazonses', 'client_id' ], 'WPMS_AMAZONSES_CLIENT_SECRET' => [ 'amazonses', 'client_secret' ], 'WPMS_AMAZONSES_REGION' => [ 'amazonses', 'region' ], 'WPMS_GMAIL_CLIENT_ID' => [ 'gmail', 'client_id' ], 'WPMS_GMAIL_CLIENT_SECRET' => [ 'gmail', 'client_secret' ], 'WPMS_MAILGUN_API_KEY' => [ 'mailgun', 'api_key' ], 'WPMS_MAILGUN_DOMAIN' => [ 'mailgun', 'domain' ], 'WPMS_MAILGUN_REGION' => [ 'mailgun', 'region' ], 'WPMS_OUTLOOK_CLIENT_ID' => [ 'outlook', 'client_id' ], 'WPMS_OUTLOOK_CLIENT_SECRET' => [ 'outlook', 'client_secret' ], 'WPMS_POSTMARK_SERVER_API_TOKEN' => [ 'postmark', 'server_api_token' ], 'WPMS_POSTMARK_MESSAGE_STREAM' => [ 'postmark', 'message_stream' ], 'WPMS_SENDGRID_API_KEY' => [ 'sendgrid', 'api_key' ], 'WPMS_SENDGRID_DOMAIN' => [ 'sendgrid', 'domain' ], 'WPMS_SPARKPOST_API_KEY' => [ 'sparkpost', 'api_key' ], 'WPMS_SPARKPOST_REGION' => [ 'sparkpost', 'region' ], 'WPMS_ZOHO_DOMAIN' => [ 'zoho', 'domain' ], 'WPMS_ZOHO_CLIENT_ID' => [ 'zoho', 'client_id' ], 'WPMS_ZOHO_CLIENT_SECRET' => [ 'zoho', 'client_secret' ], 'WPMS_RESEND_API_KEY' => [ 'resend', 'api_key' ], 'WPMS_SMTP_HOST' => [ 'smtp', 'host' ], 'WPMS_SMTP_PORT' => [ 'smtp', 'port' ], 'WPMS_SSL' => [ 'smtp', 'encryption' ], 'WPMS_SMTP_AUTH' => [ 'smtp', 'auth' ], 'WPMS_SMTP_AUTOTLS' => [ 'smtp', 'autotls' ], 'WPMS_SMTP_USER' => [ 'smtp', 'user' ], 'WPMS_SMTP_PASS' => [ 'smtp', 'pass' ], 'WPMS_LOGS_ENABLED' => [ 'logs', 'enabled' ], 'WPMS_SUMMARY_REPORT_EMAIL_DISABLED' => [ 'general', SummaryReportEmail::SETTINGS_SLUG ], ]; $defined = []; foreach ( $constants as $constant => $group_and_key ) { if ( $options->is_const_defined( $group_and_key[0], $group_and_key[1] ) ) { $defined[] = $constant; } } return $defined; } } Pages/CodeSnippetsTab.php 0000644 00000037325 15252526345 0011366 0 ustar 00 <?php namespace WPMailSMTP\Admin\Pages; use WPMailSMTP\Admin\PageAbstract; use WPMailSMTP\Integrations\WPCode\RegisterLibrary; use WPMailSMTP\Integrations\WPCode\SnippetsProvider; use WPMailSMTP\WP; /** * Code Snippets tab: surfaces the plugin's WPCode library snippets. * * @since 4.9.0 */ class CodeSnippetsTab extends PageAbstract { /** * Tab slug. * * @since 4.9.0 * * @var string */ protected $slug = 'code-snippets'; /** * Tab display priority. * * @since 4.9.0 * * @var int */ protected $priority = 50; /** * Whether to display the placeholder fallback view (no live data, no cache). * * @since 4.9.0 * * @var bool */ private $display_fallback = false; /** * Link label of a tab. * * @since 4.9.0 * * @return string */ public function get_label() { return esc_html__( 'Code Snippets', 'wp-mail-smtp' ); } /** * Title of a tab. * * @since 4.9.0 * * @return string */ public function get_title() { return $this->get_label(); } /** * Register tab hooks. Runs only when this tab is the current one. * * @since 4.9.0 */ public function hooks() { add_action( 'wp_mail_smtp_admin_area_enqueue_assets', [ $this, 'enqueue_assets' ] ); } /** * Register tab AJAX handlers. Runs on every admin AJAX request. * * @since 4.9.0 */ public function ajax() { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks -- ajax() is the framework's AJAX-registration method; hooks() never runs during an AJAX request. add_action( 'wp_ajax_wp_mail_smtp_get_wpcode_snippet_code', [ $this, 'ajax_snippet_code' ] ); add_action( 'wp_ajax_wp_mail_smtp_get_wpcode_snippet_install_url', [ $this, 'ajax_snippet_install_url' ] ); } /** * Enqueue the tab's scripts and localized strings. * * @since 4.9.0 */ public function enqueue_assets() { wp_enqueue_script( 'wp-mail-smtp-listjs', wp_mail_smtp()->assets_url . '/js/vendor/list.min.js', [ 'jquery' ], '1.5.0', true ); // WP core CodeMirror for the read-only syntax-highlighted snippet preview. $code_editor_settings = wp_enqueue_code_editor( [ 'type' => 'text/x-php' ] ); wp_enqueue_script( 'wp-mail-smtp-code-snippets', wp_mail_smtp()->assets_url . '/js/smtp-code-snippets' . WP::asset_min() . '.js', [ 'jquery', 'wp-mail-smtp-listjs', 'wp-mail-smtp-admin', 'wp-mail-smtp-admin-jconfirm', 'code-editor' ], WPMS_PLUGIN_VER, true ); wp_localize_script( 'wp-mail-smtp-code-snippets', 'wp_mail_smtp_code_snippets', [ 'wpcode_active' => function_exists( 'wpcode_get_library_snippets_by_username' ), 'wpcode_path' => $this->get_wpcode_plugin_path(), 'activate_url' => $this->get_wpcode_activate_url(), 'code_editor' => $code_editor_settings, 'install_text' => esc_html__( 'Install Snippet', 'wp-mail-smtp' ), 'installing_text' => esc_html__( 'Installing…', 'wp-mail-smtp' ), 'loading_text' => esc_html__( 'Loading snippet…', 'wp-mail-smtp' ), 'error_text' => esc_html__( 'Could not load this snippet. Please try again.', 'wp-mail-smtp' ), 'install_popup_title' => esc_html__( 'Please Install WPCode to Use the Snippet Library', 'wp-mail-smtp' ), 'install_popup_desc' => esc_html__( 'Using WPCode, you can install WP Mail SMTP code snippets with 1 click right from this page.', 'wp-mail-smtp' ), 'install_popup_btn' => esc_html__( 'Install & Activate WPCode', 'wp-mail-smtp' ), 'learn_more_text' => esc_html__( 'Learn more about WPCode', 'wp-mail-smtp' ), 'learn_more_url' => 'https://wordpress.org/plugins/insert-headers-and-footers/', 'install_popup_icon' => esc_url( wp_mail_smtp()->assets_url . '/images/recommendations/plugins/wpcode/logo.svg' ), 'activate_popup_title' => esc_html__( 'Please Activate WPCode to Use the Snippet Library', 'wp-mail-smtp' ), 'activate_popup_desc' => esc_html__( 'WPCode is installed but not active. Activate it to install WP Mail SMTP code snippets with 1 click right from this page.', 'wp-mail-smtp' ), 'activate_popup_btn' => esc_html__( 'Activate WPCode', 'wp-mail-smtp' ), 'install_error_text' => esc_html__( 'WPCode could not be installed. Please try again.', 'wp-mail-smtp' ), 'activate_error_text' => esc_html__( 'WPCode was installed but could not be activated.', 'wp-mail-smtp' ), 'activate_manual_link' => esc_html__( 'Activate it manually.', 'wp-mail-smtp' ), 'lite_activate_url' => $this->get_lite_activate_url(), ] ); } /** * Whether WPCode is active (its library helper is available). * * @since 4.9.0 * * @return bool */ private function is_wpcode_active() { return function_exists( 'wpcode_get_library_snippets_by_username' ); } /** * Path of the installed WPCode plugin (pro preferred), or empty if none is installed. * * @since 4.9.0 * * @return string */ private function get_wpcode_plugin_path() { if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $installed = get_plugins(); foreach ( [ 'wpcode-premium/wpcode.php', 'insert-headers-and-footers/ihaf.php' ] as $path ) { if ( isset( $installed[ $path ] ) ) { return $path; } } return ''; } /** * WordPress activation URL for the installed WPCode plugin, or empty if none is installed. * * @since 4.9.0 * * @return string */ private function get_wpcode_activate_url() { $path = $this->get_wpcode_plugin_path(); return $path === '' ? '' : $this->build_activate_url( $path ); } /** * WordPress activation URL for WPCode Lite by its known basename. * * Built regardless of install state (nonces don't require the plugin to * exist), so the install popup can offer a manual-activation link when an * install succeeds but silent activation fails. * * @since 4.9.0 * * @return string */ private function get_lite_activate_url() { return $this->build_activate_url( 'insert-headers-and-footers/ihaf.php' ); } /** * Build a WordPress plugin-activation URL for a plugin basename. * * Uses esc_url_raw (not wp_nonce_url, which HTML-encodes &) so the URL stays * valid when assigned to window.location.href on the client. * * @since 4.9.0 * * @param string $path Plugin basename. * * @return string */ private function build_activate_url( $path ) { // In the multisite network admin, plugins are activated from the network // plugins screen; the nonce action is the same on both. $plugins_url = is_network_admin() ? network_admin_url( 'plugins.php' ) : admin_url( 'plugins.php' ); $url = add_query_arg( [ 'action' => 'activate', 'plugin' => $path, '_wpnonce' => wp_create_nonce( 'activate-plugin_' . $path ), ], $plugins_url ); return esc_url_raw( $url ); } /** * Resolve the snippets to display. * * @since 4.9.0 * * @return array */ private function get_snippets() { $provider = new SnippetsProvider( ( new RegisterLibrary() )->get_username() ); $snippets = $provider->get_snippets(); $this->display_fallback = $provider->is_fallback(); return $snippets; } /** * Output HTML of the tab. * * @since 4.9.0 */ public function display() { $snippets = $this->get_snippets(); $wpcode_active = $this->is_wpcode_active(); $display_fallback = $this->display_fallback; $wpcode_url = 'https://downloads.wordpress.org/plugin/insert-headers-and-footers.zip'; $container_class = $display_fallback ? 'wp-mail-smtp-wpcode-blur' : ''; $wpcode_path = $this->get_wpcode_plugin_path(); $activate_required = ! $wpcode_active && $wpcode_path !== ''; ?> <div class="wp-mail-smtp-wpcode"> <div class="wp-mail-smtp-wpcode-container <?php echo sanitize_html_class( $container_class ); ?>"> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-content section-heading wp-mail-smtp-section-heading--has-divider wp-mail-smtp-wpcode-header"> <div class="wp-mail-smtp-setting-field"> <h2><?php esc_html_e( 'Code Snippets', 'wp-mail-smtp' ); ?></h2> <p> <?php printf( wp_kses( /* translators: %s - WPCode library website URL. */ __( 'Using WPCode, you can install WP Mail SMTP code snippets with 1 click directly from this page or the <a href="%s" target="_blank" rel="noopener noreferrer">WPCode library</a>.', 'wp-mail-smtp' ), [ 'a' => [ 'href' => [], 'rel' => [], 'target' => [], ], ] ), esc_url( admin_url( 'admin.php?page=wpcode-library' ) ) ); ?> </p> </div> <div class="wp-mail-smtp-wpcode-header-search"> <span aria-hidden="true" class="wp-mail-smtp-wpcode-search-icon wpms:icon-[fa6-solid--magnifying-glass]"></span> <input type="search" id="wp-mail-smtp-wpcode-snippet-search" placeholder="<?php esc_attr_e( 'Search Snippets', 'wp-mail-smtp' ); ?>"> </div> </div> <div id="wp-mail-smtp-wpcode-snippets-list"> <div class="list"> <?php foreach ( $snippets as $snippet ) : ?> <?php $this->render_snippet_card( $snippet, $wpcode_active, $wpcode_url, $activate_required, $wpcode_path ); ?> <?php endforeach; ?> </div> <div id="wp-mail-smtp-wpcode-no-results"> <?php esc_html_e( "Sorry, we didn't find any snippets that match your criteria.", 'wp-mail-smtp' ); ?> </div> </div> </div> <?php if ( $display_fallback ) : ?> <div class="wp-mail-smtp-wpcode-fallback"> <img class="wp-mail-smtp-wpcode-fallback-icons" src="<?php echo esc_url( wp_mail_smtp()->assets_url . '/images/recommendations/plugins/wpcode/logo.svg' ); ?>" alt=""> <div class="wp-mail-smtp-wpcode-fallback-title"> <?php esc_html_e( 'Please Install WPCode to Use the Snippet Library', 'wp-mail-smtp' ); ?> </div> <div class="wp-mail-smtp-wpcode-fallback-description"> <?php esc_html_e( 'Using WPCode, you can install WP Mail SMTP code snippets with 1 click right from this page.', 'wp-mail-smtp' ); ?> </div> <button class="wp-mail-smtp-btn wp-mail-smtp-btn-lg wp-mail-smtp-btn-orange wp-mail-smtp-wpcode-fallback-install-plugin" data-action="install" data-plugin="<?php echo esc_url( $wpcode_url ); ?>"> <?php esc_html_e( 'Install & Activate WPCode', 'wp-mail-smtp' ); ?> </button> <a href="https://wordpress.org/plugins/insert-headers-and-footers/" class="wp-mail-smtp-wpcode-fallback-link"><?php esc_html_e( 'Learn more about WPCode', 'wp-mail-smtp' ); ?></a> </div> <?php endif; ?> </div> <?php } /** * Verify the AJAX nonce and capability; send a JSON error and exit on failure. * * @since 4.9.0 */ private function verify_ajax() { if ( ! check_ajax_referer( 'wp-mail-smtp-admin', 'nonce', false ) || ! current_user_can( wp_mail_smtp()->get_capability_manage_options() ) ) { wp_send_json_error(); } } /** * AJAX: return a single snippet's code for the preview modal. * * Resolves by library_id from the active-WPCode helper when available, * otherwise from the inactive-state fetcher. Code is returned raw (JSON * transport); the client renders it in a text context (CodeMirror), never * as HTML. * * @since 4.9.0 */ public function ajax_snippet_code() { $this->verify_ajax(); // Nonce verified in verify_ajax(). $library_id = isset( $_POST['library_id'] ) ? absint( wp_unslash( $_POST['library_id'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing if ( empty( $library_id ) ) { wp_send_json_error(); } $snippet = ( new SnippetsProvider( ( new RegisterLibrary() )->get_username() ) )->get_snippet( $library_id ); if ( empty( $snippet ) || empty( $snippet['code'] ) ) { wp_send_json_error(); } // The provider already normalized + sanitized these fields. wp_send_json_success( [ 'title' => $snippet['title'], 'note' => $snippet['note'], 'code' => $snippet['code'], 'code_type' => $snippet['code_type'], ] ); } /** * AJAX: return a snippet's WPCode install URL. * * Used after WPCode is activated for an installed-but-inactive plugin: with * WPCode now loaded, the active-state resolver returns the snippet's install * URL, which the client navigates to. * * @since 4.9.0 */ public function ajax_snippet_install_url() { $this->verify_ajax(); // Nonce verified in verify_ajax(). $library_id = isset( $_POST['library_id'] ) ? absint( wp_unslash( $_POST['library_id'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing if ( empty( $library_id ) ) { wp_send_json_error(); } $snippet = ( new SnippetsProvider( ( new RegisterLibrary() )->get_username() ) )->get_snippet( $library_id ); if ( empty( $snippet ) || empty( $snippet['install'] ) ) { wp_send_json_error(); } wp_send_json_success( [ 'install_url' => $snippet['install'] ] ); } /** * Output the markup for a single snippet card. * * @since 4.9.0 * * @param array $snippet Snippet view-model. * @param bool $wpcode_active Whether WPCode is active. * @param string $wpcode_url WPCode plugin download URL. * @param bool $activate_required Whether WPCode is installed but inactive. * @param string $wpcode_path Installed WPCode plugin path (empty if not installed). */ private function render_snippet_card( array $snippet, $wpcode_active, $wpcode_url, $activate_required = false, $wpcode_path = '' ) { $installed = ! empty( $snippet['installed'] ); ?> <div class="wp-mail-smtp-wpcode-snippet"> <div class="wp-mail-smtp-wpcode-snippet-header"> <h3 class="wp-mail-smtp-wpcode-snippet-title"><?php echo esc_html( $snippet['title'] ); ?></h3> <div class="wp-mail-smtp-wpcode-snippet-note"><?php echo esc_html( $snippet['note'] ); ?></div> </div> <div class="wp-mail-smtp-wpcode-snippet-footer"> <?php if ( $installed ) : ?> <a href="<?php echo esc_url( $snippet['install'] ); ?>" class="wp-mail-smtp-btn wp-mail-smtp-btn-md wp-mail-smtp-btn-grey wp-mail-smtp-wpcode-install-snippet" data-action="edit"> <?php esc_html_e( 'Edit Snippet', 'wp-mail-smtp' ); ?> </a> <?php else : ?> <?php if ( $wpcode_active ) : ?> <a href="<?php echo esc_url( $snippet['install'] ); ?>" class="wp-mail-smtp-btn wp-mail-smtp-btn-md wp-mail-smtp-btn-secondary wp-mail-smtp-wpcode-install-snippet" data-action="install"> <?php esc_html_e( 'Install Snippet', 'wp-mail-smtp' ); ?> </a> <?php elseif ( $activate_required ) : ?> <a href="#" class="wp-mail-smtp-btn wp-mail-smtp-btn-md wp-mail-smtp-btn-secondary wp-mail-smtp-wpcode-activate-plugin" data-library-id="<?php echo absint( $snippet['library_id'] ); ?>" data-plugin="<?php echo esc_attr( $wpcode_path ); ?>"> <?php esc_html_e( 'Install Snippet', 'wp-mail-smtp' ); ?> </a> <?php else : ?> <a href="#" class="wp-mail-smtp-btn wp-mail-smtp-btn-md wp-mail-smtp-btn-secondary wp-mail-smtp-wpcode-install-plugin" data-library-id="<?php echo absint( $snippet['library_id'] ); ?>" data-plugin="<?php echo esc_url( $wpcode_url ); ?>"> <?php esc_html_e( 'Install Snippet', 'wp-mail-smtp' ); ?> </a> <?php endif; ?> <a href="#" class="wp-mail-smtp-btn wp-mail-smtp-btn-md wp-mail-smtp-btn-grey wp-mail-smtp-wpcode-view-snippet" data-library-id="<?php echo absint( $snippet['library_id'] ); ?>" data-install-url="<?php echo $wpcode_active ? esc_url( $snippet['install'] ) : ''; ?>" data-plugin="<?php echo esc_url( $wpcode_url ); ?>"> <?php esc_html_e( 'View', 'wp-mail-smtp' ); ?> </a> <?php endif; ?> <span class="wp-mail-smtp-wpcode-snippet-badge"><?php echo $installed ? esc_html__( 'Installed', 'wp-mail-smtp' ) : ''; ?></span> </div> </div> <?php } } Pages/AiMcpTab.php 0000644 00000023351 15252526345 0007751 0 ustar 00 <?php namespace WPMailSMTP\Admin\Pages; use WPMailSMTP\Admin\PageAbstract; use WPMailSMTP\WP; /** * AI MCP tab: 1-click installs the Vibe AI MCP plugin and surfaces the read-only Abilities API. * * @since 4.9.0 */ class AiMcpTab extends PageAbstract { /** * WPVibe plugin basename on wp.org. * * @since 4.9.0 * * @var string */ const WPVIBE_BASENAME = 'vibe-ai/vibe-ai.php'; /** * WPVibe wp.org download URL. * * @since 4.9.0 * * @var string */ const WPVIBE_DOWNLOAD_URL = 'https://downloads.wordpress.org/plugin/vibe-ai.zip'; /** * WPVibe top-level admin page slug. * * @since 4.9.0 * * @var string */ const WPVIBE_PAGE_SLUG = 'vibe-ai'; /** * Tab slug. * * @since 4.9.0 * * @var string */ protected $slug = 'ai-mcp'; /** * Tab display priority. * * @since 4.9.0 * * @var int */ protected $priority = 60; /** * Link label of a tab. * * @since 4.9.0 * * @return string */ public function get_label() { return esc_html__( 'AI MCP', 'wp-mail-smtp' ); } /** * Title of a tab. * * @since 4.9.0 * * @return string */ public function get_title() { return $this->get_label(); } /** * Register tab hooks. Runs only when this tab is the current one. * * @since 4.9.0 */ public function hooks() { add_action( 'wp_mail_smtp_admin_area_enqueue_assets', [ $this, 'enqueue_assets' ] ); } /** * Enqueue the tab's install/activate script and localized strings. * * @since 4.9.0 */ public function enqueue_assets() { wp_enqueue_script( 'wp-mail-smtp-ai-mcp', wp_mail_smtp()->assets_url . '/js/smtp-ai-mcp' . WP::asset_min() . '.js', [ 'jquery', 'wp-mail-smtp-admin' ], WPMS_PLUGIN_VER, true ); wp_localize_script( 'wp-mail-smtp-ai-mcp', 'wp_mail_smtp_ai_mcp', [ 'error_text' => esc_html__( 'Something went wrong. Please try again.', 'wp-mail-smtp' ), ] ); } /** * Resolve the WPVibe install state: not installed, installed but inactive, or active. * * @since 4.9.0 * * @return string One of 'not_installed', 'installed_inactive', 'active'. */ private function get_wpvibe_state() { if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $plugins = get_plugins(); if ( ! array_key_exists( self::WPVIBE_BASENAME, $plugins ) ) { return 'not_installed'; } if ( ! is_plugin_active( self::WPVIBE_BASENAME ) ) { return 'installed_inactive'; } return 'active'; } /** * Output the state-dependent WPVibe CTA button. * * @since 4.9.0 * * @param string $state WPVibe state. * @param bool $can_install Whether the user can install plugins. * @param bool $can_activate Whether the user can activate plugins. * @param string $setup_url WPVibe admin page URL for the active state. */ private function render_cta_button( $state, $can_install, $can_activate, $setup_url ) { if ( $state === 'active' ) { ?> <a class="wp-mail-smtp-btn wp-mail-smtp-btn-lg wp-mail-smtp-btn-blueish wp-mail-smtp-ai-mcp-wpvibe-button" href="<?php echo esc_url( $setup_url ); ?>" ><?php esc_html_e( 'Set Up WPVibe', 'wp-mail-smtp' ); ?></a> <?php return; } if ( $state === 'installed_inactive' && $can_activate ) { ?> <button type="button" class="wp-mail-smtp-btn wp-mail-smtp-btn-lg wp-mail-smtp-btn-orange wp-mail-smtp-ai-mcp-wpvibe-button" data-action="activate" data-plugin="<?php echo esc_attr( self::WPVIBE_BASENAME ); ?>" ><?php esc_html_e( 'Activate WPVibe', 'wp-mail-smtp' ); ?></button> <?php return; } if ( $state === 'not_installed' && $can_install ) { ?> <button type="button" class="wp-mail-smtp-btn wp-mail-smtp-btn-lg wp-mail-smtp-btn-orange wp-mail-smtp-ai-mcp-wpvibe-button" data-action="install" data-plugin="<?php echo esc_attr( self::WPVIBE_DOWNLOAD_URL ); ?>" ><?php esc_html_e( 'Install & Activate WPVibe', 'wp-mail-smtp' ); ?></button> <?php return; } if ( $state === 'not_installed' ) { ?> <a href="https://wordpress.org/plugins/vibe-ai/" class="wp-mail-smtp-btn wp-mail-smtp-btn-lg wp-mail-smtp-btn-orange wp-mail-smtp-ai-mcp-wpvibe-button" target="_blank" rel="noopener noreferrer" ><?php esc_html_e( 'Install from WordPress.org', 'wp-mail-smtp' ); ?></a> <?php } } /** * Output HTML of the tab. * * @since 4.9.0 */ public function display() { $state = $this->get_wpvibe_state(); $is_pro = wp_mail_smtp()->is_pro(); $can_install = current_user_can( 'install_plugins' ); $can_activate = current_user_can( 'activate_plugins' ); $wpvibe_setup = admin_url( 'admin.php?page=' . self::WPVIBE_PAGE_SLUG ); $pro_badge_url = wp_mail_smtp()->assets_url . '/images/pro-badge-small.svg'; $docs_url = wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/using-wpmailsmtp-with-ai-assistants/', [ 'medium' => 'ai-mcp', 'content' => 'View Abilities API Documentation', ] ); // Icon classes are literal so the Tailwind scanner can generate the Iconify utilities. $cards = [ [ 'icon' => 'wpms:icon-[fa6-solid--rectangle-list]', 'title' => esc_html__( 'Email Logs', 'wp-mail-smtp' ), 'bullets' => [ esc_html__( 'Browse and filter logged emails by status, mailer, date, and recipient', 'wp-mail-smtp' ), esc_html__( "Get a single email's full details and content", 'wp-mail-smtp' ), ], 'pro' => true, ], [ 'icon' => 'wpms:icon-[fa6-solid--chart-column]', 'title' => esc_html__( 'Email Stats', 'wp-mail-smtp' ), 'bullets' => [ esc_html__( 'See aggregate sending stats for any period or date range', 'wp-mail-smtp' ), esc_html__( 'Scope stats to a single mailer and check the success rate', 'wp-mail-smtp' ), ], 'pro' => true, ], [ 'icon' => 'wpms:icon-[fa6-solid--bug]', 'title' => esc_html__( 'Debug Events', 'wp-mail-smtp' ), 'bullets' => [ esc_html__( 'List recorded send errors and debug entries', 'wp-mail-smtp' ), ], 'pro' => false, ], ]; ?> <div class="wp-mail-smtp-ai-mcp"> <section class="wp-mail-smtp-ai-mcp-hero"> <div class="wp-mail-smtp-ai-mcp-hero-copy"> <p class="wp-mail-smtp-ai-mcp-eyebrow"><?php esc_html_e( 'WordPress Abilities API + WP Mail SMTP', 'wp-mail-smtp' ); ?></p> <h2 class="wp-mail-smtp-ai-mcp-title"><?php esc_html_e( 'Use WP Mail SMTP With Your Favorite AI', 'wp-mail-smtp' ); ?></h2> <p class="wp-mail-smtp-ai-mcp-lede"> <?php echo wp_kses( sprintf( /* translators: %s - WPVibe.ai inline link. */ __( 'Connect your WordPress site and WP Mail SMTP to AI assistants like Claude, ChatGPT, Cursor, and more. Ask them to review your email logs, sending stats, and debug events in plain English. No copy-pasting, no exports. Connect them with the free %s plugin.', 'wp-mail-smtp' ), sprintf( '<a href="%s" target="_blank" rel="noopener noreferrer"><strong>WPVibe.ai</strong></a>', esc_url( 'https://wpvibe.ai/?utm_source=wpmailsmtpplugin&utm_medium=link&utm_campaign=ai-mcp-page' ) ) ), [ 'a' => [ 'href' => [], 'target' => [], 'rel' => [], ], 'strong' => [], ] ); ?> </p> <div class="wp-mail-smtp-ai-mcp-cta-row"> <?php $this->render_cta_button( $state, $can_install, $can_activate, $wpvibe_setup ); ?> </div> <?php if ( $state === 'not_installed' && ! $can_install ) : ?> <p class="wp-mail-smtp-ai-mcp-install-note"> <?php esc_html_e( 'Your site is configured to disallow plugin installation from the dashboard.', 'wp-mail-smtp' ); ?> </p> <?php endif; ?> </div> <img class="wp-mail-smtp-ai-mcp-hero-illustration" src="<?php echo esc_url( wp_mail_smtp()->assets_url . '/images/ai-mcp/hero-illustration.svg' ); ?>" alt="" role="presentation" > </section> <section class="wp-mail-smtp-ai-mcp-capabilities"> <header class="wp-mail-smtp-ai-mcp-capabilities-head"> <h3 class="wp-mail-smtp-ai-mcp-capabilities-title"><?php esc_html_e( 'Everything WP Mail SMTP Can Do With AI', 'wp-mail-smtp' ); ?></h3> <a class="wp-mail-smtp-ai-mcp-docs-link" href="<?php echo esc_url( $docs_url ); ?>" target="_blank" rel="noopener noreferrer" > <span class="wp-mail-smtp-ai-mcp-docs-text"><?php esc_html_e( 'View Abilities API Documentation', 'wp-mail-smtp' ); ?></span> <span class="wp-mail-smtp-ai-mcp-docs-arrow wpms:icon-[fa6-solid--arrow-right]" aria-hidden="true"></span> </a> </header> <div class="wp-mail-smtp-ai-mcp-cards"> <?php foreach ( $cards as $card ) : ?> <article class="wp-mail-smtp-ai-mcp-card"> <header class="wp-mail-smtp-ai-mcp-card-head"> <span class="wp-mail-smtp-ai-mcp-card-icon" aria-hidden="true"> <span class="<?php echo esc_attr( $card['icon'] ); ?>"></span> </span> <h4 class="wp-mail-smtp-ai-mcp-card-title"><?php echo esc_html( $card['title'] ); ?></h4> </header> <ul class="wp-mail-smtp-ai-mcp-card-bullets"> <?php foreach ( $card['bullets'] as $bullet ) : ?> <li> <span class="wp-mail-smtp-ai-mcp-card-dot" aria-hidden="true"></span> <span class="wp-mail-smtp-ai-mcp-card-bullet-text"><?php echo esc_html( $bullet ); ?></span> </li> <?php endforeach; ?> </ul> <?php if ( $card['pro'] && ! $is_pro ) : ?> <img class="wp-mail-smtp-ai-mcp-pro-badge" src="<?php echo esc_url( $pro_badge_url ); ?>" alt="<?php esc_attr_e( 'Pro feature', 'wp-mail-smtp' ); ?>" > <?php endif; ?> </article> <?php endforeach; ?> </div> </section> </div> <?php } } Pages/EmailReports.php 0000644 00000001312 15252526345 0010730 0 ustar 00 <?php namespace WPMailSMTP\Admin\Pages; use WPMailSMTP\Admin\ParentPageAbstract; /** * Class EmailReports. * * @since 3.0.0 */ class EmailReports extends ParentPageAbstract { /** * Page default tab slug. * * @since 3.0.0 * * @var string */ protected $default_tab = 'reports'; /** * Slug of a page. * * @since 3.0.0 * * @var string */ protected $slug = 'reports'; /** * Link label of a page. * * @since 3.0.0 * * @return string */ public function get_label() { return esc_html__( 'Email Reports', 'wp-mail-smtp' ); } /** * Title of a page. * * @since 3.0.0 * * @return string */ public function get_title() { return $this->get_label(); } } Pages/SettingsTab.php 0000644 00000034275 15252526345 0010567 0 ustar 00 <?php namespace WPMailSMTP\Admin\Pages; use WPMailSMTP\Admin\ConnectionSettings; use WPMailSMTP\Admin\PageAbstract; use WPMailSMTP\Admin\SetupWizard; use WPMailSMTP\Options; use WPMailSMTP\WP; /** * Class SettingsTab is part of Area, displays general settings of the plugin. * * @since 1.0.0 */ class SettingsTab extends PageAbstract { /** * Settings constructor. * * @since 1.5.0 */ public function __construct() { parent::__construct(); add_action( 'wp_mail_smtp_admin_pages_settings_license_key', array( __CLASS__, 'display_license_key_field_content' ) ); } /** * @var string Slug of a tab. */ protected $slug = 'settings'; /** * @inheritdoc */ public function get_label() { return esc_html__( 'General', 'wp-mail-smtp' ); } /** * @inheritdoc */ public function get_title() { return $this->get_label(); } /** * @inheritdoc */ public function display() { $options = Options::init(); ?> <form method="POST" action="" autocomplete="off" class="wp-mail-smtp-connection-settings-form"> <?php $this->wp_nonce_field(); ?> <?php ob_start(); ?> <!-- License Section Title --> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-content wp-mail-smtp-clear section-heading" id="wp-mail-smtp-setting-row-license-heading"> <div class="wp-mail-smtp-setting-field"> <h2><?php esc_html_e( 'License', 'wp-mail-smtp' ); ?></h2> <p class="desc"> <?php esc_html_e( 'Your license key provides access to updates and support.', 'wp-mail-smtp' ); ?> </p> </div> </div> <?php if ( ! wp_mail_smtp()->is_pro() ) : ?> <div class="wp-mail-smtp-upgrade-license-banner"> <p><?php echo wp_kses( __( 'You\'re using <strong>WP Mail SMTP Lite</strong> - no license needed. Enjoy!', 'wp-mail-smtp' ), [ 'strong' => [] ] ); ?> 🙂</p> <p class="wp-mail-smtp-upgrade-license-banner__discount-line"> <?php printf( wp_kses( /* Translators: %s - discount value $50 */ __( 'As a valued WP Mail SMTP Lite user, you can enjoy an exclusive <strong>%s discount</strong>, automatically applied at checkout to unlock even more features!', 'wp-mail-smtp' ), [ 'strong' => [], 'br' => [], ] ), '$50' ); ?> </p> <a href="<?php echo esc_url( wp_mail_smtp()->get_upgrade_link( 'general-license-key' ) ); ?>" target="_blank" rel="noopener noreferrer" class="wp-mail-smtp-btn wp-mail-smtp-btn-md wp-mail-smtp-btn-secondary wp-mail-smtp-upgrade-license-banner__upgrade-btn"><?php esc_html_e( 'Upgrade to Pro', 'wp-mail-smtp' ); ?></a> </div> <?php endif; ?> <!-- License Key --> <div id="wp-mail-smtp-setting-row-license_key" class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-license_key wp-mail-smtp-clear"> <div class="wp-mail-smtp-setting-label"> <label for="wp-mail-smtp-setting-license_key"><?php esc_html_e( 'License Key', 'wp-mail-smtp' ); ?></label> </div> <div class="wp-mail-smtp-setting-field"> <?php do_action( 'wp_mail_smtp_admin_pages_settings_license_key', $options ); ?> </div> </div> <!-- Mail Section Title --> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-content wp-mail-smtp-clear section-heading no-desc"> <div class="wp-mail-smtp-setting-field"> <h2><?php esc_html_e( 'Primary Connection', 'wp-mail-smtp' ); ?></h2> </div> </div> <?php if ( ! is_network_admin() ) : ?> <!-- Setup Wizard button --> <div id="wp-mail-smtp-setting-row-setup-wizard-button" class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-email wp-mail-smtp-clear"> <div class="wp-mail-smtp-setting-label"> <label for="wp-mail-smtp-setting-from_email"><?php esc_html_e( 'Setup Wizard', 'wp-mail-smtp' ); ?></label> </div> <div class="wp-mail-smtp-setting-field"> <a href="<?php echo esc_url( SetupWizard::get_site_url() ); ?>" class="wp-mail-smtp-btn wp-mail-smtp-btn-md wp-mail-smtp-btn-blueish"> <?php esc_html_e( 'Launch Setup Wizard', 'wp-mail-smtp' ); ?> </a> <p class="desc"> <?php esc_html_e( 'We\'ll guide you through each step needed to get WP Mail SMTP fully set up on your site.', 'wp-mail-smtp' ); ?> </p> </div> </div> <?php endif; ?> <?php $connection = wp_mail_smtp()->get_connections_manager()->get_primary_connection(); $connection_settings = new ConnectionSettings( $connection ); // Display connection settings. $connection_settings->display(); ?> <?php $this->display_backup_connection_education(); ?> <?php $settings_content = apply_filters( 'wp_mail_smtp_admin_settings_tab_display', ob_get_clean() ); echo $settings_content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> <?php $this->display_save_btn(); ?> </form> <?php $this->display_wpforms(); $this->display_pro_banner(); } /** * License key text for a Lite version of the plugin. * * @since 1.5.0 * * @param Options $options */ public static function display_license_key_field_content( $options ) { ?> <p> <?php esc_html_e( 'Already purchased? Simply enter your license key below to connect with WP Mail SMTP Pro!', 'wp-mail-smtp' ); ?> </p> <p> <input type="password" id="wp-mail-smtp-setting-upgrade-license-key" class="wp-mail-smtp-not-form-input" placeholder="<?php esc_attr_e( 'Paste license key here', 'wp-mail-smtp' ); ?>" value="" /> <button type="button" class="wp-mail-smtp-btn wp-mail-smtp-btn-md wp-mail-smtp-btn-orange" id="wp-mail-smtp-setting-upgrade-license-button"> <?php esc_attr_e( 'Connect', 'wp-mail-smtp' ); ?> </button> </p> <?php } /** * Display a WPForms related message. * * @since 1.3.0 * @since 1.4.0 Display only to site admins. * @since 1.5.0 Do nothing. */ protected function display_wpforms() { /* * Used to have this check: * * $is_dismissed = get_user_meta( get_current_user_id(), 'wp_mail_smtp_wpforms_dismissed', true ); */ } /** * Display WP Mail SMTP Pro upgrade banner. * * @since 1.5.0 */ protected function display_pro_banner() { // Display only to site admins. Only site admins can install plugins. if ( ! is_super_admin() ) { return; } // Do not display if WP Mail SMTP Pro already installed. if ( wp_mail_smtp()->is_pro() ) { return; } $is_dismissed = get_user_meta( get_current_user_id(), 'wp_mail_smtp_pro_banner_dismissed', true ); // Do not display if user dismissed. if ( (bool) $is_dismissed === true ) { return; } $assets_url = wp_mail_smtp()->assets_url; $screenshots = [ [ 'url' => $assets_url . '/images/logs/archive.png', 'url_thumbnail' => $assets_url . '/images/logs/archive-thumbnail.png', 'title' => __( 'Email Logs', 'wp-mail-smtp' ), ], [ 'url' => $assets_url . '/images/logs/single.png', 'url_thumbnail' => $assets_url . '/images/logs/single-thumbnail.png', 'title' => __( 'Individual Email Log', 'wp-mail-smtp' ), ], [ 'url' => $assets_url . '/images/email-reports/screenshot-01.png', 'url_thumbnail' => $assets_url . '/images/email-reports/thumbnail-01.png', 'title' => __( 'Email Reports', 'wp-mail-smtp' ), ], ]; ?> <div id="wp-mail-smtp-pro-banner" class="wp-mail-smtp-upgrade-banner"> <span class="wp-mail-smtp-pro-banner-dismiss"> <button id="wp-mail-smtp-pro-banner-dismiss"> <img src="<?php echo esc_url( wp_mail_smtp()->assets_url . '/images/icons/close.svg' ); ?>" alt="<?php esc_attr_e( 'Close', 'wp-mail-smtp' ); ?>"> </button> </span> <div class="wp-mail-smtp-upgrade-banner__row"> <h3 class="wp-mail-smtp-upgrade-banner__heading"> <?php esc_html_e( 'Level Up Your Email Game - Get Pro Features Now', 'wp-mail-smtp' ); ?> </h3> <p class="wp-mail-smtp-upgrade-banner__subheading"> <?php echo wp_kses( __( 'Upgrade and join over <strong>4,000,000</strong> websites!', 'wp-mail-smtp' ), [ 'strong' => [] ] ); ?> </p> </div> <div class="wp-mail-smtp-upgrade-banner__row"> <h3 class="wp-mail-smtp-upgrade-banner__heading"> <?php esc_html_e( 'Key Features You’ll Unlock:', 'wp-mail-smtp' ); ?> </h3> <ul class="wp-mail-smtp-upgrade-banner__features"> <li> <h5><?php esc_html_e( 'Peace of Mind - Never wonder about your email status', 'wp-mail-smtp' ); ?></h5> <p><?php esc_html_e( 'Email Logging, Alerts, and Backup Connection', 'wp-mail-smtp' ); ?></p> </li> <li> <h5><?php esc_html_e( 'Professional Email Services - Access enterprise-grade email providers', 'wp-mail-smtp' ); ?></h5> <p><?php esc_html_e( 'Gmail one-click setup, Microsoft 365 / Outlook, Amazon SES, and Zoho Mail', 'wp-mail-smtp' ); ?></p> </li> <li> <h5><?php esc_html_e( 'Effortless Management - Control your email experience', 'wp-mail-smtp' ); ?></h5> <p><?php esc_html_e( 'Smart Routing, Multisite Support, and Manage Notifications', 'wp-mail-smtp' ); ?></p> </li> <li> <h5><?php esc_html_e( 'White Glove Setup - Sit back while we handle everything', 'wp-mail-smtp' ); ?></h5> <p><?php esc_html_e( 'Professional Setup and World-Class Support', 'wp-mail-smtp' ); ?></p> </li> </ul> </div> <div class="wp-mail-smtp-upgrade-banner__row"> <a href="<?php echo esc_url( wp_mail_smtp()->get_upgrade_link( 'general-cta' ) ); ?>" target="_blank" rel="noopener noreferrer" class="wp-mail-smtp-btn wp-mail-smtp-btn-secondary wp-mail-smtp-upgrade-banner__upgrade-btn"> <?php esc_html_e( 'Upgrade to WP Mail SMTP Pro', 'wp-mail-smtp' ); ?> </a> <div class="wp-mail-smtp-upgrade-banner__discount-line"> <img src="<?php echo esc_url( wp_mail_smtp()->assets_url . '/images/icons/badge-percent.svg' ); ?>" alt="<?php esc_attr_e( 'Discount', 'wp-mail-smtp' ); ?>"> <p> <?php printf( wp_kses( /* Translators: %s - discount value $50. */ __( '<strong>%s OFF</strong> for WP Mail SMTP users, applied at checkout.', 'wp-mail-smtp' ), [ 'strong' => [], ] ), '$50' ); ?> </p> </div> </div> <div class="wp-mail-smtp-upgrade-banner__row"> <div class="wp-mail-smtp-product-education__screenshots wp-mail-smtp-product-education__screenshots--three"> <?php foreach ( $screenshots as $screenshot ) : ?> <div> <a href="<?php echo esc_url( $screenshot['url'] ); ?>" data-lity data-lity-desc="<?php echo esc_attr( $screenshot['title'] ); ?>"> <img src="<?php echo esc_url( $screenshot['url_thumbnail'] ); ?>" alt="<?php esc_attr( $screenshot['title'] ); ?>"> </a> <span><?php echo esc_html( $screenshot['title'] ); ?></span> </div> <?php endforeach; ?> </div> </div> </div> <?php } /** * Display backup connection education section. * * @since 3.7.0 */ private function display_backup_connection_education() { if ( wp_mail_smtp()->is_pro() ) { return; } $upgrade_link_url = wp_mail_smtp()->get_upgrade_link( [ 'medium' => 'Backup Connection Settings', 'content' => 'Upgrade to WP Mail SMTP Pro Link', ] ); ?> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-content wp-mail-smtp-clear section-heading"> <div class="wp-mail-smtp-setting-field"> <h4 class="wp-mail-smtp-product-education__heading"> <?php esc_html_e( 'Backup Connection', 'wp-mail-smtp' ); ?> </h4> <p class="wp-mail-smtp-product-education__description"> <?php echo wp_kses( sprintf( /* translators: %s - WPMailSMTP.com Upgrade page URL. */ __( 'Don’t worry about losing emails. Add an additional connection, then set it as your Backup Connection. Emails that fail to send with the Primary Connection will be sent via the selected Backup Connection. <a href="%s" target="_blank" rel="noopener noreferrer">Upgrade to WP Mail SMTP Pro!</a>', 'wp-mail-smtp' ), esc_url( $upgrade_link_url ) ), [ 'a' => [ 'href' => [], 'rel' => [], 'target' => [], ], ] ); ?> </p> </div> </div> <div class="wp-mail-smtp-setting-row wp-mail-smtp-clear"> <div class="wp-mail-smtp-setting-label"> <label> <?php esc_html_e( 'Backup Connection', 'wp-mail-smtp' ); ?> </label> </div> <div class="wp-mail-smtp-setting-field"> <div class="wp-mail-smtp-connection-selector"> <label> <input type="radio" checked/> <span><?php esc_attr_e( 'None', 'wp-mail-smtp' ); ?></span> </label> </div> <p class="desc"> <?php echo wp_kses( sprintf( /* translators: %s - Smart routing settings page url. */ __( 'Once you add an <a href="%s">additional connection</a>, you can select it here.', 'wp-mail-smtp' ), add_query_arg( [ 'tab' => 'connections', ], wp_mail_smtp()->get_admin()->get_admin_page_url() ) ), [ 'a' => [ 'href' => [], 'target' => [], 'rel' => [], ], ] ); ?> </p> </div> </div> <?php } /** * Process tab form submission ($_POST ). * * @since 1.0.0 * * @param array $data Post data specific for the plugin. */ public function process_post( $data ) { $connection = wp_mail_smtp()->get_connections_manager()->get_primary_connection(); $connection_settings = new ConnectionSettings( $connection ); $old_data = $connection->get_options()->get_all(); $data = $connection_settings->process( $data, $old_data ); /** * Filters mail settings before save. * * @since 2.2.1 * * @param array $data Settings data. */ $data = apply_filters( 'wp_mail_smtp_settings_tab_process_post', $data ); // All the sanitization is done in Options class. Options::init()->set( $data, false, false ); $connection_settings->post_process( $data, $old_data ); if ( $connection_settings->get_scroll_to() !== false ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotValidated wp_safe_redirect( sanitize_text_field( wp_unslash( $_POST['_wp_http_referer'] ) ) . $connection_settings->get_scroll_to() ); exit; } WP::add_admin_notice( esc_html__( 'Settings were successfully saved.', 'wp-mail-smtp' ), WP::ADMIN_NOTICE_SUCCESS ); } } Pages/TestTab.php 0000644 00000060160 15252526345 0007676 0 ustar 00 <?php namespace WPMailSMTP\Admin\Pages; use WPMailSMTP\ConnectionInterface; use WPMailSMTP\Options; use WPMailSMTP\TestEmail\TestEmail; use WPMailSMTP\WP; use WPMailSMTP\Admin\PageAbstract; use WPMailSMTP\Helpers\UI; /** * Class TestTab is part of Area, displays email testing page of the plugin. * * @since 1.0.0 */ class TestTab extends PageAbstract { /** * @var string Slug of a tab. */ protected $slug = 'test'; /** * Tab priority. * * @since 2.8.0 * * @var int */ protected $priority = 10; /** * Domain Checker API object. * * @since 2.6.0 * * @var DomainChecker|null */ private $domain_checker; /** * Option key where the test email form values are persisted between visits, * so the next page load prefills with what the user last entered. * * @since 4.9.0 * * @const string */ const TEST_EMAIL_OPTION_KEY = 'wp_mail_smtp_test_email'; /** * Test email sending failed. * * @since 3.0.0 * * @const int */ const FAILED = 0; /** * Test email sent successfully. * * @since 3.0.0 * * @const int */ const SUCCESS = 1; /** * Test email domain check failed. * * @since 3.0.0 * * @const int */ const FAILED_DOMAIN_CHECK = 2; /** * Test email result. * * @since 3.0.0 * * @var int */ private $result = null; /** * Test email connection. * * @since 3.7.0 * * @var ConnectionInterface */ private $connection; /** * @inheritdoc */ public function get_label() { return esc_html__( 'Email Test', 'wp-mail-smtp' ); } /** * @inheritdoc */ public function get_title() { return $this->get_label(); } /** * Display the content of the tab. * * @since 1.0.0 */ public function display() { if ( $this->result === self::FAILED_DOMAIN_CHECK ) { $this->display_domain_check_details(); return; } if ( $this->result === self::SUCCESS ) { $this->display_success_banner(); } $this->display_form(); } /** * Process the email-test form submission. * * @since 1.0.0 * * @param array $data Submitted POST data. * * @inheritdoc */ public function process_post( $data ) { $connection = wp_mail_smtp()->get_connections_manager()->get_primary_connection(); /** * Filters test email connection object. * * @since 3.7.0 * * @param ConnectionInterface $connection The Connection object. * @param array $data Post data. */ $this->connection = apply_filters( 'wp_mail_smtp_admin_pages_test_tab_process_post_connection', $connection, $data ); if ( ! empty( $data['test']['email'] ) ) { $data['test']['email'] = wp_unslash( $data['test']['email'] ); $data['test']['email'] = filter_var( $data['test']['email'], FILTER_VALIDATE_EMAIL ); } if ( empty( $data['test']['email'] ) ) { WP::add_admin_notice( esc_html__( 'Test failed. Please use a valid email address and try to resend the test email.', 'wp-mail-smtp' ), WP::ADMIN_NOTICE_WARNING ); return; } $is_html = ! empty( $data['test']['html'] ); $test_email = ( new TestEmail() ) ->with_connection( $this->connection ) ->with_context( TestEmail::CONTEXT_ADMIN_TEST ) ->as_html( $is_html ) ->with_domain_check( true ); $test_email->send( $data['test']['email'] ); $this->result = $test_email->get_result(); $this->domain_checker = $test_email->get_domain_checker(); // Persist the form values so the next visit prefills with what the // user last entered (matches ESMTP's TestTab behavior). $test_email_options = get_option( self::TEST_EMAIL_OPTION_KEY, [] ); $test_email_options['to'] = $data['test']['email']; $test_email_options['html'] = $is_html; update_option( self::TEST_EMAIL_OPTION_KEY, $test_email_options, false ); } /** * Display test email title section. * * @since 3.0.0 */ private function display_title_section() { ?> <!-- Test Email Section Title --> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-content wp-mail-smtp-clear section-heading no-desc wp-mail-smtp-section-heading--has-divider"> <div class="wp-mail-smtp-setting-field"> <h2><?php esc_html_e( 'Send a Test Email', 'wp-mail-smtp' ); ?></h2> </div> </div> <?php } /** * Display test email form. * * @since 3.0.0 */ private function display_form() { $test_email_options = array_merge( [ 'to' => '', 'html' => true, ], get_option( self::TEST_EMAIL_OPTION_KEY, [] ) ); if ( empty( $test_email_options['to'] ) ) { $test_email_options['to'] = wp_get_current_user()->user_email; } ?> <form id="email-test-form" method="POST" action="<?php echo esc_url( $this->get_link() ); ?>"> <?php $this->wp_nonce_field(); ?> <?php $this->display_title_section(); ?> <!-- Test Email --> <div id="wp-mail-smtp-setting-row-test_email" class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-email wp-mail-smtp-clear"> <div class="wp-mail-smtp-setting-label"> <label for="wp-mail-smtp-setting-test_email"><?php esc_html_e( 'Send To', 'wp-mail-smtp' ); ?></label> </div> <div class="wp-mail-smtp-setting-field"> <input name="wp-mail-smtp[test][email]" value="<?php echo esc_attr( $test_email_options['to'] ); ?>" type="email" id="wp-mail-smtp-setting-test_email" spellcheck="false" required> <p class="desc"> <?php esc_html_e( 'Enter email address where test email will be sent.', 'wp-mail-smtp' ); ?> </p> </div> </div> <?php /** * Fires after "Send To" section on the test email page. * * @since 3.7.0 */ do_action( 'wp_mail_smtp_admin_pages_test_tab_display_form_send_to_after' ); ?> <!-- HTML/Plain --> <div id="wp-mail-smtp-setting-row-test_email_html" class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-checkbox-toggle wp-mail-smtp-clear"> <div class="wp-mail-smtp-setting-label"> <label for="wp-mail-smtp-setting-test_email_html"><?php esc_html_e( 'HTML', 'wp-mail-smtp' ); ?></label> </div> <div class="wp-mail-smtp-setting-field"> <?php UI::toggle( [ 'name' => 'wp-mail-smtp[test][html]', 'id' => 'wp-mail-smtp-setting-test_email_html', 'checked' => (bool) $test_email_options['html'], ] ); ?> <p class="desc"> <?php esc_html_e( 'Send this email in HTML or in plain text format.', 'wp-mail-smtp' ); ?> </p> </div> </div> <p class="wp-mail-smtp-submit"> <?php $btn = 'wp-mail-smtp-btn-orange'; $disabled = ''; $help_text = ''; $mailer = wp_mail_smtp()->get_providers()->get_mailer( Options::init()->get( 'mail', 'mailer' ), wp_mail_smtp()->get_processor()->get_phpmailer() ); if ( ! $mailer || ! $mailer->is_mailer_complete() ) { $btn = 'wp-mail-smtp-btn-red'; $disabled = 'disabled'; $help_text = '<span class="help-text"><strong>' . esc_html__( 'You cannot send an email. Mailer is not properly configured. Please check your settings.', 'wp-mail-smtp' ) . '</strong></span>'; } ?> <button type="submit" class="wp-mail-smtp-btn wp-mail-smtp-btn-md <?php echo esc_attr( $btn ); ?>" <?php echo esc_attr( $disabled ); ?>> <span><?php esc_html_e( 'Send Email', 'wp-mail-smtp' ); ?></span> <?php echo wp_mail_smtp()->prepare_loader( 'white', 'sm' ); // phpcs:ignore ?> </button> <?php echo $help_text; ?> </p> <?php $this->post_form_hidden_field(); ?> </form> <?php if ( ! empty( $mailer ) && $mailer->is_mailer_complete() && isset( $_GET['auto-start'] ) ) : // phpcs:ignore ?> <script> (function( $ ) { var $button = $( '.wp-mail-smtp-tab-tools-test #email-test-form .wp-mail-smtp-btn' ); $button.attr( 'disabled', true ); $button.find( 'span' ).hide(); $button.find( '.wp-mail-smtp-loading' ).show(); $( '#email-test-form' ).submit(); }( jQuery )); </script> <?php endif; } /** * Display the domain check details. * * @since 2.6.0 */ protected function display_domain_check_details() { if ( empty( $this->domain_checker ) || $this->domain_checker->no_issues() ) { return; } $this->display_title_section(); ?> <?php if ( $this->domain_checker->is_supported_mailer() ) : ?> <div class="notice-warning notice-inline"> <p><?php esc_html_e( 'The test email might have sent, but its deliverability should be improved.', 'wp-mail-smtp' ); ?></p> </div> <?php endif; ?> <?php echo $this->domain_checker->get_results_html(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> <div class="wp-mail-smtp-test-email-resend"> <a href="<?php echo esc_url( $this->get_link() ); ?>"> <?php esc_html_e( 'Send Another Test Email', 'wp-mail-smtp' ); ?> </a> </div> <?php } /** * Render the success banner shown after a test email succeeds. This is the * Lite implementation: success headline, four Pro feature bullets, an * "Upgrade to Pro" CTA with a $50 OFF badge, the hero illustration, and * the Pro Tip strip. The Pro subclass overrides this method to render its * own variants. * * @since 4.9.0 */ protected function display_success_banner() { $assets_url = wp_mail_smtp()->assets_url; $illustration = $assets_url . '/images/test-success/illustration-lite.svg'; $upgrade_url = wp_mail_smtp()->get_upgrade_link( [ 'medium' => 'lite-test-email-success', 'content' => 'Upgrade Button', ] ); $bullets = [ esc_html__( 'Log, track, and resend any email your site sends', 'wp-mail-smtp' ), esc_html__( 'Get instant failure alerts via Email, Slack, SMS, Discord, etc', 'wp-mail-smtp' ), esc_html__( 'Never miss an email with an automatic backup mailer', 'wp-mail-smtp' ), ]; ?> <div class="wpms-test-email-success-banner wp-mail-smtp-test-success-banner wp-mail-smtp-test-success-banner--lite"> <?php $this->display_success_banner_dismiss(); ?> <div class="wpms:flex wpms:items-center wpms:gap-md wpms:max-tablet:flex-col wpms:max-tablet:items-stretch"> <div class="wpms:flex wpms:flex-col wpms:flex-1 wpms:min-w-[0] wpms:gap-md wpms:p-md"> <div class="wpms:flex wpms:flex-col wpms:gap-sm"> <div class="wpms-test-email-success-banner__heading"> <span aria-hidden="true" class="wpms:icon-[fa6-solid--circle-check] wpms:text-success wpms:w-[16px] wpms:h-[16px] wpms:shrink-0"></span> <h2> <?php esc_html_e( 'Test email sent successfully! Check your inbox to confirm delivery.', 'wp-mail-smtp' ); ?> </h2> </div> <p class="wpms:m-[0]! wpms:text-sm! wpms:leading-5! wpms:text-tertiary"> <?php echo wp_kses( sprintf( /* translators: %s - "Pro" wrapped in a bold tag highlighting the WP Mail SMTP Pro tier. */ __( 'Level Up Your Email Game! Unlock these %s features and get even more from WP Mail SMTP.', 'wp-mail-smtp' ), '<strong class="wpms:font-medium! wpms:text-primary">' . esc_html__( 'Pro', 'wp-mail-smtp' ) . '</strong>' ), [ 'strong' => [ 'class' => [] ] ] ); ?> </p> </div> <ul class="wpms:m-[0]! wpms:p-[0]! wpms:list-none wpms:flex wpms:flex-col wpms:gap-sm"> <?php foreach ( $bullets as $bullet ) : ?> <li class="wpms:m-[0]! wpms:p-[0]! wpms:flex wpms:items-center wpms:gap-sm"> <span aria-hidden="true" class="wpms:icon-[fa6-solid--check] wpms:text-success wpms:w-[16px] wpms:h-[16px] wpms:shrink-0"></span> <span class="wpms:text-sm! wpms:leading-5! wpms:font-medium! wpms:text-primary"><?php echo esc_html( $bullet ); ?></span> </li> <?php endforeach; ?> <li class="wpms:m-[0]! wpms:p-[0]! wpms:flex wpms:items-center wpms:gap-sm"> <span aria-hidden="true" class="wpms:icon-[fa6-solid--check] wpms:text-success wpms:w-[16px] wpms:h-[16px] wpms:shrink-0"></span> <span class="wpms:text-sm! wpms:leading-5! wpms:font-medium! wpms:text-primary"> <?php esc_html_e( 'Priority support from our email deliverability experts', 'wp-mail-smtp' ); ?> </span> <span class="wpms:text-sm! wpms:leading-5! wpms:text-tertiary"> <?php esc_html_e( '...and much more!', 'wp-mail-smtp' ); ?> </span> </li> </ul> <div class="wpms:flex wpms:flex-col wpms:gap-[8px] wpms:items-start"> <a href="<?php echo esc_url( $upgrade_url ); ?>" target="_blank" rel="noopener noreferrer" class="wp-mail-smtp-btn wp-mail-smtp-btn-cta-large wp-mail-smtp-btn-secondary"> <?php esc_html_e( 'Upgrade to WP Mail SMTP Pro', 'wp-mail-smtp' ); ?> </a> <p class="wpms:m-[0]! wpms:flex wpms:items-center wpms:gap-[7px]"> <span aria-hidden="true" class="wpms:icon-[custom--badge-percent] wpms:text-success wpms:w-[16px] wpms:h-[16px] wpms:shrink-0"></span> <span class="wpms:text-sm! wpms:leading-5! wpms:text-tertiary"> <strong class="wpms:font-medium! wpms:text-success"><?php esc_html_e( '$50 OFF', 'wp-mail-smtp' ); ?></strong> <?php esc_html_e( 'for WP Mail SMTP users, applied at checkout.', 'wp-mail-smtp' ); ?> </span> </p> </div> </div> <div class="wpms:w-[400px] wpms:self-stretch wpms:shrink-0 wpms:max-tablet:w-full wpms:max-tablet:h-[316px]"> <img src="<?php echo esc_url( $illustration ); ?>" alt="<?php esc_attr_e( 'A person celebrating after sending an email.', 'wp-mail-smtp' ); ?>" class="wpms:block wpms:w-full wpms:h-full wpms:object-cover"> </div> </div> <?php $this->display_success_pro_tip_strip(); ?> </div> <?php } /** * Render the dismiss button shared by every success-banner variant. * Uses the WP-native `.notice-dismiss` class so the visual treatment * matches WP admin notices; JS handles the per-session hide. * * @since 4.9.0 */ protected function display_success_banner_dismiss() { ?> <button type="button" class="notice-dismiss wpms-test-email-success-banner__dismiss" aria-label="<?php esc_attr_e( 'Dismiss', 'wp-mail-smtp' ); ?>"> <span class="screen-reader-text"><?php esc_html_e( 'Dismiss', 'wp-mail-smtp' ); ?></span> </button> <?php } /** * Render the Pro Tip strip shown below the Lite and Pro-Setup-Pending * banners. Picks the first not-installed cross-sell candidate as the * featured plugin; if no candidates remain, renders nothing. * * @since 4.9.0 */ protected function display_success_pro_tip_strip() { // Capability gating lives in get_cross_sell_recommendations() — it returns // an empty pool for users without install_plugins, which short-circuits below. $recommendations = $this->get_cross_sell_recommendations( 1 ); if ( empty( $recommendations ) ) { return; } $product = $recommendations[0]; $install_url = $this->get_install_plugin_url( $product['install'] ); ?> <div class="wpms-test-email-pro-tip-strip wpms:flex wpms:items-center wpms:gap-sm wpms:px-md wpms:py-sm wpms:bg-surface-background-white wpms:border-t wpms:border-surface-divider"> <span aria-hidden="true" class="wpms:icon-[fa6-solid--lightbulb] wpms:text-utility-yellow-50 wpms:w-[14px] wpms:h-[14px] wpms:shrink-0"></span> <span class="wpms-test-email-pro-tip-strip__initial wpms:text-sm wpms:leading-5 wpms:text-tertiary"> <strong class="wpms:font-medium! wpms:text-primary"><?php esc_html_e( 'Pro Tip:', 'wp-mail-smtp' ); ?></strong> <?php echo wp_kses( sprintf( /* translators: %s - product name wrapped in <strong>, e.g. WPConsent. */ __( '%1$s with our sister plugin %2$s', 'wp-mail-smtp' ), esc_html( $product['pro_tip'] ), '<strong class="wpms:font-medium! wpms:text-primary">' . esc_html( $product['name'] ) . '</strong>' ), [ 'strong' => [ 'class' => [] ] ] ); ?> - <a href="<?php echo esc_url( $install_url ); ?>" class="js-wp-mail-smtp-plugin-install-link status-download wpms:font-medium! wpms:text-link wpms:underline wpms:focus:outline-none! wpms:focus:shadow-none!" data-plugin="<?php echo esc_attr( $product['install_url'] ); ?>" data-plugin-name="<?php echo esc_attr( $product['name'] ); ?>" > <?php /* translators: %s - product name (e.g. WPConsent). */ printf( esc_html__( 'Install %s (Free)', 'wp-mail-smtp' ), esc_html( $product['name'] ) ); ?> </a> </span> <span class="wpms-test-email-pro-tip-strip__success wpms:text-sm wpms:leading-5 wpms:text-tertiary" role="status" hidden> <?php echo wp_kses( sprintf( /* translators: %1$s - plugin name (bold). %2$s - settings-page hyperlink. */ __( '%1$s was installed and activated, please visit their %2$s to configure it.', 'wp-mail-smtp' ), '<strong class="wpms:font-medium! wpms:text-primary">' . esc_html( $product['name'] ) . '</strong>', '<a href="' . esc_url( $product['settings_page_url'] ) . '" class="wpms:font-medium! wpms:text-link wpms:underline">' . esc_html__( 'settings page', 'wp-mail-smtp' ) . '</a>' ), [ 'strong' => [ 'class' => [] ], 'a' => [ 'href' => [], 'class' => [], ], ] ); ?> </span> </div> <?php } /** * Build the cross-sell pool filtered to plugins not already installed * on this site. The catalog is inlined here — the success banner uses * its own small, hand-curated set, separate from the About-tab list at * {@see AboutTab::get_all_plugins()}. * * @since 4.9.0 * * @param int $limit Max number of products to return. * * @return array<int, array<string, string>> */ protected function get_cross_sell_recommendations( $limit = 3 ) { // phpcs:ignore Generic.Metrics.NestingLevel.MaxExceeded -- Inner foreach over per-product competitor list is intentional; flattening would obscure the catalog structure. if ( ! current_user_can( 'install_plugins' ) ) { return []; } $assets_url = wp_mail_smtp()->assets_url; $catalog = [ [ 'name' => 'ActiveLayer', 'title' => esc_html__( 'Smarter Spam Protection for WordPress', 'wp-mail-smtp' ), 'desc' => esc_html__( 'Catch spam in milliseconds with AI, invisible to your real visitors.', 'wp-mail-smtp' ), 'pro_tip' => esc_html__( 'Stop spam at the door', 'wp-mail-smtp' ), 'icon' => $assets_url . '/images/about/icon-activelayer.svg', 'plugin' => 'activelayer-anti-spam-spam-protection-for-forms-comments/activelayer-anti-spam-spam-protection-for-forms-comments.php', 'install' => 'activelayer-anti-spam-spam-protection-for-forms-comments', 'install_url' => 'https://downloads.wordpress.org/plugin/activelayer-anti-spam-spam-protection-for-forms-comments.zip', 'settings_page_url' => admin_url( 'admin.php?page=activelayer-settings' ), 'framed_icon' => true, 'competitors' => [ 'akismet/akismet.php', 'antispam-bee/antispam_bee.php', 'honeypot/wp-armour.php', 'wp-armour-extended/wp-armour-extended.php', 'cleantalk-spam-protect/cleantalk.php', 'wp-cerber/wp-cerber.php', 'anti-spam/anti-spam.php', ], ], [ 'name' => 'WPConsent', 'title' => esc_html__( 'Stay GDPR & Privacy Compliant', 'wp-mail-smtp' ), 'desc' => esc_html__( 'Add a cookie consent banner to your site and meet privacy laws in minutes.', 'wp-mail-smtp' ), 'pro_tip' => esc_html__( 'Stay GDPR & Privacy compliant', 'wp-mail-smtp' ), 'icon' => $assets_url . '/images/about/icon-wpconsent.svg', 'plugin' => 'wpconsent-cookies-banner-privacy-suite/wpconsent.php', 'install' => 'wpconsent-cookies-banner-privacy-suite', 'install_url' => 'https://downloads.wordpress.org/plugin/wpconsent-cookies-banner-privacy-suite.zip', 'settings_page_url' => admin_url( 'admin.php?page=wpconsent-cookies' ), 'framed_icon' => true, 'competitors' => [ 'cookie-law-info/cookie-law-info.php', 'complianz-gdpr/complianz-gpdr.php', 'complianz-gdpr-premium/complianz-gpdr-premium.php', 'cookie-notice/cookie-notice.php', 'gdpr-cookie-compliance/moove-gdpr.php', 'iubenda-cookie-law-solution/iubenda_cookie_solution.php', 'real-cookie-banner/index.php', 'cookiebot/cookiebot.php', 'uk-cookie-consent/uk-cookie-consent.php', 'borlabs-cookie/borlabs-cookie.php', ], ], [ 'name' => 'Duplicator', 'title' => esc_html__( 'Add Secure WordPress Backups', 'wp-mail-smtp' ), 'desc' => esc_html__( 'Automated, encrypted backups with 1-click restore to keep your site safe.', 'wp-mail-smtp' ), 'pro_tip' => esc_html__( 'Protect your site with automated backups', 'wp-mail-smtp' ), 'icon' => $assets_url . '/images/about/icon-duplicator.svg', 'plugin' => 'duplicator/duplicator.php', 'plugin_pro' => 'duplicator-pro/duplicator-pro.php', 'install' => 'duplicator', 'install_url' => 'https://downloads.wordpress.org/plugin/duplicator.zip', 'settings_page_url' => admin_url( 'admin.php?page=duplicator-settings' ), 'framed_icon' => false, 'competitors' => [ 'all-in-one-wp-migration/all-in-one-wp-migration.php', 'all-in-one-wp-migration-unlimited-extension/all-in-one-wp-migration-unlimited-extension.php', 'updraftplus/updraftplus.php', 'wpvivid-backuprestore/wpvivid-backuprestore.php', 'wpvivid-backup-pro/wpvivid-backup-pro.php', 'backwpup/backwpup.php', 'backwpup-pro/backwpup.php', 'migrate-guru/migrateguru.php', 'wp-migrate-db/wp-migrate-db.php', 'wp-migrate-db-pro/wp-migrate-db-pro.php', 'wp-staging/wp-staging.php', 'wp-staging-pro/wp-staging-pro.php', 'backupbuddy/backupbuddy.php', ], ], ]; if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $installed = get_plugins(); $candidates = array_filter( $catalog, function ( $product ) use ( $installed ) { if ( array_key_exists( $product['plugin'], $installed ) ) { return false; } // Pro variant present too — treat as "already provided" so we // don't push the Lite when the Pro version is installed. if ( ! empty( $product['plugin_pro'] ) && array_key_exists( $product['plugin_pro'], $installed ) ) { return false; } // Drop the entry when a competitor that covers the same need // is installed (e.g., recommending Duplicator to an // UpdraftPlus user is wasted screen space). if ( ! empty( $product['competitors'] ) ) { foreach ( $product['competitors'] as $competitor ) { if ( array_key_exists( $competitor, $installed ) ) { return false; } } } return true; } ); return array_slice( array_values( $candidates ), 0, $limit ); } /** * Build a nonce-protected install-plugin URL for the WP-Admin updater. * Users without the install_plugins capability get the public wp.org * page so the link still makes sense. * * @since 4.9.0 * * @param string $slug WP.org plugin slug. * * @return string */ private function get_install_plugin_url( $slug ) { if ( ! current_user_can( 'install_plugins' ) ) { return 'https://wordpress.org/plugins/' . $slug . '/'; } return wp_nonce_url( self_admin_url( 'update.php?action=install-plugin&plugin=' . $slug ), 'install-plugin_' . $slug ); } /** * Get the plain text prepared message for test email. * * Use {@see TestEmail::get_email_message_text()} instead. * * @since 1.4.0 * @since 1.5.0 Display an upsell to WP Mail SMTP Pro if free version installed. * @since 2.6.0 Change visibility, so it can be used elsewhere. * @deprecated {VERSION} * * @return string */ public static function get_email_message_text() { return TestEmail::get_email_message_text(); } /** * Returns debug information for detection, processing, and display. * * @since 1.3.0 * @deprecated {VERSION} * * @return array */ protected function get_debug_details() { _deprecated_function( __METHOD__, '4.9.0', '\WPMailSMTP\Admin\EmailSendingErrors\EmailSendingErrors::get_local_failure_info' ); return []; } /** * Displays all the various error and debug details. * * @since 1.3.0 * @deprecated {VERSION} */ protected function display_debug_details() { _deprecated_function( __METHOD__, '4.9.0', '\WPMailSMTP\Admin\EmailSendingErrors\EmailSendingErrors::print_banner_body' ); } } Pages/ActionSchedulerTab.php 0000644 00000006200 15252526345 0012026 0 ustar 00 <?php namespace WPMailSMTP\Admin\Pages; use WPMailSMTP\Admin\PageAbstract; /** * Class ActionScheduler. * * @since 2.9.0 */ class ActionSchedulerTab extends PageAbstract { /** * Part of the slug of a tab. * * @since 2.9.0 * * @var string */ protected $slug = 'action-scheduler'; /** * Tab priority. * * @since 2.9.0 * * @var int */ protected $priority = 30; /** * Link label of a tab. * * @since 2.9.0 * * @return string */ public function get_label() { return esc_html__( 'Scheduled Actions', 'wp-mail-smtp' ); } /** * Title of a tab. * * @since 2.9.0 * * @return string */ public function get_title() { return $this->get_label(); } /** * URL to a tab. * * @since 2.9.0 * * @return string */ public function get_link() { return add_query_arg( [ 's' => 'wp_mail_smtp' ], parent::get_link() ); } /** * Register hooks. * * @since 2.9.0 */ public function hooks() { add_action( 'current_screen', [ $this, 'init' ], 20 ); } /** * Init. * * @since 2.9.0 */ public function init() { if ( $this->is_applicable() ) { \ActionScheduler_AdminView::instance()->process_admin_ui(); } } /** * Display scheduled actions table. * * @since 2.9.0 */ public function display() { if ( ! $this->is_applicable() ) { return; } ?> <h1><?php echo esc_html__( 'Scheduled Actions', 'wp-mail-smtp' ); ?></h1> <p> <?php echo sprintf( wp_kses( /* translators: %s - Action Scheduler website URL. */ __( 'WP Mail SMTP is using the <a href="%s" target="_blank" rel="noopener noreferrer">Action Scheduler</a> library, which allows it to queue and process bigger tasks in the background without making your site slower for your visitors. Below you can see the list of all tasks and their status. This table can be very useful when debugging certain issues.', 'wp-mail-smtp' ), [ 'a' => [ 'href' => [], 'rel' => [], 'target' => [], ], ] ), 'https://actionscheduler.org/' ); ?> </p> <p> <?php echo esc_html__( 'Action Scheduler library is also used by other plugins, like WPForms and WooCommerce, so you might see tasks that are not related to our plugin in the table below.', 'wp-mail-smtp' ); ?> </p> <?php if ( isset( $_GET['s'] ) ) : // phpcs:ignore WordPress.Security.NonceVerification.Recommended ?> <div id="wp-mail-smtp-reset-filter"> <?php echo wp_kses( sprintf( /* translators: %s - search term. */ __( 'Search results for <strong>%s</strong>', 'wp-mail-smtp' ), sanitize_text_field( wp_unslash( $_GET['s'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended ), [ 'strong' => [] ] ); ?> <a href="<?php echo esc_url( remove_query_arg( 's' ) ); ?>"> <i class="reset dashicons dashicons-dismiss"></i> </a> </div> <?php endif; ?> <?php \ActionScheduler_AdminView::instance()->render_admin_ui(); } /** * Check if ActionScheduler_AdminView class exists. * * @since 2.9.0 * * @return bool */ private function is_applicable() { return class_exists( 'ActionScheduler_AdminView' ); } } Pages/MiscTab.php 0000644 00000044731 15252526345 0007660 0 ustar 00 <?php namespace WPMailSMTP\Admin\Pages; use WPMailSMTP\Admin\Area; use WPMailSMTP\Admin\PageAbstract; use WPMailSMTP\Helpers\UI; use WPMailSMTP\OptimizedEmailSending; use WPMailSMTP\Options; use WPMailSMTP\UsageTracking\UsageTracking; use WPMailSMTP\Reports\Emails\Summary as SummaryReportEmail; use WPMailSMTP\Tasks\Reports\SummaryEmailTask as SummaryReportEmailTask; use WPMailSMTP\WP; /** * Class MiscTab is part of Area, displays different plugin-related settings of the plugin (not related to emails). * * @since 1.0.0 */ class MiscTab extends PageAbstract { /** * Slug of a tab. * * @since 1.0.0 * * @var string */ protected $slug = 'misc'; /** * Link label of a tab. * * @since 1.0.0 * * @return string */ public function get_label() { return esc_html__( 'Misc', 'wp-mail-smtp' ); } /** * Title of a tab. * * @since 1.0.0 * * @return string */ public function get_title() { return esc_html__( 'Miscellaneous', 'wp-mail-smtp' ); } /** * Output HTML of the misc settings. * * @since 1.0.0 */ public function display() { $options = Options::init(); ?> <form method="POST" action=""> <?php $this->wp_nonce_field(); ?> <!-- Section Title --> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-content wp-mail-smtp-clear section-heading wp-mail-smtp-section-heading--has-divider no-desc"> <div class="wp-mail-smtp-setting-field"> <h2><?php echo $this->get_title(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></h2> </div> </div> <!-- Do not send --> <div id="wp-mail-smtp-setting-row-do_not_send" class="wp-mail-smtp-setting-row wp-mail-smtp-clear"> <div class="wp-mail-smtp-setting-label wp-mail-smtp-setting-label-with-tooltip"> <label for="wp-mail-smtp-setting-do_not_send"> <?php esc_html_e( 'Do Not Send', 'wp-mail-smtp' ); ?> </label> <span class="wp-mail-smtp-tooltip wp-mail-smtp-tooltip-with-icon"> <img src="<?php echo esc_url( wp_mail_smtp()->assets_url . '/images/font-awesome/info-circle.svg' ); ?>" width="15" height="15" alt="info"/> <span class="wp-mail-smtp-tooltip-text wp-mail-smtp-tooltip-small-text"> <?php printf( wp_kses( __( 'Some plugins, like BuddyPress and Events Manager, are using their own email delivery solutions. By default, this option does not block their emails, as those plugins do not use default <code>wp_mail()</code> function to send emails.', 'wp-mail-smtp' ), [ 'code' => [], ] ) ); ?> <br> <br> <?php esc_html_e( 'You will need to consult with their documentation to switch them to use default WordPress email delivery.', 'wp-mail-smtp' ); ?> <br> <?php esc_html_e( 'Test emails are allowed to be sent, regardless of this option.', 'wp-mail-smtp' ); ?> </span> </span> </div> <div class="wp-mail-smtp-setting-field"> <?php UI::toggle( [ 'name' => 'wp-mail-smtp[general][do_not_send]', 'id' => 'wp-mail-smtp-setting-do_not_send', 'value' => 'true', 'checked' => (bool) $options->get( 'general', 'do_not_send' ), 'disabled' => $options->is_const_defined( 'general', 'do_not_send' ), ] ); ?> <p class="desc"> <?php esc_html_e( 'Stop sending all emails', 'wp-mail-smtp' ); ?> </p> <p class="desc"> <?php if ( $options->is_const_defined( 'general', 'do_not_send' ) ) { echo $options->get_const_set_message( 'WPMS_DO_NOT_SEND' ); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } else { printf( wp_kses( /* translators: %s - The URL to the constants support article. */ __( 'Please read this <a href="%s" target="_blank" rel="noopener noreferrer">support article</a> if you want to enable this option using constants.', 'wp-mail-smtp' ), [ 'a' => [ 'href' => [], 'target' => [], 'rel' => [], ], ] ), // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/how-to-secure-smtp-settings-by-using-constants/', [ 'medium' => 'misc-settings', 'content' => 'Do not send setting description - support article', ] ) ) ); } ?> </p> <?php if ( wp_mail_smtp()->is_pro() ) { $is_log_blocked_emails_const_defined = $options->is_const_defined( 'logs', 'log_blocked_emails' ); ?> <div style="margin-top: 10px; display: <?php echo (bool) $options->get( 'general', 'do_not_send' ) ? 'block' : 'none'; ?>;"> <?php UI::toggle( [ 'name' => 'wp-mail-smtp[logs][log_blocked_emails]', 'id' => 'wp-mail-smtp-setting-log_blocked_emails', 'value' => 'true', 'label' => esc_html__( 'Log Blocked Emails', 'wp-mail-smtp' ), 'checked' => (bool) $options->get( 'logs', 'log_blocked_emails' ), 'disabled' => $is_log_blocked_emails_const_defined, ] ); ?> <p class="desc"> <?php esc_html_e( 'When selected, emails blocked by the "Do Not Send" option will be logged in the Email Log.', 'wp-mail-smtp' ); ?> </p> <?php if ( $is_log_blocked_emails_const_defined ) : ?> <p class="desc"> <?php echo $options->get_const_set_message( 'WPMS_LOG_BLOCKED_EMAILS' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> </p> <?php endif; ?> </div> <?php } ?> </div> </div> <!-- Hide Announcements --> <div id="wp-mail-smtp-setting-row-am_notifications_hidden" class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-checkbox-toggle wp-mail-smtp-clear"> <div class="wp-mail-smtp-setting-label"> <label for="wp-mail-smtp-setting-am_notifications_hidden"> <?php esc_html_e( 'Hide Announcements', 'wp-mail-smtp' ); ?> </label> </div> <div class="wp-mail-smtp-setting-field"> <?php UI::toggle( [ 'name' => 'wp-mail-smtp[general][am_notifications_hidden]', 'id' => 'wp-mail-smtp-setting-am_notifications_hidden', 'value' => 'true', 'checked' => (bool) $options->get( 'general', 'am_notifications_hidden' ), ] ); ?> <p class="desc"> <?php esc_html_e( 'Hide plugin announcements and update details.', 'wp-mail-smtp' ); ?> </p> </div> </div> <!-- Hide Email Delivery Errors --> <div id="wp-mail-smtp-setting-row-email_delivery_errors_hidden" class="wp-mail-smtp-setting-row wp-mail-smtp-clear"> <div class="wp-mail-smtp-setting-label"> <label for="wp-mail-smtp-setting-email_delivery_errors_hidden"> <?php esc_html_e( 'Hide Email Delivery Errors', 'wp-mail-smtp' ); ?> </label> </div> <div class="wp-mail-smtp-setting-field"> <?php $is_hard_disabled = has_filter( 'wp_mail_smtp_admin_is_error_delivery_notice_enabled' ) && ! wp_mail_smtp()->get_admin()->is_error_delivery_notice_enabled(); ?> <?php UI::toggle( [ 'name' => 'wp-mail-smtp[general][email_delivery_errors_hidden]', 'id' => 'wp-mail-smtp-setting-email_delivery_errors_hidden', 'value' => 'true', 'checked' => $is_hard_disabled || (bool) $options->get( 'general', 'email_delivery_errors_hidden' ), 'disabled' => $is_hard_disabled, ] ); ?> <p class="desc"> <?php esc_html_e( 'Hide warnings alerting of email delivery errors.', 'wp-mail-smtp' ); ?> </p> <?php if ( $is_hard_disabled ) : ?> <p class="desc"> <?php printf( /* translators: %s - filter that was used to disabled. */ esc_html__( 'Email Delivery Errors were disabled using a %s filter.', 'wp-mail-smtp' ), '<code>wp_mail_smtp_admin_is_error_delivery_notice_enabled</code>' ); ?> </p> <?php else : ?> <p class="desc"> <?php echo wp_kses( __( '<strong>This is not recommended</strong> and should only be done for staging or development sites.', 'wp-mail-smtp' ), [ 'strong' => [], ] ); ?> </p> <?php endif; ?> </div> </div> <!-- Hide Dashboard Widget --> <div id="wp-mail-smtp-setting-row-dashboard_widget_hidden" class="wp-mail-smtp-setting-row wp-mail-smtp-clear"> <div class="wp-mail-smtp-setting-label"> <label for="wp-mail-smtp-setting-dashboard_widget_hidden"> <?php esc_html_e( 'Hide Dashboard Widget', 'wp-mail-smtp' ); ?> </label> </div> <div class="wp-mail-smtp-setting-field"> <?php UI::toggle( [ 'name' => 'wp-mail-smtp[general][dashboard_widget_hidden]', 'id' => 'wp-mail-smtp-setting-dashboard_widget_hidden', 'value' => 'true', 'checked' => (bool) $options->get( 'general', 'dashboard_widget_hidden' ), ] ); ?> <p class="desc"> <?php esc_html_e( 'Hide the WP Mail SMTP Dashboard Widget.', 'wp-mail-smtp' ); ?> </p> </div> </div> <?php if ( apply_filters( 'wp_mail_smtp_admin_pages_misc_tab_show_usage_tracking_setting', true ) ) : ?> <!-- Usage Tracking --> <div id="wp-mail-smtp-setting-row-usage-tracking" class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-checkbox-toggle wp-mail-smtp-clear"> <div class="wp-mail-smtp-setting-label"> <label for="wp-mail-smtp-setting-usage-tracking"> <?php esc_html_e( 'Allow Usage Tracking', 'wp-mail-smtp' ); ?> </label> </div> <div class="wp-mail-smtp-setting-field"> <?php UI::toggle( [ 'name' => 'wp-mail-smtp[general][' . UsageTracking::SETTINGS_SLUG . ']', 'id' => 'wp-mail-smtp-setting-usage-tracking', 'value' => 'true', 'checked' => (bool) $options->get( 'general', UsageTracking::SETTINGS_SLUG ), ] ); ?> <p class="desc"> <?php esc_html_e( 'By allowing us to track usage data we can better help you because we know with which WordPress configurations, themes and plugins we should test.', 'wp-mail-smtp' ); ?> </p> </div> </div> <?php endif; ?> <!-- Summary Report Email --> <div id="wp-mail-smtp-setting-row-summary-report-email" class="wp-mail-smtp-setting-row wp-mail-smtp-clear"> <div class="wp-mail-smtp-setting-label"> <label for="wp-mail-smtp-setting-summary-report-email"> <?php esc_html_e( 'Disable Email Summaries', 'wp-mail-smtp' ); ?> </label> </div> <div class="wp-mail-smtp-setting-field"> <?php UI::toggle( [ 'name' => 'wp-mail-smtp[general][' . SummaryReportEmail::SETTINGS_SLUG . ']', 'id' => 'wp-mail-smtp-setting-summary-report-email', 'value' => 'true', 'checked' => (bool) SummaryReportEmail::is_disabled(), 'disabled' => ( $options->is_const_defined( 'general', SummaryReportEmail::SETTINGS_SLUG ) || ( wp_mail_smtp()->is_pro() && empty( Options::init()->get( 'logs', 'enabled' ) ) ) ), ] ); ?> <p class="desc"> <?php esc_html_e( 'Disable Email Summaries weekly delivery.', 'wp-mail-smtp' ); ?> <?php if ( wp_mail_smtp()->is_pro() && empty( Options::init()->get( 'logs', 'enabled' ) ) ) { echo wp_kses( sprintf( /* translators: %s - Email Log settings url. */ __( 'Please enable <a href="%s">Email Logging</a> first, before this setting can be configured.', 'wp-mail-smtp' ), esc_url( wp_mail_smtp()->get_admin()->get_admin_page_url( Area::SLUG . '&tab=logs' ) ) ), [ 'a' => [ 'href' => [], ], ] ); } else { printf( '<a href="%1$s" target="_blank">%2$s</a>', esc_url( SummaryReportEmail::get_preview_link() ), esc_html__( 'View Email Summary Example', 'wp-mail-smtp' ) ); } if ( $options->is_const_defined( 'general', SummaryReportEmail::SETTINGS_SLUG ) ) { echo '<br>' . $options->get_const_set_message( 'WPMS_SUMMARY_REPORT_EMAIL_DISABLED' ); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } ?> </p> </div> </div> <!-- Optimize email sending --> <div id="wp-mail-smtp-setting-row-optimize-email-sending" class="wp-mail-smtp-setting-row wp-mail-smtp-clear"> <div class="wp-mail-smtp-setting-label"> <label for="wp-mail-smtp-setting-optimize-email-sending"> <?php esc_html_e( 'Optimize Email Sending', 'wp-mail-smtp' ); ?> </label> </div> <div class="wp-mail-smtp-setting-field"> <?php UI::toggle( [ 'name' => 'wp-mail-smtp[general][' . OptimizedEmailSending::SETTINGS_SLUG . ']', 'id' => 'wp-mail-smtp-setting-optimize-email-sending', 'value' => 'true', 'checked' => (bool) OptimizedEmailSending::is_enabled(), ] ); ?> <p class="desc"> <?php printf( wp_kses( /* translators: %1$s - Documentation URL. */ __( 'Send emails asynchronously, which will make pages with email requests load faster, but may delay email delivery by a minute or two. <a href="%1$s" target="_blank" rel="noopener noreferrer">Learn More</a>', 'wp-mail-smtp' ), [ 'a' => [ 'href' => [], 'rel' => [], 'target' => [], ], ] ), esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/a-complete-guide-to-miscellaneous-settings/#optimize-email-sending', [ 'medium' => 'misc-settings', 'content' => 'Optimize Email Sending - support article', ] ) ) ); ?> </p> </div> </div> <!-- Rate limit --> <?php $this->display_rate_limit_settings(); ?> <!-- Uninstall --> <div id="wp-mail-smtp-setting-row-uninstall" class="wp-mail-smtp-setting-row wp-mail-smtp-clear"> <div class="wp-mail-smtp-setting-label"> <label for="wp-mail-smtp-setting-uninstall"> <?php esc_html_e( 'Uninstall WP Mail SMTP', 'wp-mail-smtp' ); ?> </label> </div> <div class="wp-mail-smtp-setting-field"> <?php UI::toggle( [ 'name' => 'wp-mail-smtp[general][uninstall]', 'id' => 'wp-mail-smtp-setting-uninstall', 'value' => 'true', 'checked' => (bool) $options->get( 'general', 'uninstall' ), ] ); ?> <p class="desc"> <?php esc_html_e( 'Remove ALL WP Mail SMTP data upon plugin deletion.', 'wp-mail-smtp' ); ?> </p> <p class="desc wp-mail-smtp-danger"> <?php esc_html_e( 'All settings will be unrecoverable.', 'wp-mail-smtp' ); ?> </p> </div> </div> <?php $this->display_save_btn(); ?> </form> <?php } /** * Display rate limit settings. * * @since 4.0.0 */ protected function display_rate_limit_settings() { ?> <div id="wp-mail-smtp-setting-row-<?php echo esc_attr( $this->get_slug() ); ?>-rate_limit-lite" class="wp-mail-smtp-setting-row wp-mail-smtp-clear"> <div class="wp-mail-smtp-setting-label"> <label for="<?php echo 'wp-mail-smtp-setting-' . esc_attr( $this->get_slug() ) . '-rate_limit-lite'; ?>"> <?php esc_html_e( 'Email Rate Limiting', 'wp-mail-smtp' ); ?> </label> </div> <div class="wp-mail-smtp-setting-field"> <?php UI::toggle( [ 'id' => 'wp-mail-smtp-setting-' . esc_attr( $this->get_slug() ) . '-rate_limit-lite', ] ); ?> <p class="desc"> <?php printf( wp_kses( /* translators: %1$s - Documentation URL. */ __( 'Limit the number of emails this site will send in each time interval (per minute, hour, day, week and month). Emails that will cross those set limits will be queued and sent as soon as your limits allow. <a href="%1$s" target="_blank" rel="noopener noreferrer">Learn More</a>', 'wp-mail-smtp' ), [ 'a' => [ 'href' => [], 'rel' => [], 'target' => [], ], ] ), esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/a-complete-guide-to-miscellaneous-settings/#email-rate-limiting', [ 'medium' => 'misc-settings', 'content' => 'Email Rate Limiting - support article', ] ) ) ); ?> </p> </div> </div> <?php } /** * Process tab form submission ($_POST). * * @since 1.0.0 * @since 2.2.0 Fixed checkbox saving and use the correct merge to prevent breaking other 'general' checkboxes. * * @param array $data Tab data specific for the plugin ($_POST). */ public function process_post( $data ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh $options = Options::init(); // Unchecked checkboxes doesn't exist in $_POST, so we need to ensure we actually have them in data to save. if ( empty( $data['general']['do_not_send'] ) ) { $data['general']['do_not_send'] = false; } if ( empty( $data['logs']['log_blocked_emails'] ) ) { $data['logs']['log_blocked_emails'] = false; } if ( empty( $data['general']['am_notifications_hidden'] ) ) { $data['general']['am_notifications_hidden'] = false; } if ( empty( $data['general']['email_delivery_errors_hidden'] ) ) { $data['general']['email_delivery_errors_hidden'] = false; } if ( empty( $data['general']['dashboard_widget_hidden'] ) ) { $data['general']['dashboard_widget_hidden'] = false; } if ( empty( $data['general']['uninstall'] ) ) { $data['general']['uninstall'] = false; } if ( empty( $data['general'][ UsageTracking::SETTINGS_SLUG ] ) ) { $data['general'][ UsageTracking::SETTINGS_SLUG ] = false; } if ( empty( $data['general'][ SummaryReportEmail::SETTINGS_SLUG ] ) ) { $data['general'][ SummaryReportEmail::SETTINGS_SLUG ] = false; } if ( empty( $data['general'][ OptimizedEmailSending::SETTINGS_SLUG ] ) ) { $data['general'][ OptimizedEmailSending::SETTINGS_SLUG ] = false; } $is_summary_report_email_opt_changed = $options->is_option_changed( $options->parse_boolean( $data['general'][ SummaryReportEmail::SETTINGS_SLUG ] ), 'general', SummaryReportEmail::SETTINGS_SLUG ); // If this option was changed, cancel summary report email task. if ( $is_summary_report_email_opt_changed ) { ( new SummaryReportEmailTask() )->cancel(); } // All the sanitization is done there. $options->set( $data, false, false ); WP::add_admin_notice( esc_html__( 'Settings were successfully saved.', 'wp-mail-smtp' ), WP::ADMIN_NOTICE_SUCCESS ); } } Pages/GetProTab.php 0000644 00000042671 15252526345 0010166 0 ustar 00 <?php namespace WPMailSMTP\Admin\Pages; use WPMailSMTP\Admin\PageAbstract; /** * Get Pro tab — a product-education page shown to Lite users only. * * @since 4.9.0 */ class GetProTab extends PageAbstract { /** * Part of the slug of a tab. * * @since 4.9.0 * * @var string */ protected $slug = 'get-pro'; /** * Tab priority. * * @since 4.9.0 * * @var int */ protected $priority = 100; /** * Link label of a tab. * * @since 4.9.0 * * @return string */ public function get_label() { return esc_html__( 'Get Pro', 'wp-mail-smtp' ); } /** * Title of a tab. * * @since 4.9.0 * * @return string */ public function get_title() { return $this->get_label(); } /** * Tab content. * * @since 4.9.0 */ public function display() { ?> <div class="wpms-get-pro-tab wpms:max-w-[1200px] wpms:mx-auto wpms:mt-[30px] wpms:bg-white wpms:rounded-[4px] wpms:border wpms:border-[#dadadf] wpms:shadow-soft"> <?php $this->display_hero(); ?> <?php $this->display_features(); ?> <?php $this->display_comparison(); ?> </div> <?php } /** * Hero: headline, supporting copy, primary CTA and the comparison anchor link. * * @since 4.9.0 */ private function display_hero() { ?> <div class="wpms:flex wpms:flex-wrap wpms:items-center wpms:justify-between wpms:gap-[40px] wpms:px-[40px] wpms:py-[40px]"> <div class="wpms:flex wpms:flex-col wpms:gap-[20px] wpms:basis-[554px] wpms:grow-0 wpms:min-w-[300px]"> <h1> <?php esc_html_e( 'Take complete control of your WordPress emails', 'wp-mail-smtp' ); ?> </h1> <p> <?php esc_html_e( 'Get email logs, delivery tracking, backup connections, and instant failure alerts so you catch issues before your customers do.', 'wp-mail-smtp' ); ?> </p> <div class="wpms:flex wpms:flex-col wpms:gap-[20px]"> <div class="wpms:flex wpms:flex-col wpms:gap-[10px] wpms:items-start"> <?php $this->display_upgrade_button( 'Hero' ); ?> <?php $this->display_discount_badge(); ?> </div> <a href="#wp-mail-smtp-get-pro-comparison" class="wpms:text-[13px] wpms:text-link! wpms:underline"> <?php esc_html_e( 'Compare Lite vs Pro features', 'wp-mail-smtp' ); ?> </a> </div> </div> <div class="wpms:shrink-0"> <img src="<?php echo esc_url( wp_mail_smtp()->assets_url . '/images/education/get-pro-tab/hero.svg' ); ?>" alt="" width="400" height="214" class="wpms:w-[400px] wpms:max-w-full wpms:h-auto" > </div> </div> <?php } /** * Feature grid: six product highlights on a pale section, closed by a CTA. * * @since 4.9.0 */ private function display_features() { ?> <div class="wpms:bg-[#fcf9e8] wpms:rounded-[4px] wpms:px-[40px] wpms:py-[40px]"> <div class="wpms:flex wpms:flex-col wpms:gap-[30px]"> <h2 class="wpms:text-center"> <?php esc_html_e( 'Features you\'ll love with Pro', 'wp-mail-smtp' ); ?> </h2> <div class="wpms:grid wpms:grid-cols-3 wpms:gap-x-[20px] wpms:gap-y-[30px] wpms:max-tablet:grid-cols-1"> <?php foreach ( $this->get_feature_cards() as $card ) : ?> <div class="wpms:flex wpms:flex-col wpms:gap-[20px] wpms:bg-white wpms:rounded-[8px] wpms:border wpms:border-[#dcdcde] wpms:shadow-subtle wpms:overflow-hidden"> <img src="<?php echo esc_url( wp_mail_smtp()->assets_url . '/images/education/get-pro-tab/' . $card['image'] . '.svg' ); ?>" alt="" class="wpms:w-full wpms:h-auto" > <div class="wpms:flex wpms:flex-col wpms:gap-[8px] wpms:px-[36px] wpms:pb-[32px]"> <h3> <?php echo esc_html( $card['title'] ); ?> </h3> <p class="wpms:text-[14px]!"> <?php echo esc_html( $card['description'] ); ?> </p> </div> </div> <?php endforeach; ?> </div> <div class="wpms:flex wpms:flex-col wpms:gap-[8px] wpms:items-center"> <?php $this->display_upgrade_button( 'Features Grid' ); ?> <?php $this->display_discount_badge(); ?> </div> </div> </div> <?php } /** * Lite vs Pro comparison: heading, twelve-row table and the closing CTA. * * @since 4.9.0 */ private function display_comparison() { ?> <div class="wpms:px-[40px] wpms:py-[40px]"> <div class="wpms:flex wpms:flex-col wpms:gap-[30px] wpms:items-center"> <div class="wpms:flex wpms:flex-col wpms:gap-[20px] wpms:items-center wpms:text-center"> <h2> <?php esc_html_e( 'Lite vs Pro: What\'s the Difference?', 'wp-mail-smtp' ); ?> </h2> <p class="wpms:max-w-[467px]"> <?php esc_html_e( 'Get the most out of WP Mail SMTP by upgrading to Pro and unlocking all of the powerful features.', 'wp-mail-smtp' ); ?> </p> </div> <div id="wp-mail-smtp-get-pro-comparison" class="wpms:w-full wpms:overflow-x-auto"> <div class="wpms:min-w-[1010px] wpms:rounded-[8px] wpms:border wpms:border-[#dcdcde] wpms:overflow-hidden"> <?php $this->display_comparison_header(); ?> <?php foreach ( $this->get_comparison_rows() as $row ) : ?> <?php $this->display_comparison_row( $row ); ?> <?php endforeach; ?> </div> </div> <div class="wpms:flex wpms:flex-col wpms:gap-[8px] wpms:items-center"> <?php $this->display_upgrade_button( 'Bottom' ); ?> <?php $this->display_discount_badge(); ?> </div> </div> </div> <?php } /** * Comparison table header row. * * @since 4.9.0 */ private function display_comparison_header() { $cell = 'wpms:flex wpms:items-start wpms:p-[20px] wpms:bg-[#f3f4f6] wpms:border-b wpms:border-l wpms:border-[#f3f4f6]'; $label = 'wpms:m-0! wpms:text-[20px]! wpms:font-bold! wpms:leading-[28px]! wpms:tracking-[0.25px] wpms:text-gray-900!'; ?> <div class="wpms:flex wpms:items-stretch"> <div class="<?php echo esc_attr( $cell ); ?> wpms:w-[310px] wpms:shrink-0"> <p class="<?php echo esc_attr( $label ); ?>"><?php esc_html_e( 'Feature', 'wp-mail-smtp' ); ?></p> </div> <div class="<?php echo esc_attr( $cell ); ?> wpms:w-[400px] wpms:shrink-0"> <p class="<?php echo esc_attr( $label ); ?>"><?php esc_html_e( 'Lite', 'wp-mail-smtp' ); ?></p> </div> <div class="<?php echo esc_attr( $cell ); ?> wpms:border-r wpms:flex-1 wpms:min-w-[300px]"> <p class="<?php echo esc_attr( $label ); ?>"><?php esc_html_e( 'Pro', 'wp-mail-smtp' ); ?></p> </div> </div> <?php } /** * A single comparison table row (feature label + Lite cell + Pro cell). * * @since 4.9.0 * * @param array $row Row data: feature label plus `lite` and `pro` cell arrays. */ private function display_comparison_row( $row ) { $cell = 'wpms:flex wpms:items-start wpms:p-[20px] wpms:bg-white wpms:border-b wpms:border-l wpms:border-[#f3f4f6]'; ?> <div class="wpms:flex wpms:items-stretch"> <div class="<?php echo esc_attr( $cell ); ?> wpms:w-[310px] wpms:shrink-0"> <p class="wpms:m-0! wpms:text-[16px]! wpms:font-medium! wpms:leading-[24px]! wpms:tracking-[0.25px] wpms:text-gray-700"> <?php echo esc_html( $row['feature'] ); ?> </p> </div> <div class="<?php echo esc_attr( $cell ); ?> wpms:w-[400px] wpms:shrink-0"> <?php $this->display_comparison_cell( $row['lite'] ); ?> </div> <div class="<?php echo esc_attr( $cell ); ?> wpms:border-r wpms:flex-1 wpms:min-w-[300px]"> <?php $this->display_comparison_cell( $row['pro'] ); ?> </div> </div> <?php } /** * Inner content of a comparison cell: a status icon plus its copy. The * lead sentence is emphasized; any follow-up detail renders below it. * * @since 4.9.0 * * @param array $cell Cell data: `status`, `lead` and optional `rest`. */ private function display_comparison_cell( $cell ) { ?> <div class="wpms:flex wpms:gap-[8px] wpms:items-start"> <?php $this->display_status_icon( $cell['status'] ); ?> <p class="wpms:m-0! wpms:text-[16px]! wpms:leading-[24px]! wpms:tracking-[0.25px] wpms:text-gray-700"> <strong class="wpms:font-bold!"><?php echo esc_html( $cell['lead'] ); ?></strong><?php if ( ! empty( $cell['rest'] ) ) : ?><br><?php echo esc_html( $cell['rest'] ); ?><?php endif; ?> </p> </div> <?php } /** * Render a comparison status icon for the given availability state. * * @since 4.9.0 * * @param string $status One of `full`, `partial`, `none`. */ private function display_status_icon( $status ) { $icons = [ 'full' => [ 'file' => 'status-full', 'label' => esc_attr__( 'Available', 'wp-mail-smtp' ) ], 'partial' => [ 'file' => 'status-partial', 'label' => esc_attr__( 'Partially available', 'wp-mail-smtp' ) ], 'none' => [ 'file' => 'status-none', 'label' => esc_attr__( 'Not available', 'wp-mail-smtp' ) ], ]; if ( ! isset( $icons[ $status ] ) ) { return; } $icon = $icons[ $status ]; ?> <img src="<?php echo esc_url( wp_mail_smtp()->assets_url . '/images/education/get-pro-tab/' . $icon['file'] . '.svg' ); ?>" alt="<?php echo esc_attr( $icon['label'] ); ?>" width="24" height="24" class="wpms:w-[24px] wpms:h-[24px] wpms:shrink-0" > <?php } /** * Primary "Upgrade to Pro" button, carrying the Lite upgrade discount. * * @since 4.9.0 * * @param string $content UTM content tag identifying the button placement. */ private function display_upgrade_button( $content ) { $url = add_query_arg( 'discount', 'SMTPLITEUPGRADE', // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound wp_mail_smtp()->get_upgrade_link( [ 'medium' => 'get-pro-tab', 'content' => $content ] ) ); ?> <a href="<?php echo esc_url( $url ); ?>" target="_blank" rel="noopener noreferrer" class="wp-mail-smtp-btn wp-mail-smtp-btn-cta-large wp-mail-smtp-btn-orange wpms:w-[300px] wpms:rounded-[4px]" > <?php esc_html_e( 'Upgrade to Pro', 'wp-mail-smtp' ); ?> </a> <?php } /** * Discount badge accompanying every upgrade button. * * @since 4.9.0 */ private function display_discount_badge() { ?> <div class="wpms:flex wpms:items-center wpms:gap-[5px]"> <span aria-hidden="true" class="wpms:icon-[custom--badge-percent] wpms:text-success wpms:w-[16px] wpms:h-[16px] wpms:shrink-0"></span> <span class="wpms:text-[14px]! wpms:leading-[20px]!"> <span class="wpms:font-semibold wpms:text-success"><?php esc_html_e( '$50 OFF', 'wp-mail-smtp' ); ?></span> <span class="wpms:text-tertiary"><?php esc_html_e( 'for WP Mail SMTP Lite users', 'wp-mail-smtp' ); ?></span> </span> </div> <?php } /** * The six feature cards shown above the comparison table. * * @since 4.9.0 * * @return array */ private function get_feature_cards() { return [ [ 'image' => 'card-email-logs', 'title' => esc_html__( 'Email Logs', 'wp-mail-smtp' ), 'description' => esc_html__( 'Save details about every email sent from your WordPress site.', 'wp-mail-smtp' ), ], [ 'image' => 'card-email-failure-alerts', 'title' => esc_html__( 'Email Failure Alerts', 'wp-mail-smtp' ), 'description' => esc_html__( 'Receive immediate alerts for email failures to quickly address issues.', 'wp-mail-smtp' ), ], [ 'image' => 'card-backup-connections', 'title' => esc_html__( 'Backup Connections', 'wp-mail-smtp' ), 'description' => esc_html__( 'Set up a secondary email provider in case your primary provider fails.', 'wp-mail-smtp' ), ], [ 'image' => 'card-one-click-mailer-setups', 'title' => esc_html__( 'One-Click Mailer Setups', 'wp-mail-smtp' ), 'description' => esc_html__( 'An easy and secure way to configure your Gmail and Outlook mailers.', 'wp-mail-smtp' ), ], [ 'image' => 'card-smart-routing', 'title' => esc_html__( 'Smart Routing', 'wp-mail-smtp' ), 'description' => esc_html__( 'Use conditional logic to send emails through different providers.', 'wp-mail-smtp' ), ], [ 'image' => 'card-advanced-mailers', 'title' => esc_html__( 'Advanced mailers', 'wp-mail-smtp' ), 'description' => esc_html__( 'Connect with top providers like Amazon SES, Microsoft 365 / Outlook, and Zoho Mail.', 'wp-mail-smtp' ), ], ]; } /** * Twelve-row Lite vs Pro feature comparison. * * @since 4.9.0 * * @return array */ private function get_comparison_rows() { $not_available = [ 'status' => 'none', 'lead' => esc_html__( 'Not available', 'wp-mail-smtp' ), 'rest' => '', ]; return [ [ 'feature' => esc_html__( 'Improved Email Deliverability', 'wp-mail-smtp' ), 'lite' => [ 'status' => 'partial', 'lead' => esc_html__( 'Connect your WordPress site to any of the following SMTP providers:', 'wp-mail-smtp' ), 'rest' => esc_html__( 'SendLayer, SMTP.com, Brevo, Google Workspace / Gmail, Mailgun, Postmark, SendGrid, SMTP2GO, Sparkpost, and any other SMTP provider using your SMTP credentials.', 'wp-mail-smtp' ), ], 'pro' => [ 'status' => 'full', 'lead' => esc_html__( 'Connect your WordPress site to any of our Lite SMTP providers, as well as:', 'wp-mail-smtp' ), 'rest' => esc_html__( 'Amazon SES, Microsoft 365 / Outlook, and Zoho Mail.', 'wp-mail-smtp' ), ], ], [ 'feature' => esc_html__( 'Weekly Email Summaries', 'wp-mail-smtp' ), 'lite' => [ 'status' => 'partial', 'lead' => esc_html__( 'Receive a basic report of your total emails sent including a summary of the previous week\'s sent emails.', 'wp-mail-smtp' ), 'rest' => '', ], 'pro' => [ 'status' => 'full', 'lead' => esc_html__( 'Receive a detailed report of your total and previous week\'s metrics for emails sent, failed, and delivered, plus statistics for your top 5 popular emails.', 'wp-mail-smtp' ), 'rest' => '', ], ], [ 'feature' => esc_html__( 'Email Error Tracking', 'wp-mail-smtp' ), 'lite' => [ 'status' => 'full', 'lead' => esc_html__( 'View email sending errors in your WordPress dashboard.', 'wp-mail-smtp' ), 'rest' => '', ], 'pro' => [ 'status' => 'full', 'lead' => esc_html__( 'View email sending errors in your WordPress dashboard.', 'wp-mail-smtp' ), 'rest' => '', ], ], [ 'feature' => esc_html__( 'Google Workspace', 'wp-mail-smtp' ), 'lite' => [ 'status' => 'partial', 'lead' => esc_html__( 'Manually connect to your account with Google Workspace/ Gmail mailer', 'wp-mail-smtp' ), 'rest' => '', ], 'pro' => [ 'status' => 'full', 'lead' => esc_html__( 'Access your Google Workspace immediately with Gmail One Click Setup', 'wp-mail-smtp' ), 'rest' => '', ], ], [ 'feature' => esc_html__( 'Outlook', 'wp-mail-smtp' ), 'lite' => $not_available, 'pro' => [ 'status' => 'full', 'lead' => esc_html__( 'Access Outlook immediately with Outlook One Click Setup', 'wp-mail-smtp' ), 'rest' => '', ], ], [ 'feature' => esc_html__( 'Email Logging', 'wp-mail-smtp' ), 'lite' => $not_available, 'pro' => [ 'status' => 'full', 'lead' => esc_html__( 'View detailed email logs in your WordPress dashboard.', 'wp-mail-smtp' ), 'rest' => esc_html__( 'Includes delivery status, email content, attachments, source, and technical details, plus the option to resend the email.', 'wp-mail-smtp' ), ], ], [ 'feature' => esc_html__( 'Instant Email Alerts', 'wp-mail-smtp' ), 'lite' => $not_available, 'pro' => [ 'status' => 'full', 'lead' => esc_html__( 'Receive an alert via your preferred channel whenever an email fails to send.', 'wp-mail-smtp' ), 'rest' => esc_html__( 'Email, Slack, SMS via Twilio, Microsoft Teams, Discord, or custom webhooks.', 'wp-mail-smtp' ), ], ], [ 'feature' => esc_html__( 'Backup Connections', 'wp-mail-smtp' ), 'lite' => $not_available, 'pro' => [ 'status' => 'full', 'lead' => esc_html__( 'Connect to multiple SMTP providers.', 'wp-mail-smtp' ), 'rest' => esc_html__( 'Choose one primary connection, then select another connection as a backup. If an email fails to send via your primary connection, it will automatically be resent using your backup connection.', 'wp-mail-smtp' ), ], ], [ 'feature' => esc_html__( 'Manage Email Notifications', 'wp-mail-smtp' ), 'lite' => $not_available, 'pro' => [ 'status' => 'full', 'lead' => esc_html__( 'Choose which default WordPress email notifications your site sends.', 'wp-mail-smtp' ), 'rest' => '', ], ], [ 'feature' => esc_html__( 'Open & Click Tracking', 'wp-mail-smtp' ), 'lite' => $not_available, 'pro' => [ 'status' => 'full', 'lead' => esc_html__( 'Monitor email open and click-through rates.', 'wp-mail-smtp' ), 'rest' => '', ], ], [ 'feature' => esc_html__( 'Email Reports', 'wp-mail-smtp' ), 'lite' => $not_available, 'pro' => [ 'status' => 'full', 'lead' => esc_html__( 'View advanced email reports in your WordPress dashboard.', 'wp-mail-smtp' ), 'rest' => esc_html__( 'Includes total number of emails sent, emails delivered, failed emails, opened emails, links clicked.', 'wp-mail-smtp' ), ], ], [ 'feature' => esc_html__( 'Smart Routing', 'wp-mail-smtp' ), 'lite' => $not_available, 'pro' => [ 'status' => 'full', 'lead' => esc_html__( 'Send emails through more than one SMTP provider.', 'wp-mail-smtp' ), 'rest' => esc_html__( 'Connect WP Mail SMTP to multiple providers, then route emails through your preferred provider based on custom conditional logic rules.', 'wp-mail-smtp' ), ], ], ]; } } Pages/About.php 0000644 00000003027 15252526345 0007401 0 ustar 00 <?php namespace WPMailSMTP\Admin\Pages; use WPMailSMTP\Admin\ParentPageAbstract; /** * About parent page. * * @since 1.5.0 * @since 2.9.0 changed parent class from PageAbstract to ParentPageAbstract. */ class About extends ParentPageAbstract { /** * Slug of a page. * * @since 1.5.0 * * @var string Slug of a page. */ protected $slug = 'about'; /** * Page default tab slug. * * @since 2.9.0 * * @var string */ protected $default_tab = 'about'; /** * Get label for a tab. * Process only those that exists. * Defaults to "About Us". * * @since 1.5.0 * * @param string $tab Tab to get label for. * * @return string */ public function get_label( $tab = '' ) { if ( ! empty( $tab ) ) { return $this->get_tab_label( $tab ); } return esc_html__( 'About Us', 'wp-mail-smtp' ); } /** * Title of a page. * * @since 1.5.0 * * @return string */ public function get_title() { return $this->get_label(); } /** * Active the given plugin. * * @deprecated 2.9.0 * * @since 1.5.0 */ public static function ajax_plugin_activate() { _deprecated_function( __METHOD__, '2.9.0', '\WPMailSMTP\Admin\Pages\AboutTab::ajax_plugin_activate' ); AboutTab::ajax_plugin_activate(); } /** * Install & activate the given plugin. * * @deprecated 2.9.0 * * @since 1.5.0 */ public static function ajax_plugin_install() { _deprecated_function( __METHOD__, '2.9.0', '\WPMailSMTP\Admin\Pages\AboutTab::ajax_plugin_install' ); AboutTab::ajax_plugin_install(); } } Pages/AuthTab.php 0000644 00000002606 15252526345 0007661 0 ustar 00 <?php namespace WPMailSMTP\Admin\Pages; use WPMailSMTP\ConnectionInterface; use WPMailSMTP\Providers\AuthAbstract; /** * Class AuthTab. * * @since 1.0.0 */ class AuthTab { /** * @var string Slug of a tab. */ protected $slug = 'auth'; /** * Launch mailer specific Auth logic. * * @since 1.0.0 */ public function process_auth() { if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) { return; } $connection = wp_mail_smtp()->get_connections_manager()->get_primary_connection(); /** * Filters auth connection object. * * @since 3.7.0 * * @param ConnectionInterface $connection The Connection object. */ $connection = apply_filters( 'wp_mail_smtp_admin_pages_auth_tab_process_auth_connection', $connection ); $auth = wp_mail_smtp()->get_providers()->get_auth( $connection->get_mailer_slug(), $connection ); if ( $auth && $auth instanceof AuthAbstract && method_exists( $auth, 'process' ) ) { $auth->process(); } } /** * Return nothing, as we don't need this functionality. * * @since 1.0.0 */ public function get_label() { return ''; } /** * Return nothing, as we don't need this functionality. * * @since 1.0.0 */ public function get_title() { return ''; } /** * Do nothing, as we don't need this functionality. * * @since 1.0.0 */ public function display() { } } Pages/DebugEventsTab.php 0000644 00000041110 15252526345 0011164 0 ustar 00 <?php namespace WPMailSMTP\Admin\Pages; use WPMailSMTP\Admin\Area; use WPMailSMTP\Admin\DebugEvents\DebugEvents; use WPMailSMTP\Admin\DebugEvents\Migration; use WPMailSMTP\Admin\DebugEvents\Table; use WPMailSMTP\Admin\PageAbstract; use WPMailSMTP\Admin\ParentPageAbstract; use WPMailSMTP\Helpers\UI; use WPMailSMTP\Options; use WPMailSMTP\WP; /** * Debug Events settings page. * * @since 3.0.0 */ class DebugEventsTab extends PageAbstract { /** * Part of the slug of a tab. * * @since 3.0.0 * * @var string */ protected $slug = 'debug-events'; /** * Tab priority. * * @since 3.0.0 * * @var int */ protected $priority = 40; /** * Debug events list table. * * @since 3.0.0 * * @var Table */ protected $table = null; /** * Plugin options. * * @since 3.0.0 * * @var Options */ protected $options; /** * Constructor. * * @since 3.0.0 * * @param ParentPageAbstract $parent_page Tab parent page. */ public function __construct( $parent_page = null ) { $this->options = Options::init(); parent::__construct( $parent_page ); // Remove unnecessary $_GET parameters and prevent url duplications in _wp_http_referer input. $this->remove_get_parameters(); } /** * Link label of a tab. * * @since 3.0.0 * * @return string */ public function get_label() { return esc_html__( 'Debug Events', 'wp-mail-smtp' ); } /** * Title of a tab. * * @since 3.0.0 * * @return string */ public function get_title() { return $this->get_label(); } /** * Register hooks. * * @since 3.0.0 */ public function hooks() { add_action( 'wp_mail_smtp_admin_area_enqueue_assets', [ $this, 'enqueue_assets' ] ); } /** * Enqueue required JS and CSS. * * @since 3.0.0 */ public function enqueue_assets() { $min = WP::asset_min(); wp_enqueue_style( 'wp-mail-smtp-flatpickr', wp_mail_smtp()->assets_url . '/css/vendor/flatpickr.min.css', [], '4.6.9' ); wp_enqueue_script( 'wp-mail-smtp-flatpickr', wp_mail_smtp()->assets_url . '/js/vendor/flatpickr.min.js', [ 'jquery' ], '4.6.9', true ); wp_enqueue_script( 'wp-mail-smtp-tools-debug-events', wp_mail_smtp()->assets_url . "/js/smtp-tools-debug-events{$min}.js", [ 'jquery', 'wp-mail-smtp-flatpickr' ], WPMS_PLUGIN_VER, true ); wp_localize_script( 'wp-mail-smtp-tools-debug-events', 'wp_mail_smtp_tools_debug_events', [ 'lang_code' => sanitize_key( WP::get_language_code() ), 'plugin_url' => wp_mail_smtp()->plugin_url, 'loader' => wp_mail_smtp()->prepare_loader( 'blue' ), 'texts' => [ 'delete_all_notice' => esc_html__( 'Are you sure you want to permanently delete all debug events?', 'wp-mail-smtp' ), 'cancel' => esc_html__( 'Cancel', 'wp-mail-smtp' ), 'close' => esc_html__( 'Close', 'wp-mail-smtp' ), 'yes' => esc_html__( 'Yes', 'wp-mail-smtp' ), 'ok' => esc_html__( 'OK', 'wp-mail-smtp' ), 'notice_title' => esc_html__( 'Heads up!', 'wp-mail-smtp' ), 'error_occurred' => esc_html__( 'An error occurred!', 'wp-mail-smtp' ), ], ] ); } /** * Get email logs list table. * * @since 3.0.0 * * @return Table */ public function get_table() { if ( $this->table === null ) { $this->table = new Table(); } return $this->table; } /** * Display scheduled actions table. * * @since 3.0.0 */ public function display() { ?> <?php if( WP::use_global_plugin_settings() && ! current_user_can( 'manage_network_options' ) ) : ?> <!-- Debug Events Section Title --> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-content wp-mail-smtp-clear section-heading" id="wp-mail-smtp-setting-row-email-heading"> <div class="wp-mail-smtp-setting-field"> <h2><?php esc_html_e( 'Debug Events', 'wp-mail-smtp' ); ?></h2> </div> <p> <?php esc_html_e( 'On this page, you can view different plugin debugging events and email sending errors.', 'wp-mail-smtp' ); ?> </p> </div> <?php else: ?> <form method="POST" action="<?php echo esc_url( $this->get_link() ); ?>"> <?php $this->wp_nonce_field(); ?> <!-- Debug Events Section Title --> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-content wp-mail-smtp-clear section-heading wp-mail-smtp-section-heading--has-divider"> <div class="wp-mail-smtp-setting-field"> <h2><?php esc_html_e( 'Debug Events', 'wp-mail-smtp' ); ?></h2> </div> <p> <?php esc_html_e( 'On this page, you can view and configure different plugin debugging events. View email sending errors and enable debugging events, allowing you to detect email sending issues.', 'wp-mail-smtp' ); ?> </p> </div> <!-- Debug Events --> <div id="wp-mail-smtp-setting-row-debug_event_types" class="wp-mail-smtp-setting-row wp-mail-smtp-clear"> <div class="wp-mail-smtp-setting-label"> <label for="wp-mail-smtp-setting-debug_event_types"> <?php esc_html_e( 'Event Types', 'wp-mail-smtp' ); ?> </label> </div> <div class="wp-mail-smtp-setting-field"> <?php UI::toggle( [ 'name' => 'wp-mail-smtp[debug_events][email_errors]', 'id' => 'wp-mail-smtp-setting-debug_events_email_errors', 'value' => 'true', 'checked' => true, 'disabled' => true, ] ); ?> <p class="desc"> <?php esc_html_e( 'Email Sending Errors', 'wp-mail-smtp' ); ?> </p> <p class="desc"> <?php esc_html_e( 'This debug event is always enabled and will record any email sending errors in the table below.', 'wp-mail-smtp' ); ?> </p> <hr class="wp-mail-smtp-setting-mid-row-sep"> <?php UI::toggle( [ 'name' => 'wp-mail-smtp[debug_events][email_debug]', 'id' => 'wp-mail-smtp-setting-debug_events_email_debug', 'value' => 'true', 'checked' => (bool) $this->options->get( 'debug_events', 'email_debug' ), ] ); ?> <p class="desc"> <?php esc_html_e( 'Debug Email Sending', 'wp-mail-smtp' ); ?> </p> <p class="desc"> <?php esc_html_e( 'Check this if you would like to debug the email sending process. Once enabled, all debug events will be logged in the table below. This setting should only be enabled for shorter debugging periods and disabled afterwards.', 'wp-mail-smtp' ); ?> </p> </div> </div> <div id="wp-mail-smtp-setting-row-debug_events_retention_period" class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-select wp-mail-smtp-clear"> <div class="wp-mail-smtp-setting-label"> <label for="wp-mail-smtp-setting-debug_events_retention_period"> <?php esc_html_e( 'Events Retention Period', 'wp-mail-smtp' ); ?> </label> </div> <div class="wp-mail-smtp-setting-field"> <select name="wp-mail-smtp[debug_events][retention_period]" id="wp-mail-smtp-setting-debug_events_retention_period" <?php disabled( $this->options->is_const_defined( 'debug_events', 'retention_period' ) ); ?>> <option value=""><?php esc_html_e( 'Forever', 'wp-mail-smtp' ); ?></option> <?php foreach ( $this->get_debug_events_retention_period_options() as $value => $label ) : ?> <option value="<?php echo esc_attr( $value ); ?>" <?php selected( $this->options->get( 'debug_events', 'retention_period' ), $value ); ?>> <?php echo esc_html( $label ); ?> </option> <?php endforeach; ?> </select> <p class="desc"> <?php esc_html_e( 'Debug events older than the selected period will be permanently deleted from the database.', 'wp-mail-smtp' ); if ( $this->options->is_const_defined( 'debug_events', 'retention_period' ) ) { //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo '<br>' . $this->options->get_const_set_message( 'WPMS_DEBUG_EVENTS_RETENTION_PERIOD' ); } ?> </p> </div> </div> <?php $this->display_save_btn(); ?> </form> <?php endif; ?> <?php if ( ! DebugEvents::is_valid_db() ) { $this->display_debug_events_not_installed(); } else { $table = $this->get_table(); $table->prepare_items(); ?> <form action="<?php echo esc_url( $this->get_link() ); ?>" method="get"> <input type="hidden" name="page" value="<?php echo esc_attr( Area::SLUG . '-tools' ); ?>" /> <input type="hidden" name="tab" value="<?php echo esc_attr( $this->get_slug() ); ?>" /> <?php // State of status filter for submission with other filters. if ( $table->get_filtered_types() !== false ) { printf( '<input type="hidden" name="type" value="%s">', esc_attr( $table->get_filtered_types() ) ); } if ( $this->get_filters_html() ) { ?> <div id="wp-mail-smtp-reset-filter"> <?php $type = $table->get_filtered_types(); echo wp_kses( sprintf( /* translators: %1$s - number of debug events found; %2$s - filtered type. */ _n( 'Found <strong>%1$s %2$s event</strong>', 'Found <strong>%1$s %2$s events</strong>', absint( $table->get_pagination_arg( 'total_items' ) ), 'wp-mail-smtp' ), absint( $table->get_pagination_arg( 'total_items' ) ), $type !== false && isset( $table->get_types()[ $type ] ) ? $table->get_types()[ $type ] : '' ), [ 'strong' => [], ] ); ?> <?php foreach ( $this->get_filters_html() as $id => $html ) : ?> <?php echo wp_kses( $html, [ 'em' => [] ] ); ?> <i class="reset dashicons dashicons-dismiss" data-scope="<?php echo esc_attr( $id ); ?>"></i> <?php endforeach; ?> </div> <?php } $table->search_box( esc_html__( 'Search Events', 'wp-mail-smtp' ), Area::SLUG . '-debug-events-search-input' ); $table->views(); $table->display(); ?> </form> <?php } } /** * Process tab form submission ($_POST ). * * @since 3.0.0 * * @param array $data Post data specific for the plugin. */ public function process_post( $data ) { // Intentional literal: this is the defensive floor for the only globally-writing path through the parent-page dispatcher. if ( WP::use_global_plugin_settings() && ! current_user_can( 'manage_network_options' ) ) { wp_die( esc_html__( 'You don\'t have the capability to perform this action.', 'wp-mail-smtp' ) ); } // Unchecked checkboxes doesn't exist in $_POST, so we need to ensure we actually have them in data to save. if ( empty( $data['debug_events']['email_debug'] ) ) { $data['debug_events']['email_debug'] = false; } // All the sanitization is done there. $this->options->set( $data, false, false ); WP::add_admin_notice( esc_html__( 'Settings were successfully saved.', 'wp-mail-smtp' ), WP::ADMIN_NOTICE_SUCCESS ); } /** * Return an array with information (HTML and id) for each filter for this current view. * * @since 3.0.0 * * @return array */ private function get_filters_html() { $filters = [ '.search-box' => $this->get_filter_search_html(), '.wp-mail-smtp-filter-date' => $this->get_filter_date_html(), ]; return array_filter( $filters ); } /** * Return HTML with information about the search filter. * * @since 3.0.0 * * @return string */ private function get_filter_search_html() { $table = $this->get_table(); $term = $table->get_filtered_search(); if ( $term === false ) { return ''; } return sprintf( /* translators: %s The searched term. */ __( 'where event contains "%s"', 'wp-mail-smtp' ), '<em>' . esc_html( $term ) . '</em>' ); } /** * Return HTML with information about the date filter. * * @since 3.0.0 * * @return string */ private function get_filter_date_html() { $table = $this->get_table(); $dates = $table->get_filtered_dates(); if ( $dates === false ) { return ''; } $dates = array_map( function ( $date ) { return date_i18n( 'M j, Y', strtotime( $date ) ); }, $dates ); $html = ''; switch ( count( $dates ) ) { case 1: $html = sprintf( /* translators: %s - Date. */ esc_html__( 'on %s', 'wp-mail-smtp' ), '<em>' . $dates[0] . '</em>' ); break; case 2: $html = sprintf( /* translators: %1$s - Date. %2$s - Date. */ esc_html__( 'between %1$s and %2$s', 'wp-mail-smtp' ), '<em>' . $dates[0] . '</em>', '<em>' . $dates[1] . '</em>' ); break; } return $html; } /** * Display a message when debug events DB table is missing. * * @since 3.0.0 */ private function display_debug_events_not_installed() { $error_message = get_option( Migration::ERROR_OPTION_NAME ); $create_missing_tables_url = wp_nonce_url( add_query_arg( [ 'create-missing-db-tables' => 1, ], $this->get_link() ), Area::SLUG . '-create-missing-db-tables' ); $contact_support_url = wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/account/support/', [ 'medium' => 'debug-events', 'content' => 'Debug Events not installed correctly', ] ); ?> <div class="notice-inline notice-error"> <h3><?php esc_html_e( 'Debug Events are Not Installed Correctly', 'wp-mail-smtp' ); ?></h3> <p> <?php if ( ! empty( $error_message ) ) { echo wp_kses( sprintf( /* translators: %1$s - create missing tables link; %2$s - contact support link. */ __( 'WP Mail SMTP is using custom database tables for some of its features. In order to work properly, the custom tables should be created, and it seems they are missing. Please try to <a href="%1$s">create the missing DB tables by clicking on this link</a>. If this issue persists, please <a href="%2$s" target="_blank" rel="noopener noreferrer">contact our support</a> and provide the error message below:', 'wp-mail-smtp' ), esc_url( $create_missing_tables_url ), esc_url( $contact_support_url ) ), [ 'a' => [ 'href' => [], 'target' => [], 'rel' => [], ], ] ); echo '<br><br>'; echo '<code>' . esc_html( $error_message ) . '</code>'; } else { echo wp_kses( sprintf( /* translators: %1$s - create missing tables link; %2$s - contact support link. */ __( 'WP Mail SMTP is using custom database tables for some of its features. In order to work properly, the custom tables should be created, and it seems they are missing. Please try to <a href="%1$s">create the missing DB tables by clicking on this link</a>. If this issue persists, please <a href="%2$s" target="_blank" rel="noopener noreferrer">contact our support</a>.', 'wp-mail-smtp' ), esc_url( $create_missing_tables_url ), esc_url( $contact_support_url ) ), [ 'a' => [ 'href' => [], 'target' => [], 'rel' => [], ], ] ); } ?> </p> </div> <?php } /** * Remove unnecessary $_GET parameters for shorter URL. * * @since 3.0.0 */ protected function remove_get_parameters() { if ( isset( $_SERVER['REQUEST_URI'] ) ) { $_SERVER['REQUEST_URI'] = remove_query_arg( [ '_wp_http_referer', '_wpnonce', 'wp-mail-smtp-debug-events-nonce', ], $_SERVER['REQUEST_URI'] // phpcs:ignore WordPress.Security.ValidatedSanitizedInput ); } } /** * Get debug events retention period options. * * @since 3.6.0 * * @return array */ protected function get_debug_events_retention_period_options() { $options = [ 604800 => esc_html__( '1 Week', 'wp-mail-smtp' ), 2628000 => esc_html__( '1 Month', 'wp-mail-smtp' ), 7885000 => esc_html__( '3 Months', 'wp-mail-smtp' ), 15770000 => esc_html__( '6 Months', 'wp-mail-smtp' ), 31540000 => esc_html__( '1 Year', 'wp-mail-smtp' ), ]; $debug_event_retention_period = $this->options->get( 'debug_events', 'retention_period' ); // Check if defined value already in list and add it if not. if ( ! empty( $debug_event_retention_period ) && ! isset( $options[ $debug_event_retention_period ] ) ) { $debug_event_retention_period_days = floor( $debug_event_retention_period / DAY_IN_SECONDS ); $options[ $debug_event_retention_period ] = sprintf( /* translators: %d - days count. */ _n( '%d Day', '%d Days', $debug_event_retention_period_days, 'wp-mail-smtp' ), $debug_event_retention_period_days ); ksort( $options ); } /** * Filter debug events retention period options. * * @since 3.6.0 * * @param array $options Debug Events retention period options. * Option key in seconds. */ return apply_filters( 'wp_mail_smtp_admin_pages_debug_events_tab_get_debug_events_retention_period_options', $options ); } } Pages/AlertsTab.php 0000644 00000041526 15252526345 0010216 0 ustar 00 <?php namespace WPMailSMTP\Admin\Pages; use WPMailSMTP\Admin\PageAbstract; use WPMailSMTP\Helpers\UI; /** * Class AlertsTab is a placeholder for Pro alerts feature. * Displays product education. * * @since 3.5.0 */ class AlertsTab extends PageAbstract { /** * Part of the slug of a tab. * * @since 3.5.0 * * @var string */ protected $slug = 'alerts'; /** * Tab priority. * * @since 3.5.0 * * @var int */ protected $priority = 20; /** * Link label of a tab. * * @since 3.5.0 * * @return string */ public function get_label() { return esc_html__( 'Alerts', 'wp-mail-smtp' ); } /** * Title of a tab. * * @since 3.5.0 * * @return string */ public function get_title() { return $this->get_label(); } /** * Output HTML of the alerts settings preview. * * @since 3.5.0 */ public function display() { $top_upgrade_button_url = wp_mail_smtp()->get_upgrade_link( [ 'medium' => 'Alerts Settings', 'content' => 'Upgrade to WP Mail SMTP Pro Button Top', ] ); $bottom_upgrade_button_url = wp_mail_smtp()->get_upgrade_link( [ 'medium' => 'Alerts Settings', 'content' => 'Upgrade to WP Mail SMTP Pro Button', ] ); ?> <div class="wp-mail-smtp-product-education"> <div class="wp-mail-smtp-product-education__row"> <h4 class="wp-mail-smtp-product-education__heading"> <?php esc_html_e( 'Alerts', 'wp-mail-smtp' ); ?> </h4> <p class="wp-mail-smtp-product-education__description"> <?php esc_html_e( 'Configure at least one of these integrations to receive notifications when email fails to send from your site. Alert notifications will contain the following important data: email subject, email Send To address, the error message, and helpful links to help you fix the issue.', 'wp-mail-smtp' ); ?> </p> <a href="<?php echo esc_url( $top_upgrade_button_url ); ?>" target="_blank" rel="noopener noreferrer" class="wp-mail-smtp-product-education__upgrade-btn wp-mail-smtp-product-education__upgrade-btn--top wp-mail-smtp-btn wp-mail-smtp-btn-upgrade wp-mail-smtp-btn-orange"> <?php esc_html_e( 'Upgrade to WP Mail SMTP Pro', 'wp-mail-smtp' ); ?> </a> </div> <div class="wp-mail-smtp-product-education__row wp-mail-smtp-product-education__row--inactive"> <div id="wp-mail-smtp-setting-row-alert_event_types" class="wp-mail-smtp-setting-row wp-mail-smtp-clear"> <div class="wp-mail-smtp-setting-label"> <label for="wp-mail-smtp-setting-debug_event_types"> <?php esc_html_e( 'Notify when', 'wp-mail-smtp' ); ?> </label> </div> <div class="wp-mail-smtp-setting-field"> <?php UI::toggle( [ 'label' => esc_html__( 'The initial email sending request fails', 'wp-mail-smtp' ), 'checked' => true, 'disabled' => true, ] ); ?> <p class="desc"> <?php esc_html_e( 'This option is always enabled and will notify you about instant email sending failures.', 'wp-mail-smtp' ); ?> </p> <hr class="wp-mail-smtp-setting-mid-row-sep"> <?php UI::toggle( [ 'label' => esc_html__( 'The deliverability verification process detects a hard bounce', 'wp-mail-smtp' ), 'disabled' => true, ] ); ?> <p class="desc"> <?php esc_html_e( 'Get notified about emails that were successfully sent, but have hard bounced on delivery attempt. A hard bounce is an email that has failed to deliver for permanent reasons, such as the recipient\'s email address being invalid.', 'wp-mail-smtp' ); ?> </p> </div> </div> </div> <div class="wp-mail-smtp-product-education__row wp-mail-smtp-product-education__row--inactive"> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-alert"> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-content section-heading"> <div class="wp-mail-smtp-setting-field"> <h3><?php esc_html_e( 'Email', 'wp-mail-smtp' ); ?></h3> <p class="desc"><?php esc_html_e( 'Enter the email addresses (3 max) you’d like to use to receive alerts when email sending fails. Read our documentation on setting up email alerts.', 'wp-mail-smtp' ); ?></p> </div> </div> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-checkbox-toggle"> <div class="wp-mail-smtp-setting-label"> <label><?php esc_html_e( 'Email Alerts', 'wp-mail-smtp' ); ?></label> </div> <div class="wp-mail-smtp-setting-field"> <?php UI::toggle(); ?> </div> </div> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-alert-options"> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-alert-connection-options"> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-text"> <div class="wp-mail-smtp-setting-label"> <label><?php esc_html_e( 'Send To', 'wp-mail-smtp' ); ?></label> </div> <div class="wp-mail-smtp-setting-field"><input type="text"></div> </div> </div> </div> </div> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-alert"> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-content section-heading"> <div class="wp-mail-smtp-setting-field"> <h3><?php esc_html_e( 'Slack', 'wp-mail-smtp' ); ?></h3> <p class="desc"><?php esc_html_e( 'Paste in the Slack webhook URL you’d like to use to receive alerts when email sending fails. Read our documentation on setting up Slack alerts.', 'wp-mail-smtp' ); ?></p> </div> </div> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-checkbox-toggle"> <div class="wp-mail-smtp-setting-label"> <label><?php esc_html_e( 'Slack Alerts', 'wp-mail-smtp' ); ?></label> </div> <div class="wp-mail-smtp-setting-field"> <?php UI::toggle(); ?> </div> </div> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-alert-options"> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-alert-connection-options"> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-text"> <div class="wp-mail-smtp-setting-label"> <label><?php esc_html_e( 'Webhook URL', 'wp-mail-smtp' ); ?></label> </div> <div class="wp-mail-smtp-setting-field"><input type="text"></div> </div> </div> </div> </div> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-alert"> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-content section-heading"> <div class="wp-mail-smtp-setting-field"> <h3><?php esc_html_e( 'Discord', 'wp-mail-smtp' ); ?></h3> <p class="desc"><?php esc_html_e( 'Paste in the Discord webhook URL you’d like to use to receive alerts when email sending fails. Read our documentation on setting up Discord alerts.', 'wp-mail-smtp' ); ?></p> </div> </div> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-checkbox-toggle"> <div class="wp-mail-smtp-setting-label"> <label><?php esc_html_e( 'Discord Alerts', 'wp-mail-smtp' ); ?></label> </div> <div class="wp-mail-smtp-setting-field"> <?php UI::toggle(); ?> </div> </div> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-alert-options"> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-alert-connection-options"> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-text"> <div class="wp-mail-smtp-setting-label"> <label><?php esc_html_e( 'Webhook URL', 'wp-mail-smtp' ); ?></label> </div> <div class="wp-mail-smtp-setting-field"><input type="text"></div> </div> </div> </div> </div> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-alert"> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-content section-heading"> <div class="wp-mail-smtp-setting-field"> <h3><?php esc_html_e( 'Microsoft Teams', 'wp-mail-smtp' ); ?></h3> <p class="desc"><?php esc_html_e( 'Paste in the Microsoft Teams webhook URL you’d like to use to receive alerts when email sending fails. Read our documentation on setting up Microsoft Teams alerts.', 'wp-mail-smtp' ); ?></p> </div> </div> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-checkbox-toggle"> <div class="wp-mail-smtp-setting-label"> <label><?php esc_html_e( 'Microsoft Teams Alerts', 'wp-mail-smtp' ); ?></label> </div> <div class="wp-mail-smtp-setting-field"> <?php UI::toggle(); ?> </div> </div> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-alert-options"> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-alert-connection-options"> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-text"> <div class="wp-mail-smtp-setting-label"> <label><?php esc_html_e( 'Webhook URL', 'wp-mail-smtp' ); ?></label> </div> <div class="wp-mail-smtp-setting-field"><input type="text"></div> </div> </div> </div> </div> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-alert"> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-content section-heading"> <div class="wp-mail-smtp-setting-field"> <h3><?php esc_html_e( 'SMS via Twilio', 'wp-mail-smtp' ); ?></h3> <p class="desc"><?php esc_html_e( 'To receive SMS alerts, you’ll need a Twilio account. Read our documentation to learn how to set up Twilio SMS, then enter your connection details below.', 'wp-mail-smtp' ); ?></p> </div> </div> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-checkbox-toggle"> <div class="wp-mail-smtp-setting-label"> <label><?php esc_html_e( 'SMS via Twilio Alerts', 'wp-mail-smtp' ); ?></label> </div> <div class="wp-mail-smtp-setting-field"> <?php UI::toggle(); ?> </div> </div> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-alert-options"> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-alert-connection-options"> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-text"> <div class="wp-mail-smtp-setting-label"> <label><?php esc_html_e( 'Twilio Account ID', 'wp-mail-smtp' ); ?></label> </div> <div class="wp-mail-smtp-setting-field"><input type="text"></div> </div> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-text"> <div class="wp-mail-smtp-setting-label"> <label><?php esc_html_e( 'Twilio Auth Token', 'wp-mail-smtp' ); ?></label> </div> <div class="wp-mail-smtp-setting-field"><input type="text"></div> </div> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-text"> <div class="wp-mail-smtp-setting-label"> <label><?php esc_html_e( 'From Phone Number', 'wp-mail-smtp' ); ?></label> </div> <div class="wp-mail-smtp-setting-field"><input type="text"></div> </div> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-text"> <div class="wp-mail-smtp-setting-label"> <label><?php esc_html_e( 'To Phone Number', 'wp-mail-smtp' ); ?></label> </div> <div class="wp-mail-smtp-setting-field"><input type="text"></div> </div> </div> </div> </div> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-alert"> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-content section-heading"> <div class="wp-mail-smtp-setting-field"> <h3><?php esc_html_e( 'Webhook', 'wp-mail-smtp' ); ?></h3> <p class="desc"><?php esc_html_e( 'Paste in the webhook URL you’d like to use to receive alerts when email sending fails. Read our documentation on setting up webhook alerts.', 'wp-mail-smtp' ); ?></p> </div> </div> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-checkbox-toggle"> <div class="wp-mail-smtp-setting-label"> <label><?php esc_html_e( 'Webhook Alerts', 'wp-mail-smtp' ); ?></label> </div> <div class="wp-mail-smtp-setting-field"> <?php UI::toggle(); ?> </div> </div> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-alert-options"> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-alert-connection-options"> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-text"> <div class="wp-mail-smtp-setting-label"> <label><?php esc_html_e( 'Webhook URL', 'wp-mail-smtp' ); ?></label> </div> <div class="wp-mail-smtp-setting-field"><input type="text"></div> </div> </div> </div> </div> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-alert"> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-content section-heading"> <div class="wp-mail-smtp-setting-field"> <h3><?php esc_html_e( 'Push Notifications', 'wp-mail-smtp' ); ?></h3> <p class="desc"><?php esc_html_e( 'To receive push notifications on this device, you\'ll need to allow our plugin to send notifications via this browser. Read our documentation on setting up Push Notification alerts.', 'wp-mail-smtp' ); ?></p> </div> </div> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-checkbox-toggle"> <div class="wp-mail-smtp-setting-label"> <label><?php esc_html_e( 'Push Notification Alerts', 'wp-mail-smtp' ); ?></label> </div> <div class="wp-mail-smtp-setting-field"> <?php UI::toggle(); ?> </div> </div> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-alert-options"> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-alert-connection-options"> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-text"> <div class="wp-mail-smtp-setting-label"> <label><?php esc_html_e( 'Connection Name', 'wp-mail-smtp' ); ?></label> </div> <div class="wp-mail-smtp-setting-field"><input type="text"></div> </div> </div> </div> </div> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-alert"> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-content section-heading"> <div class="wp-mail-smtp-setting-field"> <h3><?php esc_html_e( 'WhatsApp', 'wp-mail-smtp' ); ?></h3> <p class="desc"><?php esc_html_e( 'Enter your WhatsApp Cloud API credentials to receive alerts when email sending fails. You\'ll need to create a Meta developer account and set up WhatsApp Business Platform.', 'wp-mail-smtp' ); ?></p> </div> </div> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-checkbox-toggle"> <div class="wp-mail-smtp-setting-label"> <label><?php esc_html_e( 'WhatsApp Alerts', 'wp-mail-smtp' ); ?></label> </div> <div class="wp-mail-smtp-setting-field"> <?php UI::toggle(); ?> </div> </div> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-alert-options"> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-alert-connection-options"> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-text"> <div class="wp-mail-smtp-setting-label"> <label><?php esc_html_e( 'Access Token', 'wp-mail-smtp' ); ?></label> </div> <div class="wp-mail-smtp-setting-field"><input type="password"></div> </div> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-text"> <div class="wp-mail-smtp-setting-label"> <label><?php esc_html_e( 'WhatsApp Business Account ID', 'wp-mail-smtp' ); ?></label> </div> <div class="wp-mail-smtp-setting-field"><input type="text"></div> </div> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-text"> <div class="wp-mail-smtp-setting-label"> <label><?php esc_html_e( 'Phone Number ID', 'wp-mail-smtp' ); ?></label> </div> <div class="wp-mail-smtp-setting-field"><input type="text"></div> </div> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-text"> <div class="wp-mail-smtp-setting-label"> <label><?php esc_html_e( 'To Phone Number', 'wp-mail-smtp' ); ?></label> </div> <div class="wp-mail-smtp-setting-field"><input type="text"></div> </div> </div> </div> </div> </div> <a href="<?php echo esc_url( $bottom_upgrade_button_url ); ?>" target="_blank" rel="noopener noreferrer" class="wp-mail-smtp-product-education__upgrade-btn wp-mail-smtp-product-education__upgrade-btn--bottom wp-mail-smtp-btn wp-mail-smtp-btn-upgrade wp-mail-smtp-btn-orange"> <?php esc_html_e( 'Upgrade to WP Mail SMTP Pro', 'wp-mail-smtp' ); ?> </a> </div> <?php } } Pages/Logs.php 0000644 00000001770 15252526345 0007236 0 ustar 00 <?php namespace WPMailSMTP\Admin\Pages; use WPMailSMTP\Admin\Area; use WPMailSMTP\Admin\PageAbstract; use WPMailSMTP\WP; /** * Class Logs */ class Logs extends PageAbstract { /** * Slug of a page. * * @since 1.5.0 * * @var string */ protected $slug = 'logs'; /** * Get the page/tab link. * * @since 1.5.0 * @since 2.1.0 Changed the URL to point to the email log settings tab. * * @return string */ public function get_link() { return add_query_arg( 'tab', $this->slug, WP::admin_url( 'admin.php?page=' . Area::SLUG ) ); } /** * Link label of a tab. * * @since 1.5.0 * * @return string */ public function get_label() { return esc_html__( 'Email Log', 'wp-mail-smtp' ); } /** * Title of a tab. * * @since 1.5.0 * * @return string */ public function get_title() { return $this->get_label(); } /** * Tab content. * * @since 2.1.0 Moved the display content to the email log settings tab. */ public function display() {} } Pages/ExportTab.php 0000644 00000013135 15252526345 0010240 0 ustar 00 <?php namespace WPMailSMTP\Admin\Pages; use WPMailSMTP\Admin\PageAbstract; /** * Class ExportTab is a placeholder for Pro email logs export. * Displays product education. * * @since 2.9.0 */ class ExportTab extends PageAbstract { /** * Part of the slug of a tab. * * @since 2.9.0 * * @var string */ protected $slug = 'export'; /** * Tab priority. * * @since 2.9.0 * * @var int */ protected $priority = 20; /** * Link label of a tab. * * @since 2.9.0 * * @return string */ public function get_label() { return esc_html__( 'Export', 'wp-mail-smtp' ); } /** * Title of a tab. * * @since 2.9.0 * * @return string */ public function get_title() { return $this->get_label(); } /** * Output HTML of the email logs export form preview. * * @since 2.9.0 */ public function display() { $top_upgrade_button_url = wp_mail_smtp()->get_upgrade_link( [ 'medium' => 'tools-export', 'content' => 'upgrade-to-wp-mail-smtp-pro-button-top', ] ); $bottom_upgrade_button_url = wp_mail_smtp()->get_upgrade_link( [ 'medium' => 'tools-export', 'content' => 'upgrade-to-wp-mail-smtp-pro-button', ] ); ?> <div id="wp-mail-smtp-tools-export-email-logs-product-education" class="wp-mail-smtp-product-education"> <div class="wp-mail-smtp-product-education__row"> <h4 class="wp-mail-smtp-product-education__heading"> <?php esc_html_e( 'Export Email Logs', 'wp-mail-smtp' ); ?> </h4> <p class="wp-mail-smtp-product-education__description"> <?php esc_html_e( 'Easily export your logs to CSV or Excel. Filter the logs before you export and only download the data you need. This feature lets you easily create your own deliverability reports. You can also use the data in 3rd party dashboards to track deliverability along with your other website statistics.', 'wp-mail-smtp' ); ?> </p> <a href="<?php echo esc_url( $top_upgrade_button_url ); ?>" target="_blank" rel="noopener noreferrer" class="wp-mail-smtp-product-education__upgrade-btn wp-mail-smtp-product-education__upgrade-btn--top wp-mail-smtp-btn wp-mail-smtp-btn-upgrade wp-mail-smtp-btn-orange"> <?php esc_html_e( 'Upgrade to WP Mail SMTP Pro', 'wp-mail-smtp' ); ?> </a> </div> <div class="wp-mail-smtp-product-education__row wp-mail-smtp-product-education__row--inactive"> <section class="wp-clearfix"> <h5><?php esc_html_e( 'Export Type', 'wp-mail-smtp' ); ?></h5> <label> <input type="radio" checked><?php esc_html_e( 'Export in CSV (.csv)', 'wp-mail-smtp' ); ?> </label> <label> <input type="radio"><?php esc_html_e( 'Export in Microsoft Excel (.xlsx)', 'wp-mail-smtp' ); ?> </label> <label> <input type="radio"><?php esc_html_e( 'Export in EML (.eml)', 'wp-mail-smtp' ); ?> </label> </section> <section class="wp-clearfix"> <h5><?php esc_html_e( 'Common Information', 'wp-mail-smtp' ); ?></h5> <label><input type="checkbox" checked><?php esc_html_e( 'To Address', 'wp-mail-smtp' ); ?></label> <label><input type="checkbox" checked><?php esc_html_e( 'From Address', 'wp-mail-smtp' ); ?></label> <label><input type="checkbox" checked><?php esc_html_e( 'From Name', 'wp-mail-smtp' ); ?></label> <label><input type="checkbox" checked><?php esc_html_e( 'Subject', 'wp-mail-smtp' ); ?></label> <label><input type="checkbox" checked><?php esc_html_e( 'Body', 'wp-mail-smtp' ); ?></label> <label><input type="checkbox" checked><?php esc_html_e( 'Created Date', 'wp-mail-smtp' ); ?></label> <label><input type="checkbox" checked><?php esc_html_e( 'Number of Attachments', 'wp-mail-smtp' ); ?></label> <label><input type="checkbox" checked><?php esc_html_e( 'Attachments', 'wp-mail-smtp' ); ?></label> </section> <section class="wp-clearfix"> <h5><?php esc_html_e( 'Additional Information', 'wp-mail-smtp' ); ?></h5> <label><input type="checkbox"><?php esc_html_e( 'Status', 'wp-mail-smtp' ); ?></label> <label><input type="checkbox"><?php esc_html_e( 'Carbon Copy (CC)', 'wp-mail-smtp' ); ?></label> <label><input type="checkbox"><?php esc_html_e( 'Blind Carbon Copy (BCC)', 'wp-mail-smtp' ); ?></label> <label><input type="checkbox"><?php esc_html_e( 'Headers', 'wp-mail-smtp' ); ?></label> <label><input type="checkbox"><?php esc_html_e( 'Mailer', 'wp-mail-smtp' ); ?></label> <label><input type="checkbox"><?php esc_html_e( 'Error Details', 'wp-mail-smtp' ); ?></label> <label><input type="checkbox"><?php esc_html_e( 'Email log ID', 'wp-mail-smtp' ); ?></label> <label><input type="checkbox"><?php esc_html_e( 'Source', 'wp-mail-smtp' ); ?></label> </section> <section class="wp-clearfix"> <h5><?php esc_html_e( 'Custom Date Range', 'wp-mail-smtp' ); ?></h5> <input type="text" class="wp-mail-smtp-date-selector" placeholder="<?php esc_html_e( 'Select a date range', 'wp-mail-smtp' ); ?>"> </section> <section class="wp-clearfix"> <h5><?php esc_html_e( 'Search', 'wp-mail-smtp' ); ?></h5> <select class="wp-mail-smtp-search-box-field"> <option><?php esc_html_e( 'Email Addresses', 'wp-mail-smtp' ); ?></option> </select> <input type="text" class="wp-mail-smtp-search-box-term"> </section> </div> <a href="<?php echo esc_url( $bottom_upgrade_button_url ); ?>" target="_blank" rel="noopener noreferrer" class="wp-mail-smtp-product-education__upgrade-btn wp-mail-smtp-product-education__upgrade-btn--bottom wp-mail-smtp-btn wp-mail-smtp-btn-upgrade wp-mail-smtp-btn-orange"> <?php esc_html_e( 'Upgrade to WP Mail SMTP Pro', 'wp-mail-smtp' ); ?> </a> </div> <?php } } Pages/AboutTab.php 0000644 00000104275 15252526345 0010037 0 ustar 00 <?php namespace WPMailSMTP\Admin\Pages; use Plugin_Upgrader; use WPMailSMTP\Admin\PageAbstract; use WPMailSMTP\Admin\PluginsInstallSkin; use WPMailSMTP\Helpers\Helpers; /** * About tab. * * @since 2.9.0 */ class AboutTab extends PageAbstract { /** * Part of the slug of a tab. * * @since 2.9.0 * * @var string */ protected $slug = 'about'; /** * Tab priority. * * @since 2.9.0 * * @var int */ protected $priority = 20; /** * Link label of a tab. * * @since 2.9.0 * * @return string */ public function get_label() { return esc_html__( 'About Us', 'wp-mail-smtp' ); } /** * Title of a tab. * * @since 2.9.0 * * @return string */ public function get_title() { return $this->get_label(); } /** * Tab content. * * @since 2.9.0 */ public function display() { ?> <div class="wp-mail-smtp-admin-about-section wp-mail-smtp-admin-columns"> <div class="wp-mail-smtp-admin-column-60"> <h3> <?php esc_html_e( 'Hello and welcome to WP Mail SMTP, the easiest and most popular WordPress SMTP plugin. We build software that helps your site reliably deliver emails every time.', 'wp-mail-smtp' ); ?> </h3> <p> <?php esc_html_e( 'Email deliverability has been a well-documented problem for all WordPress websites. However as WPForms grew, we became more aware of this painful issue that affects our users and the larger WordPress community. So we decided to solve this problem and make a solution that\'s beginner friendly.', 'wp-mail-smtp' ); ?> </p> <p> <?php esc_html_e( 'Our goal is to make reliable email deliverability easy for WordPress.', 'wp-mail-smtp' ); ?> </p> <p> <?php printf( wp_kses( /* translators: %1$s - WPForms URL, %2$s - WPBeginner URL, %3$s - OptinMonster URL, %4$s - MonsterInsights URL, %5$s - Awesome Motive URL */ __( 'WP Mail SMTP is brought to you by the same team that\'s behind the most user friendly WordPress forms, <a href="%1$s" target="_blank" rel="noopener noreferrer">WPForms</a>, the largest WordPress resource site, <a href="%2$s" target="_blank" rel="noopener noreferrer">WPBeginner</a>, the most popular lead-generation software, <a href="%3$s" target="_blank" rel="noopener noreferrer">OptinMonster</a>, the best WordPress analytics plugin, <a href="%4$s" target="_blank" rel="noopener noreferrer">MonsterInsights</a>, and <a href="%5$s" target="_blank" rel="noopener noreferrer">more</a>.', 'wp-mail-smtp' ), [ 'a' => [ 'href' => [], 'rel' => [], 'target' => [], ], ] ), 'https://wpforms.com/?utm_source=wpmailsmtpplugin&utm_medium=pluginaboutpage&utm_campaign=aboutwpmailsmtp', 'https://www.wpbeginner.com/?utm_source=wpmailsmtpplugin&utm_medium=pluginaboutpage&utm_campaign=aboutwpmailsmtp', 'https://optinmonster.com/?utm_source=wpmailsmtpplugin&utm_medium=pluginaboutpage&utm_campaign=aboutwpmailsmtp', 'https://www.monsterinsights.com/?utm_source=wpmailsmtpplugin&utm_medium=pluginaboutpage&utm_campaign=aboutwpmailsmtp', // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound esc_url( wp_mail_smtp()->get_utm_url( 'https://awesomemotive.com/', [ 'medium' => 'pluginaboutpage', 'content' => 'aboutwpmailsmtp' ] ) ) ); ?> </p> <p> <?php esc_html_e( 'Yup, we know a thing or two about building awesome products that customers love.', 'wp-mail-smtp' ); ?> </p> </div> <div class="wp-mail-smtp-admin-column-40 wp-mail-smtp-admin-column-last"> <figure> <img src="<?php echo esc_url( wp_mail_smtp()->assets_url . '/images/about/team.jpg' ); ?>" alt="<?php esc_attr_e( 'The WPForms Team photo', 'wp-mail-smtp' ); ?>"> <figcaption> <?php esc_html_e( 'The WPForms Team', 'wp-mail-smtp' ); ?> </figcaption> </figure> </div> </div> <?php // Do not display the plugin section if the user can't install or activate them. if ( ! current_user_can( 'install_plugins' ) && ! current_user_can( 'activate_plugins' ) ) { return; } $this->display_plugins(); } /** * Display the plugins section. * * @since 2.9.0 */ protected function display_plugins() { ?> <div class="wp-mail-smtp-admin-about-plugins"> <div class="plugins-container"> <?php foreach ( self::get_am_plugins() as $key => $plugin ) : $is_url_external = false; $data = $this->get_about_plugins_data( $plugin ); if ( isset( $plugin['pro'] ) && array_key_exists( $plugin['pro']['path'], get_plugins() ) ) { $is_url_external = true; $plugin = $plugin['pro']; $data = array_merge( $data, $this->get_about_plugins_data( $plugin, true ) ); } // Do not display a plugin which has to be installed and the user can't install it. if ( ! current_user_can( 'install_plugins' ) && $data['status_class'] === 'status-download' ) { continue; } ?> <div class="plugin-container"> <div class="plugin-item"> <div class="details wp-mail-smtp-clear"> <img src="<?php echo esc_url( $plugin['icon'] ); ?>" alt="<?php esc_attr_e( 'Plugin icon', 'wp-mail-smtp' ); ?>"> <h5 class="plugin-name"> <?php echo esc_html( $plugin['name'] ); ?> </h5> <p class="plugin-desc"> <?php echo esc_html( $plugin['desc'] ); ?> </p> </div> <div class="actions wp-mail-smtp-clear"> <div class="status"> <strong> <?php printf( /* translators: %s - status HTML text. */ esc_html__( 'Status: %s', 'wp-mail-smtp' ), '<span class="status-label ' . esc_attr( $data['status_class'] ) . '">' . esc_html( $data['status_text'] ) . '</span>' ); ?> </strong> </div> <div class="action-button"> <?php $go_to_class = ''; if ( $is_url_external && $data['status_class'] === 'status-download' ) { $go_to_class = ' go_to'; } ?> <a href="<?php echo esc_url( $plugin['url'] ); ?>" class="<?php echo esc_attr( $data['action_class'] . $go_to_class ); ?>" data-plugin="<?php echo esc_attr( $data['plugin_src'] ); ?>"> <?php echo esc_html( $data['action_text'] ); ?> </a> </div> </div> </div> </div> <?php endforeach; ?> </div> </div> <?php } /** * Generate all the required CSS classed and labels to be used in rendering. * * @since 2.9.0 * * @param array $plugin Plugin slug. * @param bool $is_pro License type. * * @return mixed */ protected function get_about_plugins_data( $plugin, $is_pro = false ) { $data = []; if ( array_key_exists( $plugin['path'], get_plugins() ) ) { if ( is_plugin_active( $plugin['path'] ) ) { // Status text/status. $data['status_class'] = 'status-active'; $data['status_text'] = esc_html__( 'Active', 'wp-mail-smtp' ); // Button text/status. $data['action_class'] = $data['status_class'] . ' button button-secondary disabled'; $data['action_text'] = esc_html__( 'Activated', 'wp-mail-smtp' ); $data['plugin_src'] = esc_attr( $plugin['path'] ); } else { // Status text/status. $data['status_class'] = 'status-inactive'; $data['status_text'] = esc_html__( 'Inactive', 'wp-mail-smtp' ); // Button text/status. $data['action_class'] = $data['status_class'] . ' button button-secondary'; $data['action_text'] = esc_html__( 'Activate', 'wp-mail-smtp' ); $data['plugin_src'] = esc_attr( $plugin['path'] ); } } else { if ( ! $is_pro ) { // Doesn't exist, install. // Status text/status. $data['status_class'] = 'status-download'; $data['status_text'] = esc_html__( 'Not Installed', 'wp-mail-smtp' ); // Button text/status. $data['action_class'] = $data['status_class'] . ' button button-primary'; $data['action_text'] = esc_html__( 'Install Plugin', 'wp-mail-smtp' ); $data['plugin_src'] = esc_url( $plugin['url'] ); // If plugin URL is not a zip file, open a new tab with site URL. if ( preg_match( '/.*\.zip$/', $plugin['url'] ) === 0 ) { $data['status_class'] = 'status-open'; $data['action_class'] = $data['status_class'] . ' button button-primary'; $data['action_text'] = esc_html__( 'Visit Site', 'wp-mail-smtp' ); } } } return $data; } /** * List of AM plugins that we propose to install. * * @since 2.9.0 * * @return array */ private static function get_am_plugins() { $data = [ 'om' => [ 'path' => 'optinmonster/optin-monster-wp-api.php', 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-om.png', 'name' => esc_html__( 'OptinMonster', 'wp-mail-smtp' ), 'desc' => esc_html__( 'Instantly get more subscribers, leads, and sales with the #1 conversion optimization toolkit. Create high converting popups, announcement bars, spin a wheel, and more with smart targeting and personalization.', 'wp-mail-smtp' ), 'url' => 'https://downloads.wordpress.org/plugin/optinmonster.zip', ], 'wpforms' => [ 'path' => 'wpforms-lite/wpforms.php', 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-wpf.png', 'name' => esc_html__( 'WPForms', 'wp-mail-smtp' ), 'desc' => esc_html__( 'The best drag & drop WordPress form builder. Easily create beautiful contact forms, surveys, payment forms, and more with our 600+ form templates. Trusted by over 5 million websites as the best forms plugin.', 'wp-mail-smtp' ), 'url' => 'https://downloads.wordpress.org/plugin/wpforms-lite.zip', 'pro' => [ 'path' => 'wpforms/wpforms.php', 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-wpf.png', 'name' => esc_html__( 'WPForms Pro', 'wp-mail-smtp' ), 'desc' => esc_html__( 'The best drag & drop WordPress form builder. Easily create beautiful contact forms, surveys, payment forms, and more with our 600+ form templates. Trusted by over 5 million websites as the best forms plugin.', 'wp-mail-smtp' ), 'url' => 'https://wpforms.com/?utm_source=WordPress&utm_medium=about&utm_campaign=smtp', ], ], 'mi' => [ 'path' => 'google-analytics-for-wordpress/googleanalytics.php', 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-mi.png', 'name' => esc_html__( 'MonsterInsights', 'wp-mail-smtp' ), 'desc' => esc_html__( 'The leading WordPress analytics plugin that shows you how people find and use your website, so you can make data driven decisions to grow your business. Properly set up Google Analytics without writing code.', 'wp-mail-smtp' ), 'url' => 'https://downloads.wordpress.org/plugin/google-analytics-for-wordpress.zip', 'pro' => [ 'path' => 'google-analytics-premium/googleanalytics-premium.php', 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-mi.png', 'name' => esc_html__( 'MonsterInsights Pro', 'wp-mail-smtp' ), 'desc' => esc_html__( 'The leading WordPress analytics plugin that shows you how people find and use your website, so you can make data driven decisions to grow your business. Properly set up Google Analytics without writing code.', 'wp-mail-smtp' ), 'url' => 'https://www.monsterinsights.com/?utm_source=WordPress&utm_medium=about&utm_campaign=smtp', ], ], 'aioseo' => [ 'path' => 'all-in-one-seo-pack/all_in_one_seo_pack.php', 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-aioseo.png', 'name' => esc_html__( 'AIOSEO', 'wp-mail-smtp' ), 'desc' => esc_html__( 'The original WordPress SEO plugin and toolkit that improves your website’s search rankings. Comes with all the SEO features like Local SEO, WooCommerce SEO, sitemaps, SEO optimizer, schema, and more.', 'wp-mail-smtp' ), 'url' => 'https://downloads.wordpress.org/plugin/all-in-one-seo-pack.zip', 'pro' => [ 'path' => 'all-in-one-seo-pack-pro/all_in_one_seo_pack.php', 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-aioseo.png', 'name' => esc_html__( 'AIOSEO', 'wp-mail-smtp' ), 'desc' => esc_html__( 'The original WordPress SEO plugin and toolkit that improves your website’s search rankings. Comes with all the SEO features like Local SEO, WooCommerce SEO, sitemaps, SEO optimizer, schema, and more.', 'wp-mail-smtp' ), 'url' => 'https://aioseo.com/?utm_source=WordPress&utm_medium=about&utm_campaign=smtp', ], ], 'seedprod' => [ 'path' => 'coming-soon/coming-soon.php', 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-seedprod.png', 'name' => esc_html__( 'SeedProd', 'wp-mail-smtp' ), 'desc' => esc_html__( 'The fastest drag & drop landing page builder for WordPress. Create custom landing pages without writing code, connect them with your CRM, collect subscribers, and grow your audience. Trusted by 1 million sites.', 'wp-mail-smtp' ), 'url' => 'https://downloads.wordpress.org/plugin/coming-soon.zip', 'pro' => [ 'path' => 'seedprod-coming-soon-pro-5/seedprod-coming-soon-pro-5.php', 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-seedprod.png', 'name' => esc_html__( 'SeedProd', 'wp-mail-smtp' ), 'desc' => esc_html__( 'The fastest drag & drop landing page builder for WordPress. Create custom landing pages without writing code, connect them with your CRM, collect subscribers, and grow your audience. Trusted by 1 million sites.', 'wp-mail-smtp' ), 'url' => 'https://www.seedprod.com/?utm_source=WordPress&utm_medium=about&utm_campaign=smtp', ], ], 'rafflepress' => [ 'path' => 'rafflepress/rafflepress.php', 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-rp.png', 'name' => esc_html__( 'RafflePress', 'wp-mail-smtp' ), 'desc' => esc_html__( 'Turn your website visitors into brand ambassadors! Easily grow your email list, website traffic, and social media followers with the most powerful giveaways & contests plugin for WordPress.', 'wp-mail-smtp' ), 'url' => 'https://downloads.wordpress.org/plugin/rafflepress.zip', 'pro' => [ 'path' => 'rafflepress-pro/rafflepress-pro.php', 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-rp.png', 'name' => esc_html__( 'RafflePress Pro', 'wp-mail-smtp' ), 'desc' => esc_html__( 'Turn your website visitors into brand ambassadors! Easily grow your email list, website traffic, and social media followers with the most powerful giveaways & contests plugin for WordPress.', 'wp-mail-smtp' ), 'url' => 'https://rafflepress.com/pricing/?utm_source=WordPress&utm_medium=about&utm_campaign=smtp', ], ], 'pushengage' => [ 'path' => 'pushengage/main.php', 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-pushengage.png', 'name' => esc_html__( 'PushEngage', 'wp-mail-smtp' ), 'desc' => esc_html__( 'Connect with your visitors after they leave your website with the leading web push notification software. Over 10,000+ businesses worldwide use PushEngage to send 15 billion notifications each month.', 'wp-mail-smtp' ), 'url' => 'https://downloads.wordpress.org/plugin/pushengage.zip', ], 'smash-balloon-instagram-feeds' => [ 'path' => 'instagram-feed/instagram-feed.php', 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-smash-balloon-instagram-feeds.png', 'name' => esc_html__( 'Smash Balloon Instagram Feeds', 'wp-mail-smtp' ), 'desc' => esc_html__( 'Easily display Instagram content on your WordPress site without writing any code. Comes with multiple templates, ability to show content from multiple accounts, hashtags, and more. Trusted by 1 million websites.', 'wp-mail-smtp' ), 'url' => 'https://downloads.wordpress.org/plugin/instagram-feed.zip', 'pro' => [ 'path' => 'instagram-feed-pro/instagram-feed.php', 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-smash-balloon-instagram-feeds.png', 'name' => esc_html__( 'Smash Balloon Instagram Feeds', 'wp-mail-smtp' ), 'desc' => esc_html__( 'Easily display Instagram content on your WordPress site without writing any code. Comes with multiple templates, ability to show content from multiple accounts, hashtags, and more. Trusted by 1 million websites.', 'wp-mail-smtp' ), 'url' => 'https://smashballoon.com/instagram-feed/?utm_source=WordPress&utm_medium=about&utm_campaign=smtp', ], ], 'smash-balloon-facebook-feeds' => [ 'path' => 'custom-facebook-feed/custom-facebook-feed.php', 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-smash-balloon-facebook-feeds.png', 'name' => esc_html__( 'Smash Balloon Facebook Feeds', 'wp-mail-smtp' ), 'desc' => esc_html__( 'Easily display Facebook content on your WordPress site without writing any code. Comes with multiple templates, ability to embed albums, group content, reviews, live videos, comments, and reactions.', 'wp-mail-smtp' ), 'url' => 'https://downloads.wordpress.org/plugin/custom-facebook-feed.zip', 'pro' => [ 'path' => 'custom-facebook-feed-pro/custom-facebook-feed.php', 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-smash-balloon-facebook-feeds.png', 'name' => esc_html__( 'Smash Balloon Facebook Feeds', 'wp-mail-smtp' ), 'desc' => esc_html__( 'Easily display Facebook content on your WordPress site without writing any code. Comes with multiple templates, ability to embed albums, group content, reviews, live videos, comments, and reactions.', 'wp-mail-smtp' ), 'url' => 'https://smashballoon.com/custom-facebook-feed/?utm_source=WordPress&utm_medium=about&utm_campaign=smtp', ], ], 'smash-balloon-youtube-feeds' => [ 'path' => 'feeds-for-youtube/youtube-feed.php', 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-smash-balloon-youtube-feeds.png', 'name' => esc_html__( 'Smash Balloon YouTube Feeds', 'wp-mail-smtp' ), 'desc' => esc_html__( 'Easily display YouTube videos on your WordPress site without writing any code. Comes with multiple layouts, ability to embed live streams, video filtering, ability to combine multiple channel videos, and more.', 'wp-mail-smtp' ), 'url' => 'https://downloads.wordpress.org/plugin/feeds-for-youtube.zip', 'pro' => [ 'path' => 'youtube-feed-pro/youtube-feed.php', 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-smash-balloon-youtube-feeds.png', 'name' => esc_html__( 'Smash Balloon YouTube Feeds', 'wp-mail-smtp' ), 'desc' => esc_html__( 'Easily display YouTube videos on your WordPress site without writing any code. Comes with multiple layouts, ability to embed live streams, video filtering, ability to combine multiple channel videos, and more.', 'wp-mail-smtp' ), 'url' => 'https://smashballoon.com/youtube-feed/?utm_source=WordPress&utm_medium=about&utm_campaign=smtp', ], ], 'smash-balloon-twitter-feeds' => [ 'path' => 'custom-twitter-feeds/custom-twitter-feed.php', 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-smash-balloon-twitter-feeds.png', 'name' => esc_html__( 'Smash Balloon Twitter Feeds', 'wp-mail-smtp' ), 'desc' => esc_html__( 'Easily display Twitter content in WordPress without writing any code. Comes with multiple layouts, ability to combine multiple Twitter feeds, Twitter card support, tweet moderation, and more.', 'wp-mail-smtp' ), 'url' => 'https://downloads.wordpress.org/plugin/custom-twitter-feeds.zip', 'pro' => [ 'path' => 'custom-twitter-feeds-pro/custom-twitter-feed.php', 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-smash-balloon-twitter-feeds.png', 'name' => esc_html__( 'Smash Balloon Twitter Feeds', 'wp-mail-smtp' ), 'desc' => esc_html__( 'Easily display Twitter content in WordPress without writing any code. Comes with multiple layouts, ability to combine multiple Twitter feeds, Twitter card support, tweet moderation, and more.', 'wp-mail-smtp' ), 'url' => 'https://smashballoon.com/custom-twitter-feeds/?utm_source=WordPress&utm_medium=about&utm_campaign=smtp', ], ], 'trustpulse' => [ 'path' => 'trustpulse-api/trustpulse.php', 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-trustpulse.png', 'name' => esc_html__( 'TrustPulse', 'wp-mail-smtp' ), 'desc' => esc_html__( 'Boost your sales and conversions by up to 15% with real-time social proof notifications. TrustPulse helps you show live user activity and purchases to help convince other users to purchase.', 'wp-mail-smtp' ), 'url' => 'https://downloads.wordpress.org/plugin/trustpulse-api.zip', ], 'searchwp' => [ 'path' => '', 'icon' => wp_mail_smtp()->assets_url . '/images/about/searchwp.png', 'name' => esc_html__( 'SearchWP', 'wp-mail-smtp' ), 'desc' => esc_html__( 'The most advanced WordPress search plugin. Customize your WordPress search algorithm, reorder search results, track search metrics, and everything you need to leverage search to grow your business.', 'wp-mail-smtp' ), 'url' => 'https://searchwp.com/?utm_source=WordPress&utm_medium=about&utm_campaign=smtp', 'pro' => [ 'path' => 'searchwp/index.php', 'icon' => wp_mail_smtp()->assets_url . '/images/about/searchwp.png', 'name' => esc_html__( 'SearchWP', 'wp-mail-smtp' ), 'desc' => esc_html__( 'The most advanced WordPress search plugin. Customize your WordPress search algorithm, reorder search results, track search metrics, and everything you need to leverage search to grow your business.', 'wp-mail-smtp' ), 'url' => 'https://searchwp.com/?utm_source=WordPress&utm_medium=about&utm_campaign=smtp', ], ], 'affiliatewp' => [ 'path' => '', 'icon' => wp_mail_smtp()->assets_url . '/images/about/affiliatewp.png', 'name' => esc_html__( 'AffiliateWP', 'wp-mail-smtp' ), 'desc' => esc_html__( 'The #1 affiliate management plugin for WordPress. Easily create an affiliate program for your eCommerce store or membership site within minutes and start growing your sales with the power of referral marketing.', 'wp-mail-smtp' ), 'url' => 'https://affiliatewp.com/?utm_source=WordPress&utm_medium=about&utm_campaign=smtp', 'pro' => [ 'path' => 'affiliate-wp/affiliate-wp.php', 'icon' => wp_mail_smtp()->assets_url . '/images/about/affiliatewp.png', 'name' => esc_html__( 'AffiliateWP', 'wp-mail-smtp' ), 'desc' => esc_html__( 'The #1 affiliate management plugin for WordPress. Easily create an affiliate program for your eCommerce store or membership site within minutes and start growing your sales with the power of referral marketing.', 'wp-mail-smtp' ), 'url' => 'https://affiliatewp.com/?utm_source=WordPress&utm_medium=about&utm_campaign=smtp', ], ], 'wp-simple-pay' => [ 'path' => 'stripe/stripe-checkout.php', 'icon' => wp_mail_smtp()->assets_url . '/images/about/wp-simple-pay.png', 'name' => esc_html__( 'WP Simple Pay', 'wp-mail-smtp' ), 'desc' => esc_html__( 'The #1 Stripe payments plugin for WordPress. Start accepting one-time and recurring payments on your WordPress site without setting up a shopping cart. No code required.', 'wp-mail-smtp' ), 'url' => 'https://downloads.wordpress.org/plugin/stripe.zip', 'pro' => [ 'path' => 'wp-simple-pay-pro-3/simple-pay.php', 'icon' => wp_mail_smtp()->assets_url . '/images/about/wp-simple-pay.png', 'name' => esc_html__( 'WP Simple Pay Pro', 'wp-mail-smtp' ), 'desc' => esc_html__( 'The #1 Stripe payments plugin for WordPress. Start accepting one-time and recurring payments on your WordPress site without setting up a shopping cart. No code required.', 'wp-mail-smtp' ), 'url' => 'https://wpsimplepay.com/?utm_source=WordPress&utm_medium=about&utm_campaign=smtp', ], ], 'easy-digital-downloads' => [ 'path' => 'easy-digital-downloads/easy-digital-downloads.php', 'icon' => wp_mail_smtp()->assets_url . '/images/about/edd.png', 'name' => esc_html__( 'Easy Digital Downloads', 'wp-mail-smtp' ), 'desc' => esc_html__( 'The best WordPress eCommerce plugin for selling digital downloads. Start selling eBooks, software, music, digital art, and more within minutes. Accept payments, manage subscriptions, advanced access control, and more.', 'wp-mail-smtp' ), 'url' => 'https://downloads.wordpress.org/plugin/easy-digital-downloads.zip', ], 'sugar-calendar' => [ 'path' => 'sugar-calendar-lite/sugar-calendar-lite.php', 'icon' => wp_mail_smtp()->assets_url . '/images/about/sugar-calendar.png', 'name' => esc_html__( 'Sugar Calendar Lite', 'wp-mail-smtp' ), 'desc' => esc_html__( 'A simple & powerful event calendar plugin for WordPress that comes with all the event management features including payments, scheduling, timezones, ticketing, recurring events, and more.', 'wp-mail-smtp' ), 'url' => 'https://downloads.wordpress.org/plugin/sugar-calendar-lite.zip', 'pro' => [ 'path' => 'sugar-calendar/sugar-calendar.php', 'icon' => wp_mail_smtp()->assets_url . '/images/about/sugar-calendar.png', 'name' => esc_html__( 'Sugar Calendar', 'wp-mail-smtp' ), 'desc' => esc_html__( 'A simple & powerful event calendar plugin for WordPress that comes with all the event management features including payments, scheduling, timezones, ticketing, recurring events, and more.', 'wp-mail-smtp' ), 'url' => 'https://sugarcalendar.com/?utm_source=WordPress&utm_medium=about&utm_campaign=smtp', ], ], 'wp-charitable' => [ 'path' => 'charitable/charitable.php', 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-charitable.png', 'name' => esc_html__( 'Charitable', 'wp-mail-smtp' ), 'desc' => esc_html__( 'Top-rated WordPress donation and fundraising plugin. Over 10,000+ non-profit organizations and website owners use Charitable to create fundraising campaigns and raise more money online.', 'wp-mail-smtp' ), 'url' => 'https://downloads.wordpress.org/plugin/charitable.zip', ], 'wpcode' => [ 'path' => 'insert-headers-and-footers/ihaf.php', 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-wpcode.png', 'name' => esc_html__( 'WPCode Lite', 'wp-mail-smtp' ), 'desc' => esc_html__( 'Future proof your WordPress customizations with the most popular code snippet management plugin for WordPress. Trusted by over 1,500,000+ websites for easily adding code to WordPress right from the admin area.', 'wp-mail-smtp' ), 'url' => 'https://downloads.wordpress.org/plugin/insert-headers-and-footers.zip', 'pro' => [ 'path' => 'wpcode-premium/wpcode.php', 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-wpcode.png', 'name' => esc_html__( 'WPCode Pro', 'wp-mail-smtp' ), 'desc' => esc_html__( 'Future proof your WordPress customizations with the most popular code snippet management plugin for WordPress. Trusted by over 1,500,000+ websites for easily adding code to WordPress right from the admin area.', 'wp-mail-smtp' ), 'url' => 'https://wpcode.com/?utm_source=WordPress&utm_medium=about&utm_campaign=smtp', ], ], 'duplicator' => [ 'path' => 'duplicator/duplicator.php', 'icon' => wp_mail_smtp()->assets_url . '/images/about/duplicator-icon-large.png', 'name' => esc_html__( 'Duplicator', 'wp-mail-smtp' ), 'desc' => esc_html__( 'Leading WordPress backup & site migration plugin. Over 1,500,000+ smart website owners use Duplicator to make reliable and secure WordPress backups to protect their websites. It also makes website migration really easy.', 'wp-mail-smtp' ), 'url' => 'https://downloads.wordpress.org/plugin/duplicator.zip', 'pro' => [ 'path' => 'duplicator-pro/duplicator-pro.php', 'icon' => wp_mail_smtp()->assets_url . '/images/about/duplicator-icon-large.png', 'name' => esc_html__( 'Duplicator Pro', 'wp-mail-smtp' ), 'desc' => esc_html__( 'Leading WordPress backup & site migration plugin. Over 1,500,000+ smart website owners use Duplicator to make reliable and secure WordPress backups to protect their websites. It also makes website migration really easy.', 'wp-mail-smtp' ), 'url' => 'https://duplicator.com/?utm_source=WordPress&utm_medium=about&utm_campaign=smtp', ], ], 'activelayer' => [ 'path' => 'activelayer-anti-spam-spam-protection-for-forms-comments/activelayer-anti-spam-spam-protection-for-forms-comments.php', 'icon' => wp_mail_smtp()->assets_url . '/images/about/icon-activelayer.svg', 'name' => esc_html__( 'ActiveLayer', 'wp-mail-smtp' ), 'desc' => esc_html__( 'Smarter spam protection for WordPress. Catch spam in milliseconds with AI, invisible to your real visitors.', 'wp-mail-smtp' ), 'url' => 'https://downloads.wordpress.org/plugin/activelayer-anti-spam-spam-protection-for-forms-comments.zip', ], 'wpconsent' => [ 'path' => 'wpconsent-cookies-banner-privacy-suite/wpconsent.php', 'icon' => wp_mail_smtp()->assets_url . '/images/about/icon-wpconsent.svg', 'name' => esc_html__( 'WPConsent', 'wp-mail-smtp' ), 'desc' => esc_html__( 'Stay GDPR & privacy compliant. Add a cookie consent banner to your site and meet privacy laws in minutes.', 'wp-mail-smtp' ), 'url' => 'https://downloads.wordpress.org/plugin/wpconsent-cookies-banner-privacy-suite.zip', ], 'wpvibe' => [ 'path' => 'vibe-ai/vibe-ai.php', 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-vibe-ai.png', 'name' => esc_html__( 'Vibe AI', 'wp-mail-smtp' ), 'desc' => esc_html__( 'AI-powered tools for WordPress. Work faster and smarter with automation built for your WordPress workflow.', 'wp-mail-smtp' ), 'url' => 'https://downloads.wordpress.org/plugin/vibe-ai.zip', ], 'universally' => [ 'path' => 'universally-language-translation-multilingual-tool/universally.php', 'icon' => wp_mail_smtp()->assets_url . '/images/about/icon-universally.svg', 'name' => esc_html__( 'Universally', 'wp-mail-smtp' ), 'desc' => esc_html__( 'Make your WordPress site accessible to everyone. Automatic accessibility fixes and a visitor widget, no coding required.', 'wp-mail-smtp' ), 'url' => 'https://downloads.wordpress.org/plugin/universally-language-translation-multilingual-tool.zip', ], ]; return $data; } /** * Active the given plugin. * * @since 2.9.0 */ public static function ajax_plugin_activate() { $error = esc_html__( 'Could not activate the plugin. Please activate it from the Plugins page.', 'wp-mail-smtp' ); // Check for permissions. if ( ! current_user_can( 'activate_plugins' ) ) { wp_send_json_error( $error ); } if ( empty( $_POST['plugin'] ) ) { wp_send_json_error( $error ); } $plugin_slug = sanitize_text_field( wp_unslash( $_POST['plugin'] ) ); $whitelisted_plugins = []; foreach ( self::get_am_plugins() as $item ) { if ( ! empty( $item['path'] ) ) { $whitelisted_plugins[] = $item['path']; } if ( ! empty( $item['pro']['path'] ) ) { $whitelisted_plugins[] = $item['pro']['path']; } } if ( ! in_array( $plugin_slug, $whitelisted_plugins, true ) ) { wp_send_json_error( esc_html__( 'Could not activate the plugin. Plugin is not whitelisted.', 'wp-mail-smtp' ) ); } $activate = activate_plugins( $plugin_slug ); if ( ! is_wp_error( $activate ) ) { wp_send_json_success( esc_html__( 'Plugin activated.', 'wp-mail-smtp' ) ); } wp_send_json_error( $error ); } /** * Install & activate the given plugin. * * @since 2.9.0 */ public static function ajax_plugin_install() { // phpcs:ignore:Generic.Metrics.CyclomaticComplexity.TooHigh $error = esc_html__( 'Could not install the plugin.', 'wp-mail-smtp' ); // Check for permissions. if ( ! current_user_can( 'install_plugins' ) ) { wp_send_json_error( $error ); } if ( empty( $_POST['plugin'] ) ) { wp_send_json_error(); } $plugin_url = esc_url_raw( wp_unslash( $_POST['plugin'] ) ); if ( ! in_array( $plugin_url, wp_list_pluck( array_values( self::get_am_plugins() ), 'url' ) , true ) ) { wp_send_json_error( esc_html__( 'Could not install the plugin. Plugin is not whitelisted.', 'wp-mail-smtp' ) ); } // Set the current screen to avoid undefined notices. set_current_screen( 'wp-mail-smtp_page_wp-mail-smtp-about' ); // Prepare variables. $url = esc_url_raw( add_query_arg( [ 'page' => 'wp-mail-smtp-about', ], admin_url( 'admin.php' ) ) ); /* * The `request_filesystem_credentials` function will output a credentials form in case of failure. * We don't want that, since it will break AJAX response. So just hide output with a buffer. */ ob_start(); // phpcs:ignore WPForms.Formatting.EmptyLineAfterAssigmentVariables.AddEmptyLine $creds = request_filesystem_credentials( $url, '', false, false, null ); ob_end_clean(); // Check for file system permissions. if ( false === $creds ) { wp_send_json_error( $error ); } if ( ! WP_Filesystem( $creds ) ) { wp_send_json_error( $error ); } // Do not allow WordPress to search/download translations, as this will break JS output. remove_action( 'upgrader_process_complete', [ 'Language_Pack_Upgrader', 'async_upgrade' ], 20 ); // Import the plugin upgrader. Helpers::include_plugin_upgrader(); // Create the plugin upgrader with our custom skin. $installer = new Plugin_Upgrader( new PluginsInstallSkin() ); // Error check. if ( ! method_exists( $installer, 'install' ) ) { wp_send_json_error( $error ); } $installer->install( $plugin_url ); // Flush the cache and return the newly installed plugin basename. wp_cache_flush(); if ( $installer->plugin_info() ) { $plugin_basename = $installer->plugin_info(); if ( $plugin_basename === 'wpforms-lite/wpforms.php' ) { add_option( 'wpforms_installation_source', 'wp-mail-smtp-about-us' ); } // Activate the plugin silently. $activated = activate_plugin( $plugin_basename ); if ( ! is_wp_error( $activated ) ) { wp_send_json_success( [ 'msg' => esc_html__( 'Plugin installed & activated.', 'wp-mail-smtp' ), 'is_activated' => true, 'basename' => $plugin_basename, ] ); } else { wp_send_json_success( [ 'msg' => esc_html__( 'Plugin installed.', 'wp-mail-smtp' ), 'is_activated' => false, 'basename' => $plugin_basename, ] ); } } wp_send_json_error( $error ); } } Pages/AdditionalConnectionsTab.php 0000644 00000012477 15252526345 0013242 0 ustar 00 <?php namespace WPMailSMTP\Admin\Pages; use WPMailSMTP\Admin\PageAbstract; /** * Class AdditionalConnectionsTab is a placeholder for Pro additional connections feature. * Displays product education. * * @since 3.7.0 */ class AdditionalConnectionsTab extends PageAbstract { /** * Part of the slug of a tab. * * @since 3.7.0 * * @var string */ protected $slug = 'connections'; /** * Constructor. * * @since 3.7.0 * * @param PageAbstract $parent_page Parent page object. */ public function __construct( $parent_page = null ) { parent::__construct( $parent_page ); if ( wp_mail_smtp()->get_admin()->get_current_tab() === $this->slug && ! wp_mail_smtp()->is_pro() ) { $this->hooks(); } } /** * Link label of a tab. * * @since 3.7.0 * * @return string */ public function get_label() { return esc_html__( 'Additional Connections', 'wp-mail-smtp' ); } /** * Register hooks. * * @since 3.7.0 */ public function hooks() { add_action( 'wp_mail_smtp_admin_area_enqueue_assets', [ $this, 'enqueue_assets' ] ); } /** * Enqueue required JS and CSS. * * @since 3.7.0 */ public function enqueue_assets() { wp_enqueue_style( 'wp-mail-smtp-admin-lity', wp_mail_smtp()->assets_url . '/css/vendor/lity.min.css', [], '2.4.1' ); wp_enqueue_script( 'wp-mail-smtp-admin-lity', wp_mail_smtp()->assets_url . '/js/vendor/lity.min.js', [], '2.4.1' ); } /** * Output HTML of additional connections' education. * * @since 3.7.0 */ public function display() { $top_upgrade_button_url = wp_mail_smtp()->get_upgrade_link( [ 'medium' => 'Additional Connections Settings', 'content' => 'Upgrade to WP Mail SMTP Pro Button Top', ] ); $bottom_upgrade_button_url = wp_mail_smtp()->get_upgrade_link( [ 'medium' => 'Additional Connections Settings', 'content' => 'Upgrade to WP Mail SMTP Pro Button', ] ); ?> <div id="wp-mail-additional-connections-product-education" class="wp-mail-smtp-product-education"> <div class="wp-mail-smtp-product-education__row"> <h4 class="wp-mail-smtp-product-education__heading"> <?php esc_html_e( 'Additional Connections', 'wp-mail-smtp' ); ?> </h4> <p class="wp-mail-smtp-product-education__description"> <?php esc_html_e( 'Create additional connections to set a backup for your Primary Connection or to configure Smart Routing.', 'wp-mail-smtp' ); ?> </p> <a href="<?php echo esc_url( $top_upgrade_button_url ); ?>" target="_blank" rel="noopener noreferrer" class="wp-mail-smtp-product-education__upgrade-btn wp-mail-smtp-product-education__upgrade-btn--top wp-mail-smtp-btn wp-mail-smtp-btn-upgrade wp-mail-smtp-btn-orange"> <?php esc_html_e( 'Upgrade to WP Mail SMTP Pro', 'wp-mail-smtp' ); ?> </a> </div> <?php $this->display_education_screenshots(); $this->display_education_features_list(); ?> <a href="<?php echo esc_url( $bottom_upgrade_button_url ); ?>" target="_blank" rel="noopener noreferrer" class="wp-mail-smtp-product-education__upgrade-btn wp-mail-smtp-product-education__upgrade-btn--bottom wp-mail-smtp-btn wp-mail-smtp-btn-upgrade wp-mail-smtp-btn-orange"> <?php esc_html_e( 'Upgrade to WP Mail SMTP Pro', 'wp-mail-smtp' ); ?> </a> </div> <?php } /** * Output HTML of additional connections' education screenshots. * * @since 3.7.0 */ protected function display_education_screenshots() { $assets_url = wp_mail_smtp()->assets_url . '/images/additional-connections/'; $screenshots = [ [ 'url' => $assets_url . 'screenshot-01.png', 'url_thumbnail' => $assets_url . 'thumbnail-01.png', 'title' => __( 'Backup Connection', 'wp-mail-smtp' ), ], [ 'url' => $assets_url . 'screenshot-02.png', 'url_thumbnail' => $assets_url . 'thumbnail-02.png', 'title' => __( 'Smart Routing', 'wp-mail-smtp' ), ], ]; ?> <div class="wp-mail-smtp-product-education__row wp-mail-smtp-product-education__row--full-width"> <div class="wp-mail-smtp-product-education__screenshots wp-mail-smtp-product-education__screenshots--two"> <?php foreach ( $screenshots as $screenshot ) : ?> <div> <a href="<?php echo esc_url( $screenshot['url'] ); ?>" data-lity data-lity-desc="<?php echo esc_attr( $screenshot['title'] ); ?>"> <img src="<?php echo esc_url( $screenshot['url_thumbnail'] ); ?>" alt="<?php esc_attr( $screenshot['title'] ); ?>"> </a> <span><?php echo esc_html( $screenshot['title'] ); ?></span> </div> <?php endforeach; ?> </div> </div> <?php } /** * Output HTML of additional connections' education features list. * * @since 3.7.0 */ protected function display_education_features_list() { ?> <div class="wp-mail-smtp-product-education__row wp-mail-smtp-product-education__row--full-width"> <div class="wp-mail-smtp-product-education__list"> <h4><?php esc_html_e( 'With additional connections you can...', 'wp-mail-smtp' ); ?></h4> <div> <ul> <li><?php esc_html_e( 'Set a Backup Connection', 'wp-mail-smtp' ); ?></li> </ul> <ul> <li><?php esc_html_e( 'Use mailers for different purposes', 'wp-mail-smtp' ); ?></li> </ul> <ul> <li><?php esc_html_e( 'Create advanced routing rules', 'wp-mail-smtp' ); ?></li> </ul> </div> </div> </div> <?php } } Pages/EmailReportsTab.php 0000644 00000012456 15252526345 0011372 0 ustar 00 <?php namespace WPMailSMTP\Admin\Pages; use WPMailSMTP\Admin\PageAbstract; /** * Class EmailTrackingReportsTab is a placeholder for Pro email tracking reports. * Displays product education. * * @since 3.0.0 */ class EmailReportsTab extends PageAbstract { /** * Part of the slug of a tab. * * @since 3.0.0 * * @var string */ protected $slug = 'reports'; /** * Tab priority. * * @since 3.0.0 * * @var int */ protected $priority = 10; /** * Link label of a tab. * * @since 3.0.0 * * @return string */ public function get_label() { return esc_html__( 'Email Reports', 'wp-mail-smtp' ); } /** * Title of a tab. * * @since 3.0.0 * * @return string */ public function get_title() { return $this->get_label(); } /** * Register hooks. * * @since 3.0.0 */ public function hooks() { add_action( 'wp_mail_smtp_admin_area_enqueue_assets', [ $this, 'enqueue_assets' ] ); } /** * Enqueue required JS and CSS. * * @since 3.0.0 */ public function enqueue_assets() { wp_enqueue_style( 'wp-mail-smtp-admin-lity', wp_mail_smtp()->assets_url . '/css/vendor/lity.min.css', [], '2.4.1' ); wp_enqueue_script( 'wp-mail-smtp-admin-lity', wp_mail_smtp()->assets_url . '/js/vendor/lity.min.js', [], '2.4.1', false ); } /** * Output HTML of the email reports education. * * @since 3.0.0 */ public function display() { $top_upgrade_button_url = wp_mail_smtp()->get_upgrade_link( [ 'medium' => 'email-reports', 'content' => 'upgrade-to-wp-mail-smtp-pro-button-link-top', ] ); $bottom_upgrade_button_url = wp_mail_smtp()->get_upgrade_link( [ 'medium' => 'email-reports', 'content' => 'upgrade-to-wp-mail-smtp-pro-button-link', ] ); $assets_url = wp_mail_smtp()->assets_url . '/images/email-reports/'; $screenshots = [ [ 'url' => $assets_url . 'screenshot-01.png', 'url_thumbnail' => $assets_url . 'thumbnail-01.png', 'title' => __( 'Stats at a Glance', 'wp-mail-smtp' ), ], [ 'url' => $assets_url . 'screenshot-02.png', 'url_thumbnail' => $assets_url . 'thumbnail-02.png', 'title' => __( 'Detailed Stats by Subject Line', 'wp-mail-smtp' ), ], [ 'url' => $assets_url . 'screenshot-03.png', 'url_thumbnail' => $assets_url . 'thumbnail-03.png', 'title' => __( 'Weekly Email Report', 'wp-mail-smtp' ), ], ]; ?> <div id="wp-mail-smtp-email-reports-product-education" class="wp-mail-smtp-product-education"> <div class="wp-mail-smtp-product-education__row"> <p class="wp-mail-smtp-product-education__description"> <?php esc_html_e( 'Email reports make it easy to track deliverability and engagement at-a-glance. Your open and click-through rates are grouped by subject line, making it easy to review the performance of campaigns or notifications. The report also displays Sent and Failed emails each week so you spot any issues quickly. When you upgrade, we\'ll also add an email report chart right in your WordPress dashboard.', 'wp-mail-smtp' ); ?> </p> <a href="<?php echo esc_url( $top_upgrade_button_url ); ?>" target="_blank" rel="noopener noreferrer" class="wp-mail-smtp-product-education__upgrade-btn wp-mail-smtp-product-education__upgrade-btn--top wp-mail-smtp-btn wp-mail-smtp-btn-upgrade wp-mail-smtp-btn-orange"> <?php esc_html_e( 'Upgrade to WP Mail SMTP Pro', 'wp-mail-smtp' ); ?> </a> </div> <div class="wp-mail-smtp-product-education__row"> <div class="wp-mail-smtp-product-education__screenshots wp-mail-smtp-product-education__screenshots--three"> <?php foreach ( $screenshots as $screenshot ) : ?> <div> <a href="<?php echo esc_url( $screenshot['url'] ); ?>" data-lity data-lity-desc="<?php echo esc_attr( $screenshot['title'] ); ?>"> <img src="<?php echo esc_url( $screenshot['url_thumbnail'] ); ?>" alt="<?php esc_attr( $screenshot['title'] ); ?>"> </a> <span><?php echo esc_html( $screenshot['title'] ); ?></span> </div> <?php endforeach; ?> </div> </div> <div class="wp-mail-smtp-product-education__row"> <div class="wp-mail-smtp-product-education__list"> <h4><?php esc_html_e( 'Unlock these awesome reporting features:', 'wp-mail-smtp' ); ?></h4> <div> <ul> <li><?php esc_html_e( 'Get weekly deliverability reports', 'wp-mail-smtp' ); ?></li> <li><?php esc_html_e( 'View stats grouped by subject line', 'wp-mail-smtp' ); ?></li> </ul> <ul> <li><?php esc_html_e( 'Track total emails sent each week', 'wp-mail-smtp' ); ?></li> <li><?php esc_html_e( 'Measure open rate and click through rates', 'wp-mail-smtp' ); ?></li> </ul> <ul> <li><?php esc_html_e( 'Spot failed emails quickly', 'wp-mail-smtp' ); ?></li> <li><?php esc_html_e( 'See email report graphs in WordPress', 'wp-mail-smtp' ); ?></li> </ul> </div> </div> </div> <a href="<?php echo esc_url( $bottom_upgrade_button_url ); ?>" target="_blank" rel="noopener noreferrer" class="wp-mail-smtp-product-education__upgrade-btn wp-mail-smtp-product-education__upgrade-btn--bottom wp-mail-smtp-btn wp-mail-smtp-btn-upgrade wp-mail-smtp-btn-orange"> <?php esc_html_e( 'Upgrade to WP Mail SMTP Pro', 'wp-mail-smtp' ); ?> </a> </div> <?php } } Pages/SmartRoutingTab.php 0000644 00000032156 15252526345 0011421 0 ustar 00 <?php namespace WPMailSMTP\Admin\Pages; use WPMailSMTP\Admin\PageAbstract; use WPMailSMTP\Helpers\UI; use WPMailSMTP\WP; /** * Class SmartRoutingTab is a placeholder for Pro smart routing feature. * Displays product education. * * @since 3.7.0 */ class SmartRoutingTab extends PageAbstract { /** * Part of the slug of a tab. * * @since 3.7.0 * * @var string */ protected $slug = 'routing'; /** * Constructor. * * @since 3.7.0 * * @param PageAbstract $parent_page Parent page object. */ public function __construct( $parent_page = null ) { parent::__construct( $parent_page ); if ( wp_mail_smtp()->get_admin()->get_current_tab() === $this->slug && ! wp_mail_smtp()->is_pro() ) { $this->hooks(); } } /** * Link label of a tab. * * @since 3.7.0 * * @return string */ public function get_label() { return esc_html__( 'Smart Routing', 'wp-mail-smtp' ); } /** * Register hooks. * * @since 3.7.0 */ public function hooks() { add_action( 'wp_mail_smtp_admin_area_enqueue_assets', [ $this, 'enqueue_assets' ] ); } /** * Enqueue required JS and CSS. * * @since 3.7.0 */ public function enqueue_assets() { wp_enqueue_style( 'wp-mail-smtp-smart-routing', wp_mail_smtp()->plugin_url . '/assets/css/smtp-smart-routing.min.css', [], WPMS_PLUGIN_VER ); } /** * Output HTML of smart routing education. * * @since 3.7.0 */ public function display() { $top_upgrade_button_url = wp_mail_smtp()->get_upgrade_link( [ 'medium' => 'Smart Routing Settings', 'content' => 'Upgrade to WP Mail SMTP Pro Button Top', ] ); $bottom_upgrade_button_url = wp_mail_smtp()->get_upgrade_link( [ 'medium' => 'Smart Routing Settings', 'content' => 'Upgrade to WP Mail SMTP Pro Button', ] ); ?> <div id="wp-mail-smtp-smart-routing-product-education" class="wp-mail-smtp-product-education"> <div class="wp-mail-smtp-product-education__row wp-mail-smtp-product-education__row--no-border"> <h4 class="wp-mail-smtp-product-education__heading"> <?php esc_html_e( 'Smart Routing', 'wp-mail-smtp' ); ?> </h4> <p class="wp-mail-smtp-product-education__description"> <?php esc_html_e( 'Send emails from different additional connections based on your configured conditions. Emails that do not match any of the conditions below will be sent via your Primary Connection.', 'wp-mail-smtp' ); ?> </p> <a href="<?php echo esc_url( $top_upgrade_button_url ); ?>" target="_blank" rel="noopener noreferrer" class="wp-mail-smtp-product-education__upgrade-btn wp-mail-smtp-product-education__upgrade-btn--top wp-mail-smtp-btn wp-mail-smtp-btn-upgrade wp-mail-smtp-btn-orange"> <?php esc_html_e( 'Upgrade to WP Mail SMTP Pro', 'wp-mail-smtp' ); ?> </a> </div> <div class="wp-mail-smtp-product-education__row wp-mail-smtp-product-education__row--inactive wp-mail-smtp-product-education__row--no-border wp-mail-smtp-product-education__row--no-padding wp-mail-smtp-product-education__row--full-width"> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-no-border"> <?php UI::toggle( [ 'label' => esc_html__( 'Enable Smart Routing', 'wp-mail-smtp' ), 'class' => 'wp-mail-smtp-smart-routing-toggle', 'checked' => true, ] ); ?> </div> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-no-border wp-mail-smtp-setting-row-no-padding"> <div class="wp-mail-smtp-smart-routing-routes"> <div class="wp-mail-smtp-smart-routing-route"> <div class="wp-mail-smtp-smart-routing-route__header"> <span><?php esc_html_e( 'Send with', 'wp-mail-smtp' ); ?></span> <select class="wp-mail-smtp-smart-routing-route__connection"> <option><?php esc_html_e( 'WooCommerce Emails (SendLayer)', 'wp-mail-smtp' ); ?></option> </select> <span><?php esc_html_e( 'if the following conditions are met...', 'wp-mail-smtp' ); ?></span> <div class="wp-mail-smtp-smart-routing-route__actions"> <div class="wp-mail-smtp-smart-routing-route__order"> <button class="wp-mail-smtp-smart-routing-route__order-btn wp-mail-smtp-smart-routing-route__order-btn--up"> <img src="<?php echo esc_url( wp_mail_smtp()->assets_url . '/images/icons/arrow-up.svg' ); ?>" alt="<?php esc_attr_e( 'Arrow Up', 'wp-mail-smtp' ); ?>"> </button> <button class="wp-mail-smtp-smart-routing-route__order-btn wp-mail-smtp-smart-routing-route__order-btn--down"> <img src="<?php echo esc_url( wp_mail_smtp()->assets_url . '/images/icons/arrow-up.svg' ); ?>" alt="<?php esc_attr_e( 'Arrow Down', 'wp-mail-smtp' ); ?>"> </button> </div> <button class="wp-mail-smtp-smart-routing-route__delete"> <i class="dashicons dashicons-trash"></i> </button> </div> </div> <div class="wp-mail-smtp-smart-routing-route__main"> <div class="wp-mail-smtp-conditional"> <div class="wp-mail-smtp-conditional__group"> <table> <tbody> <tr class="wp-mail-smtp-conditional__row"> <td class="wp-mail-smtp-conditional__property-col"> <select> <option><?php esc_html_e( 'Subject', 'wp-mail-smtp' ); ?></option> </select> </td> <td class="wp-mail-smtp-conditional__operator-col"> <select class="wp-mail-smtp-conditional__operator"> <option><?php esc_html_e( 'Contains', 'wp-mail-smtp' ); ?></option> </select> </td> <td class="wp-mail-smtp-conditional__value-col"> <input type="text" value="<?php esc_html_e( 'Order', 'wp-mail-smtp' ); ?>" class="wp-mail-smtp-conditional__value"> </td> <td class="wp-mail-smtp-conditional__actions"> <button class="wp-mail-smtp-conditional__add-rule wp-mail-smtp-btn wp-mail-smtp-btn-md wp-mail-smtp-btn-grey"> <?php esc_html_e( 'And', 'wp-mail-smtp' ); ?> </button> <button class="wp-mail-smtp-conditional__delete-rule"> <i class="dashicons dashicons-trash" aria-hidden="true"></i> </button> </td> </tr> <tr class="wp-mail-smtp-conditional__row"> <td class="wp-mail-smtp-conditional__property-col"> <select class="wp-mail-smtp-conditional__property"> <option><?php esc_html_e( 'From Email', 'wp-mail-smtp' ); ?></option> </select> </td> <td class="wp-mail-smtp-conditional__operator-col"> <select class="wp-mail-smtp-conditional__operator"> <option><?php esc_html_e( 'Is', 'wp-mail-smtp' ); ?></option> </select> </td> <td class="wp-mail-smtp-conditional__value-col"> <input type="text" value="shop@wpmailsmtp.com" class="wp-mail-smtp-conditional__value"> </td> <td class="wp-mail-smtp-conditional__actions"> <button class="wp-mail-smtp-conditional__add-rule wp-mail-smtp-btn wp-mail-smtp-btn-md wp-mail-smtp-btn-grey"> <?php esc_html_e( 'And', 'wp-mail-smtp' ); ?> </button> <button class="wp-mail-smtp-conditional__delete-rule"> <i class="dashicons dashicons-trash" aria-hidden="true"></i> </button> </td> </tr> </tbody> </table> <div class="wp-mail-smtp-conditional__group-delimiter"><?php esc_html_e( 'or', 'wp-mail-smtp' ); ?></div> </div> <div class="wp-mail-smtp-conditional__group"> <table> <tbody> <tr class="wp-mail-smtp-conditional__row"> <td class="wp-mail-smtp-conditional__property-col"> <select class="wp-mail-smtp-conditional__property"> <option><?php esc_html_e( 'From Email', 'wp-mail-smtp' ); ?></option> </select> </td> <td class="wp-mail-smtp-conditional__operator-col"> <select class="wp-mail-smtp-conditional__operator"> <option><?php esc_html_e( 'Is', 'wp-mail-smtp' ); ?></option> </select> </td> <td class="wp-mail-smtp-conditional__value-col"> <input type="text" value="returns@wpmailsmtp.com" class="wp-mail-smtp-conditional__value"> </td> <td class="wp-mail-smtp-conditional__actions"> <button class="wp-mail-smtp-conditional__add-rule wp-mail-smtp-btn wp-mail-smtp-btn-md wp-mail-smtp-btn-grey"> <?php esc_html_e( 'And', 'wp-mail-smtp' ); ?> </button> <button class="wp-mail-smtp-conditional__delete-rule"> <i class="dashicons dashicons-trash" aria-hidden="true"></i> </button> </td> </tr> </tbody> </table> <div class="wp-mail-smtp-conditional__group-delimiter"><?php esc_html_e( 'or', 'wp-mail-smtp' ); ?></div> </div> <button class="wp-mail-smtp-conditional__add-group wp-mail-smtp-btn wp-mail-smtp-btn-md wp-mail-smtp-btn-grey"> <?php esc_html_e( 'Add New Group', 'wp-mail-smtp' ); ?> </button> </div> </div> </div> <div class="wp-mail-smtp-smart-routing-route"> <div class="wp-mail-smtp-smart-routing-route__header"> <span><?php esc_html_e( 'Send with', 'wp-mail-smtp' ); ?></span> <select class="wp-mail-smtp-smart-routing-route__connection"> <option><?php esc_html_e( 'Contact Emails (SMTP.com)', 'wp-mail-smtp' ); ?></option> </select> <span><?php esc_html_e( 'if the following conditions are met...', 'wp-mail-smtp' ); ?></span> <div class="wp-mail-smtp-smart-routing-route__actions"> <div class="wp-mail-smtp-smart-routing-route__order"> <button class="wp-mail-smtp-smart-routing-route__order-btn wp-mail-smtp-smart-routing-route__order-btn--up"> <img src="<?php echo esc_url( wp_mail_smtp()->assets_url . '/images/icons/arrow-up.svg' ); ?>" alt="<?php esc_attr_e( 'Arrow Up', 'wp-mail-smtp' ); ?>"> </button> <button class="wp-mail-smtp-smart-routing-route__order-btn wp-mail-smtp-smart-routing-route__order-btn--down"> <img src="<?php echo esc_url( wp_mail_smtp()->assets_url . '/images/icons/arrow-up.svg' ); ?>" alt="<?php esc_attr_e( 'Arrow Down', 'wp-mail-smtp' ); ?>"> </button> </div> <button class="wp-mail-smtp-smart-routing-route__delete"> <i class="dashicons dashicons-trash"></i> </button> </div> </div> <div class="wp-mail-smtp-smart-routing-route__main"> <div class="wp-mail-smtp-conditional"> <div class="wp-mail-smtp-conditional__group"> <table> <tbody> <tr class="wp-mail-smtp-conditional__row"> <td class="wp-mail-smtp-conditional__property-col"> <select> <option><?php esc_html_e( 'Initiator', 'wp-mail-smtp' ); ?></option> </select> </td> <td class="wp-mail-smtp-conditional__operator-col"> <select class="wp-mail-smtp-conditional__operator"> <option><?php esc_html_e( 'Is', 'wp-mail-smtp' ); ?></option> </select> </td> <td class="wp-mail-smtp-conditional__value-col"> <input type="text" value="<?php esc_html_e( 'WPForms', 'wp-mail-smtp' ); ?>" class="wp-mail-smtp-conditional__value"> </td> <td class="wp-mail-smtp-conditional__actions"> <button class="wp-mail-smtp-conditional__add-rule wp-mail-smtp-btn wp-mail-smtp-btn-md wp-mail-smtp-btn-grey"> <?php esc_html_e( 'And', 'wp-mail-smtp' ); ?> </button> <button class="wp-mail-smtp-conditional__delete-rule"> <i class="dashicons dashicons-trash" aria-hidden="true"></i> </button> </td> </tr> </tbody> </table> <div class="wp-mail-smtp-conditional__group-delimiter"><?php esc_html_e( 'or', 'wp-mail-smtp' ); ?></div> </div> <button class="wp-mail-smtp-conditional__add-group wp-mail-smtp-btn wp-mail-smtp-btn-md wp-mail-smtp-btn-grey"> <?php esc_html_e( 'Add New Group', 'wp-mail-smtp' ); ?> </button> </div> </div> </div> </div> <div class="wp-mail-smtp-smart-routing-routes-note"> <img src="<?php echo esc_url( wp_mail_smtp()->assets_url . '/images/icons/lightbulb.svg' ); ?>" alt="<?php esc_attr_e( 'Light bulb icon', 'wp-mail-smtp' ); ?>"> <?php esc_html_e( 'Friendly reminder, your Primary Connection will be used for all emails that do not match the conditions above.', 'wp-mail-smtp' ); ?> </div> </div> </div> <a href="<?php echo esc_url( $bottom_upgrade_button_url ); ?>" target="_blank" rel="noopener noreferrer" class="wp-mail-smtp-product-education__upgrade-btn wp-mail-smtp-product-education__upgrade-btn--bottom wp-mail-smtp-btn wp-mail-smtp-btn-upgrade wp-mail-smtp-btn-orange"> <?php esc_html_e( 'Upgrade to WP Mail SMTP Pro', 'wp-mail-smtp' ); ?> </a> </div> <?php } } Pages/Tools.php 0000644 00000001257 15252526345 0007432 0 ustar 00 <?php namespace WPMailSMTP\Admin\Pages; use WPMailSMTP\Admin\ParentPageAbstract; /** * Class Tools. * * @since 2.8.0 */ class Tools extends ParentPageAbstract { /** * Slug of a page. * * @since 2.8.0 * * @var string */ protected $slug = 'tools'; /** * Page default tab slug. * * @since 2.8.0 * * @var string */ protected $default_tab = 'test'; /** * Link label of a page. * * @since 2.8.0 * * @return string */ public function get_label() { return esc_html__( 'Tools', 'wp-mail-smtp' ); } /** * Title of a page. * * @since 2.8.0 * * @return string */ public function get_title() { return $this->get_label(); } } Pages/ControlTab.php 0000644 00000032164 15252526345 0010402 0 ustar 00 <?php namespace WPMailSMTP\Admin\Pages; use WPMailSMTP\Admin\PageAbstract; use WPMailSMTP\Helpers\UI; use WPMailSMTP\WP; /** * Class ControlTab is a placeholder for Pro Email Control tab settings. * Displays an upsell. * * @since 1.6.0 */ class ControlTab extends PageAbstract { /** * Slug of a tab. * * @since 1.6.0 * * @var string */ protected $slug = 'control'; /** * Link label of a tab. * * @since 1.6.0 * * @return string */ public function get_label() { return esc_html__( 'Email Controls', 'wp-mail-smtp' ); } /** * Title of a tab. * * @since 1.6.0 * * @return string */ public function get_title() { return $this->get_label(); } /** * Get the list of all available emails that we can manage. * * @see https://github.com/johnbillion/wp_mail Apr 12th 2019. * * @since 3.1.0 * * @return array */ public static function get_controls() { return [ 'comments' => [ 'title' => esc_html__( 'Comments', 'wp-mail-smtp' ), 'emails' => [ 'dis_comments_awaiting_moderation' => [ 'label' => esc_html__( 'Awaiting Moderation', 'wp-mail-smtp' ), 'desc' => esc_html__( 'Comment is awaiting moderation. Sent to the site admin and post author if they can edit comments.', 'wp-mail-smtp' ), ], 'dis_comments_published' => [ 'label' => esc_html__( 'Published', 'wp-mail-smtp' ), 'desc' => esc_html__( 'Comment has been published. Sent to the post author.', 'wp-mail-smtp' ), ], ], ], 'admin_email' => [ 'title' => esc_html__( 'Change of Admin Email', 'wp-mail-smtp' ), 'emails' => [ 'dis_admin_email_attempt' => [ 'label' => esc_html__( 'Site Admin Email Change Attempt', 'wp-mail-smtp' ), 'desc' => esc_html__( 'Change of site admin email address was attempted. Sent to the proposed new email address.', 'wp-mail-smtp' ), ], 'dis_admin_email_changed' => [ 'label' => esc_html__( 'Site Admin Email Changed', 'wp-mail-smtp' ), 'desc' => esc_html__( 'Site admin email address was changed. Sent to the old site admin email address.', 'wp-mail-smtp' ), ], 'dis_admin_email_network_attempt' => [ 'label' => esc_html__( 'Network Admin Email Change Attempt', 'wp-mail-smtp' ), 'desc' => esc_html__( 'Change of network admin email address was attempted. Sent to the proposed new email address.', 'wp-mail-smtp' ), ], 'dis_admin_email_network_changed' => [ 'label' => esc_html__( 'Network Admin Email Changed', 'wp-mail-smtp' ), 'desc' => esc_html__( 'Network admin email address was changed. Sent to the old network admin email address.', 'wp-mail-smtp' ), ], ], ], 'user_details' => [ 'title' => esc_html__( 'Change of User Email or Password', 'wp-mail-smtp' ), 'emails' => [ 'dis_user_details_password_reset_request' => [ 'label' => esc_html__( 'Reset Password Request', 'wp-mail-smtp' ), 'desc' => esc_html__( 'User requested a password reset via "Lost your password?". Sent to the user.', 'wp-mail-smtp' ), ], 'dis_user_details_password_reset' => [ 'label' => esc_html__( 'Password Reset Successfully', 'wp-mail-smtp' ), 'desc' => esc_html__( 'User reset their password from the password reset link. Sent to the site admin.', 'wp-mail-smtp' ), ], 'dis_user_details_password_changed' => [ 'label' => esc_html__( 'Password Changed', 'wp-mail-smtp' ), 'desc' => esc_html__( 'User changed their password. Sent to the user.', 'wp-mail-smtp' ), ], 'dis_user_details_email_change_attempt' => [ 'label' => esc_html__( 'Email Change Attempt', 'wp-mail-smtp' ), 'desc' => esc_html__( 'User attempted to change their email address. Sent to the proposed new email address.', 'wp-mail-smtp' ), ], 'dis_user_details_email_changed' => [ 'label' => esc_html__( 'Email Changed', 'wp-mail-smtp' ), 'desc' => esc_html__( 'User changed their email address. Sent to the user.', 'wp-mail-smtp' ), ], ], ], 'personal_data' => [ 'title' => esc_html__( 'Personal Data Requests', 'wp-mail-smtp' ), 'emails' => [ 'dis_personal_data_user_confirmed' => [ 'label' => esc_html__( 'User Confirmed Export / Erasure Request', 'wp-mail-smtp' ), 'desc' => esc_html__( 'User clicked a confirmation link in personal data export or erasure request email. Sent to the site or network admin.', 'wp-mail-smtp' ), ], 'dis_personal_data_erased_data' => [ 'label' => esc_html__( 'Admin Erased Data', 'wp-mail-smtp' ), 'desc' => esc_html__( 'Site admin clicked "Erase Personal Data" button next to a confirmed data erasure request. Sent to the requester email address.', 'wp-mail-smtp' ), ], 'dis_personal_data_sent_export_link' => [ 'label' => esc_html__( 'Admin Sent Link to Export Data', 'wp-mail-smtp' ), 'desc' => esc_html__( 'Site admin clicked "Email Data" button next to a confirmed data export request. Sent to the requester email address.', 'wp-mail-smtp' ) . '<br>' . '<strong>' . esc_html__( 'Disabling this option will block users from being able to export their personal data, as they will not receive an email with a link.', 'wp-mail-smtp' ) . '</strong>', ], ], ], 'auto_updates' => [ 'title' => esc_html__( 'Automatic Updates', 'wp-mail-smtp' ), 'emails' => [ 'dis_auto_updates_plugin_status' => [ 'label' => esc_html__( 'Plugin Status', 'wp-mail-smtp' ), 'desc' => esc_html__( 'Completion or failure of a background automatic plugin update. Sent to the site or network admin.', 'wp-mail-smtp' ), ], 'dis_auto_updates_theme_status' => [ 'label' => esc_html__( 'Theme Status', 'wp-mail-smtp' ), 'desc' => esc_html__( 'Completion or failure of a background automatic theme update. Sent to the site or network admin.', 'wp-mail-smtp' ), ], 'dis_auto_updates_status' => [ 'label' => esc_html__( 'WP Core Status', 'wp-mail-smtp' ), 'desc' => esc_html__( 'Completion or failure of a background automatic core update. Sent to the site or network admin.', 'wp-mail-smtp' ), ], 'dis_auto_updates_full_log' => [ 'label' => esc_html__( 'Full Log', 'wp-mail-smtp' ), 'desc' => esc_html__( 'Full log of background update results which includes information about WordPress core, plugins, themes, and translations updates. Only sent when you are using a development version of WordPress. Sent to the site or network admin.', 'wp-mail-smtp' ), ], ], ], 'new_user' => [ 'title' => esc_html__( 'New User', 'wp-mail-smtp' ), 'emails' => [ 'dis_new_user_created_to_admin' => [ 'label' => esc_html__( 'Created (Admin)', 'wp-mail-smtp' ), 'desc' => esc_html__( 'A new user was created. Sent to the site admin.', 'wp-mail-smtp' ), ], 'dis_new_user_created_to_user' => [ 'label' => esc_html__( 'Created (User)', 'wp-mail-smtp' ), 'desc' => esc_html__( 'A new user was created. Sent to the new user.', 'wp-mail-smtp' ), ], 'dis_new_user_invited_to_site_network' => [ 'label' => esc_html__( 'Invited To Site', 'wp-mail-smtp' ), 'desc' => esc_html__( 'A new user was invited to a site from Users -> Add New -> Add New User. Sent to the invited user.', 'wp-mail-smtp' ), ], 'dis_new_user_created_network' => [ 'label' => esc_html__( 'Created On Site', 'wp-mail-smtp' ), 'desc' => esc_html__( 'A new user account was created. Sent to Network Admin.', 'wp-mail-smtp' ), ], 'dis_new_user_added_activated_network' => [ 'label' => esc_html__( 'Added / Activated on Site', 'wp-mail-smtp' ), 'desc' => esc_html__( 'A user has been added, or their account activation has been successful. Sent to the user, that has been added/activated.', 'wp-mail-smtp' ), ], ], ], 'network_new_site' => [ 'title' => esc_html__( 'New Site', 'wp-mail-smtp' ), 'emails' => [ 'dis_new_site_user_registered_site_network' => [ 'label' => esc_html__( 'User Created Site', 'wp-mail-smtp' ), 'desc' => esc_html__( 'User registered for a new site. Sent to the site admin.', 'wp-mail-smtp' ), ], 'dis_new_site_user_added_activated_site_in_network_to_admin' => [ 'label' => esc_html__( 'Network Admin: User Activated / Added Site', 'wp-mail-smtp' ), 'desc' => esc_html__( 'User activated their new site, or site was added from Network Admin -> Sites -> Add New. Sent to Network Admin.', 'wp-mail-smtp' ), ], 'dis_new_site_user_added_activated_site_in_network_to_site' => [ 'label' => esc_html__( 'Site Admin: Activated / Added Site', 'wp-mail-smtp' ), 'desc' => esc_html__( 'User activated their new site, or site was added from Network Admin -> Sites -> Add New. Sent to Site Admin.', 'wp-mail-smtp' ), ], ], ], ]; } /** * Output HTML of the email controls settings preview. * * @since 1.6.0 * @since 2.1.0 Replaced images with SVGs. * @since 3.1.0 Updated layout to inactive settings preview. */ public function display() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh $top_upgrade_button_url = add_query_arg( [ 'discount' => 'LITEUPGRADE' ], wp_mail_smtp()->get_upgrade_link( [ 'medium' => 'Email Controls', 'content' => 'Upgrade to WP Mail SMTP Pro Button Top', ] ) ); $bottom_upgrade_button_url = add_query_arg( [ 'discount' => 'LITEUPGRADE' ], wp_mail_smtp()->get_upgrade_link( [ 'medium' => 'Email Controls', 'content' => 'Upgrade to WP Mail SMTP Pro Button', ] ) ); ?> <div id="wp-mail-smtp-email-controls-product-education" class="wp-mail-smtp-product-education"> <div class="wp-mail-smtp-product-education__row"> <h4 class="wp-mail-smtp-product-education__heading"> <?php esc_html_e( 'Email Controls', 'wp-mail-smtp' ); ?> </h4> <p class="wp-mail-smtp-product-education__description"> <?php esc_html_e( 'Email controls allow you to manage the automatic notifications you receive from your WordPress website. With the flick of a switch, you can reduce inbox clutter and focus on the alerts that matter the most. It\'s easy to disable emails about comments, email or password changes, WordPress updates, user registrations, and personal data requests.', 'wp-mail-smtp' ); ?> </p> <a href="<?php echo esc_url( $top_upgrade_button_url ); ?>" target="_blank" rel="noopener noreferrer" class="wp-mail-smtp-product-education__upgrade-btn wp-mail-smtp-product-education__upgrade-btn--top wp-mail-smtp-btn wp-mail-smtp-btn-upgrade wp-mail-smtp-btn-orange"> <?php esc_html_e( 'Upgrade to WP Mail SMTP Pro', 'wp-mail-smtp' ); ?> </a> </div> <div class="wp-mail-smtp-product-education__row wp-mail-smtp-product-education__row--inactive"> <?php foreach ( static::get_controls() as $section_id => $section ) : if ( empty( $section['emails'] ) ) { continue; } if ( $this->is_it_for_multisite( sanitize_key( $section_id ) ) && ! WP::use_global_plugin_settings() ) { continue; } ?> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-content wp-mail-smtp-clear section-heading no-desc"> <div class="wp-mail-smtp-setting-field"> <h5><?php echo esc_html( $section['title'] ); ?></h5> </div> </div> <?php foreach ( $section['emails'] as $email_id => $email ) : $email_id = sanitize_key( $email_id ); if ( empty( $email_id ) || empty( $email['label'] ) ) { continue; } if ( $this->is_it_for_multisite( sanitize_key( $email_id ) ) && ! WP::use_global_plugin_settings() ) { continue; } ?> <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-checkbox-toggle wp-mail-smtp-clear"> <div class="wp-mail-smtp-setting-label"> <label><?php echo esc_html( $email['label'] ); ?></label> </div> <div class="wp-mail-smtp-setting-field"> <?php UI::toggle( [ 'checked' => true ] ); ?> <?php if ( ! empty( $email['desc'] ) ) : ?> <p class="desc"> <?php echo $email['desc']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> </p> <?php endif; ?> </div> </div> <?php endforeach; ?> <?php endforeach; ?> </div> <a href="<?php echo esc_url( $bottom_upgrade_button_url ); ?>" target="_blank" rel="noopener noreferrer" class="wp-mail-smtp-product-education__upgrade-btn wp-mail-smtp-product-education__upgrade-btn--bottom wp-mail-smtp-btn wp-mail-smtp-btn-upgrade wp-mail-smtp-btn-orange"> <?php esc_html_e( 'Upgrade to WP Mail SMTP Pro', 'wp-mail-smtp' ); ?> </a> </div> <?php } /** * Whether this key dedicated to MultiSite environment. * * @since 1.5.0 * * @param string $key Email unique key. * * @return bool */ protected function is_it_for_multisite( $key ) { return strpos( $key, 'network' ) !== false; } /** * Not used as we display an upsell. * * @since 1.6.0 * * @param array $data Post data specific for the plugin. */ public function process_post( $data ) { } } Pages/LogsTab.php 0000644 00000013473 15252526345 0007670 0 ustar 00 <?php namespace WPMailSMTP\Admin\Pages; use WPMailSMTP\Admin\PageAbstract; use WPMailSMTP\Admin\ParentPageAbstract; /** * Class LogsTab is a placeholder for Lite users and redirects them to Email Log page. * * @since 1.6.0 */ class LogsTab extends PageAbstract { /** * Part of the slug of a tab. * * @since 1.6.0 * * @var string */ protected $slug = 'logs'; /** * Constructor. * * @since 3.0.0 * * @param ParentPageAbstract $parent_page Tab parent page. */ public function __construct( $parent_page = null ) { parent::__construct( $parent_page ); $current_tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( wp_mail_smtp()->get_admin()->is_admin_page() && $current_tab === 'logs' ) { $this->hooks(); } } /** * Link label of a tab. * * @since 1.6.0 * * @return string */ public function get_label() { return esc_html__( 'Email Log', 'wp-mail-smtp' ); } /** * Title of a tab. * * @since 1.6.0 * * @return string */ public function get_title() { return $this->get_label(); } /** * Register hooks. * * @since 3.0.0 */ public function hooks() { add_action( 'wp_mail_smtp_admin_area_enqueue_assets', [ $this, 'enqueue_assets' ] ); } /** * Enqueue required JS and CSS. * * @since 3.0.0 */ public function enqueue_assets() { wp_enqueue_style( 'wp-mail-smtp-admin-lity', wp_mail_smtp()->assets_url . '/css/vendor/lity.min.css', [], '2.4.1' ); wp_enqueue_script( 'wp-mail-smtp-admin-lity', wp_mail_smtp()->assets_url . '/js/vendor/lity.min.js', [], '2.4.1', false ); } /** * Display the upsell content for the Email Log feature. * * @since 1.6.0 * @since 2.1.0 Moved the display content from the email log page (WP admin menu "Email Log" page). */ public function display() { $top_upgrade_button_url = add_query_arg( [ 'discount' => 'LITEUPGRADE' ], wp_mail_smtp()->get_upgrade_link( [ 'medium' => 'logs', 'content' => 'Upgrade to Pro Button Top', ] ) ); $bottom_upgrade_button_url = add_query_arg( [ 'discount' => 'LITEUPGRADE' ], wp_mail_smtp()->get_upgrade_link( [ 'medium' => 'logs', 'content' => 'Upgrade to Pro Button', ] ) ); $assets_url = wp_mail_smtp()->assets_url . '/images/logs/'; $screenshots = [ [ 'url' => $assets_url . 'archive.png', 'url_thumbnail' => $assets_url . 'archive-thumbnail.png', 'title' => __( 'Email Log Index', 'wp-mail-smtp' ), ], [ 'url' => $assets_url . 'single.png', 'url_thumbnail' => $assets_url . 'single-thumbnail.png', 'title' => __( 'Individual Email Log', 'wp-mail-smtp' ), ], ]; ?> <div id="wp-mail-smtp-email-logs-product-education" class="wp-mail-smtp-product-education"> <div class="wp-mail-smtp-product-education__row"> <h4 class="wp-mail-smtp-product-education__heading"> <?php esc_html_e( 'Email Log', 'wp-mail-smtp' ); ?> </h4> <p class="wp-mail-smtp-product-education__description"> <?php esc_html_e( 'Email logging makes it easy to save details about all of the emails sent from your WordPress site. You can search and filter the email log to find specific messages and check the color-coded delivery status. Email logging also allows you to resend emails, save attachments, and export your logs in different formats.', 'wp-mail-smtp' ); ?> </p> <a href="<?php echo esc_url( $top_upgrade_button_url ); ?>" target="_blank" rel="noopener noreferrer" class="wp-mail-smtp-product-education__upgrade-btn wp-mail-smtp-product-education__upgrade-btn--top wp-mail-smtp-btn wp-mail-smtp-btn-upgrade wp-mail-smtp-btn-orange"> <?php esc_html_e( 'Upgrade to WP Mail SMTP Pro', 'wp-mail-smtp' ); ?> </a> </div> <div class="wp-mail-smtp-product-education__row"> <div class="wp-mail-smtp-product-education__screenshots wp-mail-smtp-product-education__screenshots--two"> <?php foreach ( $screenshots as $screenshot ) : ?> <div> <a href="<?php echo esc_url( $screenshot['url'] ); ?>" data-lity data-lity-desc="<?php echo esc_attr( $screenshot['title'] ); ?>"> <img src="<?php echo esc_url( $screenshot['url_thumbnail'] ); ?>" alt="<?php esc_attr( $screenshot['title'] ); ?>"> </a> <span><?php echo esc_html( $screenshot['title'] ); ?></span> </div> <?php endforeach; ?> </div> </div> <div class="wp-mail-smtp-product-education__row"> <div class="wp-mail-smtp-product-education__list"> <h4><?php esc_html_e( 'Unlock these awesome logging features:', 'wp-mail-smtp' ); ?></h4> <div> <ul> <li><?php esc_html_e( 'Save detailed email headers', 'wp-mail-smtp' ); ?></li> <li><?php esc_html_e( 'See sent and failed emails', 'wp-mail-smtp' ); ?></li> </ul> <ul> <li><?php esc_html_e( 'Resend emails and attachments', 'wp-mail-smtp' ); ?></li> <li><?php esc_html_e( 'Track email opens and clicks', 'wp-mail-smtp' ); ?></li> </ul> <ul> <li><?php esc_html_e( 'Print email logs or save as PDF', 'wp-mail-smtp' ); ?></li> <li><?php esc_html_e( 'Export logs to CSV, XLSX, or EML', 'wp-mail-smtp' ); ?></li> </ul> </div> </div> </div> <a href="<?php echo esc_url( $bottom_upgrade_button_url ); ?>" target="_blank" rel="noopener noreferrer" class="wp-mail-smtp-product-education__upgrade-btn wp-mail-smtp-product-education__upgrade-btn--bottom wp-mail-smtp-btn wp-mail-smtp-btn-upgrade wp-mail-smtp-btn-orange"> <?php esc_html_e( 'Upgrade to WP Mail SMTP Pro', 'wp-mail-smtp' ); ?> </a> </div> <?php } /** * Not used as we are simply redirecting users. * * @since 1.6.0 * * @param array $data Post data specific for the plugin. */ public function process_post( $data ) { } } Review.php 0000644 00000015522 15252526345 0006534 0 ustar 00 <?php namespace WPMailSMTP\Admin; use WPMailSMTP\Options; /** * Class for admin notice requesting plugin review. * * @since 2.1.0 */ class Review { /** * The name of the WP option for the review notice data. * * Data attributes: * - time * - dismissed * * @since 2.1.0 */ const NOTICE_OPTION = 'wp_mail_smtp_review_notice'; /** * Days the plugin waits before displaying a review request. * * @since 2.1.0 */ const WAIT_PERIOD = 14; /** * Initialize hooks. * * @since 2.1.0 */ public function hooks() { add_action( 'admin_init', [ $this, 'admin_notices' ] ); add_action( 'wp_ajax_wp_mail_smtp_review_dismiss', array( $this, 'review_dismiss' ) ); } /** * Display notices only in Network Admin if in Multisite. * Otherwise, display in Admin Dashboard. * * @since 3.8.0 * * @return void */ public function admin_notices() { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks if ( is_multisite() ) { add_action( 'network_admin_notices', [ $this, 'review_request' ] ); } else { add_action( 'admin_notices', [ $this, 'review_request' ] ); } } /** * Add admin notices as needed for reviews. * * @since 2.1.0 */ public function review_request() { // Only consider showing the review request to admin users. if ( ! is_super_admin() ) { return; } // Verify that we can do a check for reviews. $review = get_option( self::NOTICE_OPTION ); $time = time(); $load = false; if ( empty( $review ) ) { $review = [ 'time' => $time, 'dismissed' => false, ]; update_option( self::NOTICE_OPTION, $review ); } else { // Check if it has been dismissed or not. if ( isset( $review['dismissed'] ) && ! $review['dismissed'] ) { $load = true; } } // If we cannot load, return early. if ( ! $load ) { return; } $this->review(); } /** * Maybe show review request. * * @since 2.1.0 */ private function review() { // Get the currently selected mailer. $mailer = Options::init()->get( 'mail', 'mailer' ); // Skip if no or the default mailer is selected. if ( empty( $mailer ) || $mailer === 'mail' ) { return; } // Fetch when plugin was initially activated. $activated = get_option( 'wp_mail_smtp_activated_time' ); // Skip if the plugin activated time is not set. if ( empty( $activated ) ) { return; } $mailer_object = wp_mail_smtp() ->get_providers() ->get_mailer( $mailer, wp_mail_smtp()->get_processor()->get_phpmailer() ); // Check if mailer setup is complete. $mailer_setup_complete = ! empty( $mailer_object ) ? $mailer_object->is_mailer_complete() : false; // Skip if the mailer is not set or the plugin is active for less then a defined number of days. if ( ! $mailer_setup_complete || ( $activated + ( DAY_IN_SECONDS * self::WAIT_PERIOD ) ) > time() ) { return; } // We have a candidate! Output a review message. ?> <div class="notice notice-info is-dismissible wp-mail-smtp-review-notice"> <div class="wp-mail-smtp-review-step wp-mail-smtp-review-step-1"> <p><?php esc_html_e( 'Are you enjoying WP Mail SMTP?', 'wp-mail-smtp' ); ?></p> <p> <a href="#" class="wp-mail-smtp-review-switch-step" data-step="3"><?php esc_html_e( 'Yes', 'wp-mail-smtp' ); ?></a> • <a href="#" class="wp-mail-smtp-review-switch-step" data-step="2"><?php esc_html_e( 'No', 'wp-mail-smtp' ); ?></a> </p> </div> <div class="wp-mail-smtp-review-step wp-mail-smtp-review-step-2" style="display: none"> <p><?php esc_html_e( 'We\'re sorry to hear you aren\'t enjoying WP Mail SMTP. We would love a chance to improve. Could you take a minute and let us know what we can do better?', 'wp-mail-smtp' ); ?></p> <p> <?php printf( '<a href="%1$s" class="wp-mail-smtp-dismiss-review-notice wp-mail-smtp-review-out" target="_blank" rel="noopener noreferrer">%2$s</a>', // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/plugin-feedback/', [ 'medium' => 'review-notice', 'content' => 'Provide Feedback' ] ) ), esc_html__( 'Provide Feedback', 'wp-mail-smtp' ) ); ?> • <a href="#" class="wp-mail-smtp-dismiss-review-notice" target="_blank" rel="noopener noreferrer"> <?php esc_html_e( 'No thanks', 'wp-mail-smtp' ); ?> </a> </p> </div> <div class="wp-mail-smtp-review-step wp-mail-smtp-review-step-3" style="display: none"> <p><?php esc_html_e( 'That\'s fantastic! Would you consider giving it a 5-star rating on WordPress.org? It will help other users with email issues and it will mean the world to us!', 'wp-mail-smtp' ); ?></p> <p> <a href="https://wpmailsmtp.com/wpmailsmtp-wordpress-review/" class="wp-mail-smtp-dismiss-review-notice wp-mail-smtp-review-out" target="_blank" rel="noopener noreferrer"> <?php esc_html_e( 'Yes, I\'ll rate it with 5-stars', 'wp-mail-smtp' ); ?> </a> • <a href="#" class="wp-mail-smtp-dismiss-review-notice" target="_blank" rel="noopener noreferrer"><?php esc_html_e( 'No, maybe later', 'wp-mail-smtp' ); ?></a> • <a href="#" class="wp-mail-smtp-dismiss-review-notice" target="_blank" rel="noopener noreferrer"><?php esc_html_e( 'I already did', 'wp-mail-smtp' ); ?></a> </p> </div> </div> <script type="text/javascript"> jQuery( document ).ready( function ( $ ) { $( document ).on( 'click', '.wp-mail-smtp-dismiss-review-notice, .wp-mail-smtp-review-notice button', function( e ) { if ( ! $( this ).hasClass( 'wp-mail-smtp-review-out' ) ) { e.preventDefault(); } $.post( ajaxurl, { action: 'wp_mail_smtp_review_dismiss' } ); $( '.wp-mail-smtp-review-notice' ).remove(); } ); $( document ).on( 'click', '.wp-mail-smtp-review-switch-step', function( e ) { e.preventDefault(); var target = parseInt( $( this ).attr( 'data-step' ), 10 ); if ( target ) { var $notice = $( this ).closest( '.wp-mail-smtp-review-notice' ); var $review_step = $notice.find( '.wp-mail-smtp-review-step-' + target ); if ( $review_step.length > 0 ) { $notice.find( '.wp-mail-smtp-review-step:visible' ).fadeOut( function() { $review_step.fadeIn(); } ); } } } ); } ); </script> <?php } /** * Dismiss the review admin notice. * * @since 2.1.0 */ public function review_dismiss() { $review = get_option( self::NOTICE_OPTION, [] ); $review['time'] = time(); $review['dismissed'] = true; update_option( self::NOTICE_OPTION, $review ); if ( is_super_admin() && is_multisite() ) { $site_list = get_sites(); foreach ( (array) $site_list as $site ) { switch_to_blog( $site->blog_id ); update_option( self::NOTICE_OPTION, $review ); restore_current_blog(); } } wp_send_json_success(); } }
dvadf
dvadf
| ver. 1.4 |
Github
|
.
| PHP 7.3.33 | Generation time: 0.04 |
proxy
|
phpinfo
|
Settings